Published: 2026-04-14
PostgreSQL JSON and JSONB: The Complete Guide for SQL Developers
PostgreSQL has some of the most mature JSON support of any relational database. jsonb, GIN indexes, the SQL/JSON path language, and over a decade of operator evolution mean a single Postgres instance can do the work most teams used to split between Postgres and MongoDB. As of May 2026, PostgreSQL 18 is GA and ships every feature in this guide. Here's the practical guide.
Quick Answers
Q: What is the difference between json and jsonb in PostgreSQL?
A: json stores the original document text verbatim — whitespace, key order, and duplicate keys are all preserved. jsonb parses to a binary tree at insert time, strips whitespace, sorts keys, and collapses duplicates. jsonb is slightly slower to write and dramatically faster to query, supports GIN indexing, and is what almost every production app should use.
Q: Can I index JSON paths in PostgreSQL?
A: Yes. jsonb supports GIN on the whole document (default jsonb_ops for any operator, smaller jsonb_path_ops for containment only), plus B-tree expression indexes on a single extracted path like ((data->>'email')).
Q: How do I query nested JSON in PostgreSQL?
A: Use -> to traverse keys (returns the same type as the input), ->> to extract a value as text, and #> / #>> to traverse a path array. The SQL/JSON path language (PG 12) adds jsonb_path_query, jsonb_path_exists, and the @? / @@ operators; PG 17 adds the standard JSON_TABLE, JSON_VALUE, JSON_QUERY, and JSON_EXISTS functions.
Q: Can PostgreSQL replace MongoDB?
A: For most document-store workloads, yes. jsonb gives you indexed nested queries, JSONPath, full-text search, ACID transactions across documents and relational tables, and joins. MongoDB still wins for sharded multi-region writes and the aggregation pipeline at TB scale.
A Brief History of JSON in PostgreSQL
PostgreSQL's JSON story is older than most developers realize. The json type landed in 9.2 (2012), the binary jsonb type in 9.4 (2014), the SQL/JSON path language in 12 (2019), the IS JSON predicate and pg_input_is_valid in 16, and the standard SQL/JSON functions (JSON_TABLE, JSON_VALUE, JSON_QUERY, JSON_EXISTS) in 17 (2024). Among open-source relational databases, PostgreSQL's SQL/JSON coverage is now one of the most complete.
json vs jsonb — Pick One and Move On
PostgreSQL ships two JSON types. They look identical from SQL but behave very differently underneath:
jsonstores the original text of the document verbatim. Whitespace, key order, and duplicate keys are preserved. Each query has to re-parse the text.jsonbparses the document at insert time and stores a binary tree. Whitespace is stripped, keys are sorted, and duplicates collapse to the last value. Queries skip parsing and can use indexes.
Use jsonb. The only reason to pick json is if your application depends on byte-for-byte preservation of an externally-signed document — a rare case. As of PG 17, the write-time cost of binary parsing is rarely measurable against the read-time savings on any non-trivial workload.
Querying JSON: Operators You Will Actually Use
PostgreSQL exposes JSON access through operators rather than function calls. The shortlist: See the PostgreSQL JSON functions and operators reference for the full list.
-- Extract a field as jsonb (chainable) SELECT data->'address'->'city' FROM customers; -- Extract as text (for comparisons in WHERE) SELECT * FROM customers WHERE data->>'email' = '[email protected]'; -- Traverse a deep path SELECT data #>> '{address,city}' FROM customers; -- Containment: does the document contain this fragment? SELECT * FROM orders WHERE items @> '[{"sku":"ABC-123"}]'; -- Key/path existence SELECT * FROM customers WHERE data ? 'phone'; SELECT * FROM customers WHERE data @? '$.address.city'; -- SQL/JSON path predicate SELECT * FROM products WHERE specs @@ '$.weight_kg > 5';
The mental model: -> keeps you in JSON-land, ->> drops you into SQL text-land. Almost every WHERE clause ends with ->>.
Indexing JSON Paths
This is where PostgreSQL leaves every other relational database behind. Three useful index strategies, all built on GIN indexes: (See jsonb Indexing in the Postgres docs.)
1. GIN on the whole document
Indexes every key and value. Best when you don't know which paths users will filter on:
CREATE INDEX idx_customers_data ON customers USING GIN (data);
2. GIN with jsonb_path_ops
Smaller and faster than the default operator class, but only supports the @> containment operator. Pick this when containment is your only access pattern:
CREATE INDEX idx_customers_data_ops ON customers USING GIN (data jsonb_path_ops);
3. B-tree expression index on a known path
The fastest option when you have a single hot path. Behaves exactly like a normal column index:
CREATE INDEX idx_customers_email ON customers ((data->>'email'));
Validation, Generation, and Mutation
PostgreSQL exposes a full set of helpers that often surprise developers used to other engines:
jsonb_set(doc, path, value)— immutable update; returns a new documentdoc - 'key'— remove a keydoc || other_doc— concatenate / shallow-mergejsonb_pretty(doc)— format for humansjsonb_typeof(value)— runtime type checkpg_input_is_valid(text, 'jsonb')— non-throwing validity check (16+)
PostgreSQL JSONB vs MongoDB
This is the question every team asks once they realize how capable jsonb is. The honest answer:
| Concern | PostgreSQL jsonb | MongoDB |
|---|---|---|
| Schema flexibility | Per-column schemaless inside a typed table | Per-collection schemaless by default |
| Joins between documents | Native SQL joins, fast and arbitrary | $lookup aggregation, slower and limited |
| Multi-document transactions | First-class — same ACID engine as everything else | Supported since 4.0, but with sharding caveats |
| Indexing | B-tree, GIN, GiST, BRIN, expression indexes on any path | B-tree, hashed, multikey, geospatial, text |
| Aggregation | Standard SQL + window functions | Aggregation pipeline (more verbose, more visual) |
| Sharding | External (Citus, partitioning) | Built-in |
| Operational footprint | One database for documents and relations | Separate cluster, separate ops |
Pick PostgreSQL when your data has any relational structure at all, you want one database to operate, or you need real cross-document transactions and joins.
Pick MongoDB when your collections grow into the multi-TB range, you need shard-aware writes from day one, or your team prefers the aggregation pipeline mental model.
For most product startups, the answer is jsonb. The "we'll need MongoDB later for scale" decision is usually paid for in immediate complexity and almost never recouped.
A Realistic Schema Pattern
The pattern that ages well: keep the columns you query and join on as real columns, and put the long tail of optional fields into jsonb:
CREATE TABLE events (
id bigserial PRIMARY KEY,
user_id bigint NOT NULL REFERENCES users(id),
event_type text NOT NULL,
occurred_at timestamptz NOT NULL DEFAULT now(),
properties jsonb NOT NULL DEFAULT '{}'::jsonb
);
CREATE INDEX idx_events_user_time
ON events (user_id, occurred_at DESC);
CREATE INDEX idx_events_props
ON events USING GIN (properties jsonb_path_ops);You get fast user-timeline queries from the B-tree, and any new event property is queryable through containment without a migration.
Working with PostgreSQL JSON in a SQL IDE
JSON columns are notoriously painful in most SQL IDEs — they render as a single line of unreadable text, you can't filter by path, and there's no way to peek at the document shape without writing a query.
Jam SQL Studio treats json and jsonb as first-class. Cells render as collapsible trees, the filter chip exposes JSONPath operators (has property, property =, property contains) that compile to @>, ->>, and the SQL/JSON path operators underneath, and a Peek popover scans the loaded rows to show you which paths exist and how often. The same UI works against nvarchar(max) on SQL Server, JSON on MySQL, CLOB on Oracle, and TEXT on SQLite — one mental model, five engines.
Browse PostgreSQL JSON Without Writing SELECTs
Jam SQL Studio renders jsonb as a tree, filters by JSON path, and previews the document shape. Free for personal use.
Last verified against the official PostgreSQL documentation on 2026-05-13.
Jam SQL Studio