JSONB Operations
PostgreSQL's JSONB is first-class in Zanith. Extract values, check containment, test key existence — all through typed field references.
When to use JSONB
Not everything fits neatly into columns. User preferences, feature flags, form submissions, API responses — JSONB lets you store structured data without creating new tables for every shape. The tradeoff is less type safety (it's unknown by default), but more flexibility.
Defining a JSONB field
const User = defineModel((m) => ({ name: 'User', table: 'users', fields: { ...m.id(), email: m.string(), // Untyped JSON (flexible) settings: m.json().nullable(), // Typed JSON (if you know the shape) preferences: m.json<{ theme: string; language: string }>().nullable(), },}));Text extraction (→>>)
Extract a text value from a JSON key. This is the most common operation — it returns the value as a string, which you can then compare.
// Find posts where metadata.type = 'article'const articles = await db.post.query() .where(({ post }) => post.metadata.jsonText('type').eq('article') ) .execute(); // Generated SQL:// WHERE "posts"."metadata"->>'type' = $1// Params: ['article']Containment (@>)
Check if a JSONB column contains a specific structure. This is powerful for filtering by nested values without extracting each key individually.
// Find posts with { featured: true } in their metadataconst featured = await db.post.query() .where(({ post }) => post.metadata.jsonContains({ featured: true }) ) .execute(); // Generated SQL:// WHERE "posts"."metadata" @> $1::jsonb// Params: [{ featured: true }] // Works with nested structures too:post.metadata.jsonContains({ config: { priority: 'high' } })Key existence (?)
Check if a key exists in the JSON object — regardless of its value.
// Find posts that have a 'tags' key in their metadataconst tagged = await db.post.query() .where(({ post }) => post.metadata.jsonHasKey('tags') ) .execute(); // Generated SQL:// WHERE "posts"."metadata" ? $1// Params: ['tags']Operator reference
| Method | PostgreSQL operator | Use case |
|---|---|---|
.jsonText(key) | ->> 'key' | Extract text value, then compare with .eq(), .contains(), etc. |
.jsonPath(key) | -> 'key' | Extract JSON value (not text) for nested access |
.jsonContains(obj) | @> obj::jsonb | Check if JSON contains a structure |
.jsonHasKey(key) | ? 'key' | Check if a key exists in the JSON object |
Combining with other filters
// Published posts with type='article' and featured=trueconst results = await db.post.query() .with({ author: true }) .where(({ post, author }) => and( post.published.eq(true), post.metadata.jsonText('type').eq('article'), post.metadata.jsonContains({ featured: true }), author.role.eq('ADMIN') ) ) .execute();JSONB filters work seamlessly with regular field filters, relational filters, and logical combinators (and, or, not).