> For the complete documentation index, see [llms.txt](https://crystal-garage.gitbook.io/lustra-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://crystal-garage.gitbook.io/lustra-docs/additional-and-advanced-features/jsonb.md).

# JSONB

Lustra supports PostgreSQL `jsonb` columns through normal model columns and expression helpers.

```crystal
class User
  include Lustra::Model

  column notification_preferences : JSON::Any
end
```

In migrations, create a `jsonb` column and usually add a GIN index when you query it often.

```crystal
create_table(:users) do |t|
  t.column :notification_preferences, :jsonb, index: "gin", default: "'{}'"
end
```

## JSONB Paths

Use `jsonb(path)` in query expressions. Dot-separated paths are converted to PostgreSQL JSONB operators.

```crystal
User.query.where { notification_preferences.jsonb("email.enabled") == true }
```

For literal equality, Lustra uses the index-friendly `@>` containment form.

```sql
WHERE "notification_preferences" @> '{"email":{"enabled":true}}'
```

Escape a literal dot in a key with a backslash.

```crystal
User.query.where { notification_preferences.jsonb("external\\.id") == "abc" }
```

## Casting

Use `cast` when you need to compare a JSONB path as another SQL type.

```crystal
User.query.where {
  notification_preferences.jsonb("email.frequency").cast("text") == "daily"
}
```

Casting uses arrow notation instead of the containment form.

## Key Existence

Use the JSONB key helpers for PostgreSQL `?`, `?|`, and `?&`.

```crystal
User.query.where { notification_preferences.jsonb_key_exists?("email") }

User.query.where {
  notification_preferences.jsonb_any_key_exists?(["email", "sms"])
}

User.query.where {
  notification_preferences.jsonb_all_keys_exists?(["email", "sms"])
}
```

You can call key helpers on nested JSONB paths too.

```crystal
User.query.where {
  notification_preferences.jsonb("channels").jsonb_key_exists?("email")
}
```

## JSONB Arrays

Use `contains?` on a JSONB path to test whether an array contains a value.

```crystal
User.query.where {
  notification_preferences.jsonb("enabled_channels").contains?("email")
}
```

## Low-Level Helpers

`Lustra::SQL::JSONB` exposes helper methods when you need SQL fragments outside the expression engine.

```crystal
include Lustra::SQL::JSONB

jsonb_exists?("notification_preferences", "email")
jsonb_any_exists?("notification_preferences", ["email", "sms"])
jsonb_all_exists?("notification_preferences", ["email", "sms"])
jsonb_eq("notification_preferences", "email.enabled", true)
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://crystal-garage.gitbook.io/lustra-docs/additional-and-advanced-features/jsonb.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
