Start Here
JSONB is useful when only part of the row is messy.
That is common in early products. You know the user, account, owner, timestamps, and permissions. You do not yet know every setting, trait, event payload, or integration field.
Put the stable parts in normal columns. Put the moving part in JSONB.
What To Build First
Do not make the whole table a document. Keep the important columns visible.
create table profiles (
id bigserial primary key,
user_id bigint not null,
created_at timestamptz not null default now(),
traits jsonb not null default '{}'
);
create index profiles_traits_gin
on profiles using gin (traits);
Now you can query flexible fields without losing the normal database shape.
select id, user_id
from profiles
where traits @> '{"plan":"trial"}';
Good First Uses
- Sparse profile fields.
- Integration payloads.
- Event metadata.
- Feature flags or product settings that are still changing.
- Extra attributes on a relational object.
The key phrase is “extra attributes.” If the whole product is document-shaped, JSONB may not be the right default.
The Part People Skip
Index for real queries only.
JSONB makes it easy to store many shapes, but it also makes it easy to hide a bad model. If you start adding indexes for every key, you are rebuilding schema design the hard way.
When a field becomes important, promote it:
alter table profiles
add column plan text;
update profiles
set plan = traits->>'plan'
where traits ? 'plan';
That is not a failure. It is the product telling you the field is now part of the core model.
What This Does Not Replace
JSONB is not a reason to avoid schema design forever. It is a way to buy time for the parts that are still unknown.
Use normal columns for fields used in permissions, billing, joins, sorting, and core filters. Those fields deserve names, types, and constraints.
Move To A Document Database When
- Most of the workload is document-native.
- Joins and constraints are mostly getting in your way.
- Your team wants the operational model of a document database.
- You keep fighting Postgres instead of using its strengths.
For many apps, JSONB is enough for the flexible part while Postgres handles the system of record.