Blog Post

JSONB in PostgreSQL: The Beginner’s Guide I Wish I Had

PostgreSQL’s JSONB data type gives you the flexibility of a document store while keeping all the relational superpowers you already know. But the truth is simple: most developers struggle with JSONB purely because they forget the syntax. The arrows, operators, casting — it’s easy to mix them up when you haven’t used them in a while.

This guide is the refresher I wish someone handed me years ago. We’ll walk through creating JSONB columns, inserting data, reading values, filtering, updating, and indexing — using examples based on the Pagila Sample Database.

What Exactly Is JSONB?

PostgreSQL has two JSON types: json and jsonb. The “b” stands for binary, which means PostgreSQL stores it in an optimized, index-friendly format.

  • json = stored as text, no indexing
  • jsonb = binary, deduplicated, sortable, indexable

If you need performance or indexing, always choose jsonb.

Adding a JSONB Column

Let’s extend Pagila’s customer table with a JSONB column.


ALTER TABLE customer
ADD COLUMN metadata JSONB;
    

Insert some JSON data:


UPDATE customer
SET metadata = jsonb_build_object(
  'preferences', jsonb_build_object(
      'email', (customer_id = 1),          -- only customer_id = 1 gets true
      'sms', (customer_id % 3 = 0)         -- true for 3, 6, 9, ...
  ),
  'tags', CASE
            WHEN customer_id % 5 = 0 THEN '["vip", "priority"]'::jsonb
            WHEN customer_id % 2 = 0 THEN '["loyal"]'::jsonb
            ELSE '["new"]'::jsonb
          END
);
    

How to Retrieve Values from JSONB

This is where most people get confused. PostgreSQL gives you two arrow operators:

  • -> returns JSON (object or array)
  • ->> returns text

Get the whole object:


SELECT metadata->'preferences'
FROM customer;
    

Extract a boolean or string as text:


SELECT metadata->'preferences'->>'email' AS email_opt_in
FROM customer;
    

Extract an array element:


SELECT metadata->'tags'->>0 AS first_tag
FROM customer;
    

Filtering Rows Based on JSONB

JSONB filtering is where the syntax surprises most beginners. You have two main ways to filter: containment and value extraction.

1. Containment: find rows containing JSON structure


SELECT customer_id
FROM customer
WHERE metadata @> '{"preferences": {"email": true}}';
    

Containment checks whether the right-hand JSON is a subset of the left. It’s incredibly powerful — and incredibly misunderstood — but perfect when you want to match entire JSON structures.

2. Extract and compare a specific value


SELECT customer_id
FROM customer
WHERE metadata->'preferences'->>'sms' = 'false';
    

Use this approach when you care about a single field, not a whole structure.

Updating JSONB Values

JSONB isn’t like a regular column — you don’t update individual keys directly. You use jsonb_set, which returns a modified copy.

Update a single key:


UPDATE customer
SET metadata = jsonb_set(
      metadata,
      '{preferences,email}',
      'false'::jsonb
    )
WHERE customer_id = 1;
    

Add a new key:


UPDATE customer
SET metadata = jsonb_set(
      metadata,
      '{profile}',
      '{"created_at": "2025-01-01"}',
      true
    );
    

The final true tells PostgreSQL to create missing keys as needed.

Working with JSONB Arrays

Append to an array:


UPDATE customer
SET metadata = jsonb_set(
      metadata,
      '{tags}',
      (metadata->'tags') || '["new"]'::jsonb
    );
    

Check if an array contains a value:


SELECT customer_id, metadata
FROM customer
WHERE metadata->'tags' ? 'vip';
    

The ? operator checks whether the array contains the given string.

Indexing JSONB (Beginner Edition)

JSONB without indexing is fun for learning… and painful in production. Here are the two simple indexes everyone should start with.

1. GIN index for containment queries


CREATE INDEX idx_customer_metadata_gin
ON customer USING GIN (metadata);
    

2. BTREE expression index for single-key lookups


CREATE INDEX idx_customer_sms_pref
ON customer ((metadata->'preferences'->>'sms'));
    

That’s the beginner-friendly version. Start here — then go read my Advanced JSONB article when you want to get serious!

When to Use JSONB (and When Not To)

JSONB is great for:

  • Optional or sparse data
  • Storing API payloads
  • Per-user configuration
  • Fields that change shape over time

JSONB is not great for:

  • Highly relational data
  • Values you frequently filter on
  • Columns that need strict constraints
  • Data that must be sorted efficiently

Conclusion

JSONB looks intimidating until you’ve used it a few times. Once the operators click, you realize just how powerful PostgreSQL becomes when you mix relational structure with flexible documents.

Use this guide as your cheat sheet whenever you forget the syntax. And when you’re ready to push JSONB to its limits — indexing, performance tuning, query patterns — check out my Advanced JSONB article!