JSONB in PostgreSQL

JSONB is PostgreSQL's binary JSON type — it lets you store, query, and index JSON documents inside a relational database, giving Postgres much of the flexibility of a document store like MongoDB without leaving SQL. You can keep semi-structured or schema-flexible data in a single column, query deep into it, and index it for fast lookups, all alongside your normal relational tables and transactions.

JSONB is a big reason "just use Postgres" holds up against reaching for a separate NoSQL database. When part of your data is genuinely schema-flexible — user preferences, event payloads, third-party API responses, product attributes that vary by category — JSONB stores it naturally while the rest of your schema stays relational. The skill is knowing when that flexibility helps and when a normal column is the better choice.

TL;DR

Quick Example

Store a flexible document, then query and filter into it with JSONB operators and a GIN index for speed:

The @> containment query, backed by a GIN index, finds matching documents without scanning the table.

Core Concepts

JSONB vs JSON

Postgres has two JSON types, and the choice is almost always JSONB:

Use json only in the rare case you must preserve the exact input text byte-for-byte. For everything you'll query or index, use JSONB.

Accessing Data

JSONB has a family of operators for reaching into documents:

Indexing With GIN

Without an index, a JSONB query scans and inspects every row. A GIN (Generalized Inverted Index) makes containment (@>) and key-existence queries fast by indexing the document's keys and values:

jsonb_path_ops is a more compact GIN variant optimized specifically for @> containment. For a single frequently-filtered field, a plain expression index on the extracted value (CREATE INDEX ON products ((attrs->>'cpu'))) can be smaller and faster than a whole-document GIN index.

When to Use JSONB (and When Not To)

JSONB is powerful and easy to overuse. A guide:

Use JSONB when:

Prefer normal columns when:

💡 Tip: A good hybrid pattern: promote the fields you query and constrain on into real columns, and keep the long tail of variable attributes in a JSONB column. You get relational rigor where it matters and flexibility where you need it.

Best Practices

Default to JSONB, Reserve JSON for Exact-Text Cases

Unless you specifically need to preserve the original text (whitespace, key order, duplicate keys), use JSONB — it's queryable and indexable. Choosing json for a column you'll filter on is a common early misstep.

Index the Access Pattern, Not the Whole Column Reflexively

Match the index to how you query. Use a GIN index (or jsonb_path_ops) for containment queries across the document; use an expression index for a single hot field. Indexing everything wastes space; indexing nothing scans every row.

Don't Abandon Schema Design

JSONB is a tool for the flexible parts of your data, not a license to store everything as one giant blob. Over-nesting relational data into JSONB loses joins, constraints, and query clarity — the reasons you picked Postgres. Model stable data relationally.

Cast Explicitly for Comparisons

->> returns text; comparing or doing math needs a cast ((attrs->>'ram_gb')::int). Forgetting the cast leads to string comparisons ("100" < "9") and subtle bugs.

Validate Shape at the Edges

Postgres won't enforce a JSONB document's structure by default. If a document must have certain fields/types, validate in the application or with a CHECK constraint / trigger, so garbage doesn't accumulate in the column.

Common Mistakes

Using json Instead of jsonb

Querying JSONB Without an Index

Storing Relational Data as a Blob

Dumping structured, frequently-joined data into one JSONB column throws away foreign keys, constraints, and efficient joins. Keep well-structured data in columns; use JSONB for the variable remainder.

FAQ

Should I use json or jsonb?

Almost always jsonb. It stores JSON in a binary form that's fast to query and can be indexed with GIN, at the cost of slightly slower writes and not preserving the exact input text (whitespace, key order, duplicate keys). Use plain json only when you must retain the original text byte-for-byte and won't query into it.

How do I make JSONB queries fast?

Index them. For containment (@>) and key-existence queries across a document, add a GIN index (USING gin (col), or the smaller jsonb_path_ops variant for @>-only). For a single field you filter on constantly, an expression index on the extracted value (((col->>'field'))) is often smaller and faster. Without an index, JSONB queries scan every row.

What's the difference between -> and ->>?

-> returns the extracted value as JSONB (so you can chain further into it); ->> returns it as text. Use -> when navigating deeper into nested JSON, and ->> when you want the scalar value out — remembering to cast it ((col->>'n')::int) for numeric comparisons or math.

Does JSONB replace a NoSQL document database?

For many use cases, yes — JSONB gives Postgres document-style storage, deep querying, and indexing while keeping SQL, joins, transactions, and constraints on the rest of your data. A dedicated document database may still win at extreme document-oriented scale or specific features, but JSONB lets a single Postgres serve both relational and flexible data, avoiding a second system.

When should I NOT use JSONB?

When the data has a stable, known shape and you filter, join, sort, or constrain on it heavily — normal columns are faster, clearer, and support first-class constraints (foreign keys, NOT NULL, uniqueness). JSONB is for genuinely flexible, sparse, or externally-shaped data. Don't use it to avoid schema design for structured data.

Related Topics

References