From daf4384f874c2289684bd099f5c101ffc999f004 Mon Sep 17 00:00:00 2001 From: Brainslug Date: Mon, 20 Apr 2026 17:04:16 +0200 Subject: [PATCH 01/18] basic json query docs --- content/_partials/json-filter.md | 270 ++++++++++++++++++++ content/_partials/json-function.md | 209 ++++++++++++++- content/_partials/query-functions.md | 1 - content/guides/04.connect/2.filter-rules.md | 6 +- 4 files changed, 482 insertions(+), 4 deletions(-) create mode 100644 content/_partials/json-filter.md diff --git a/content/_partials/json-filter.md b/content/_partials/json-filter.md new file mode 100644 index 000000000..300aabe6f --- /dev/null +++ b/content/_partials/json-filter.md @@ -0,0 +1,270 @@ +## The `_json` Filter Operator + +The `_json` operator filters items by values inside a JSON field. It accepts an object mapping JSON paths to standard filter operators, letting you compare specific keys or array elements without loading the full document. + +`_json` is only valid on `json`-typed fields. + +### Syntax + +``` +?filter={"field":{"_json":{"path":{"_operator":value}}}} +``` + +**Examples:** + +``` +?filter={"metadata":{"_json":{"color":{"_eq":"blue"}}}} +?filter={"metadata":{"_json":{"price":{"_gte":100}}}} +?filter={"metadata":{"_json":{"tags[0]":{"_null":true}}}} +?filter={"metadata":{"_json":{"settings.theme":{"_contains":"dark"}}}} +``` + +Path keys inside `_json` use the same dot-and-bracket notation as the [`json(field, path)` function](/guides/connect/query-parameters). + +### Supported Inner Operators + +| Category | Operators | +| --------------- | --------------------------------------------------------- | +| Equality | `_eq`, `_neq`, `_ieq`, `_nieq` | +| Null | `_null`, `_nnull` | +| Set | `_in`, `_nin` | +| String | `_contains`, `_ncontains`, `_icontains`, `_nicontains` | +| Prefix / Suffix | `_starts_with`, `_ends_with` (plus `_i` variants) | +| Numeric | `_gt`, `_gte`, `_lt`, `_lte`, `_between`, `_nbetween` | +| Empty | `_empty`, `_nempty` | + +### Relational JSON Filtering + +`_json` nests under relational keys the same way other filters do. To filter on a JSON field belonging to a related item, nest `_json` under the relation name: + +```json +{ + "category_id": { + "metadata": { + "_json": { + "color": { "_eq": "blue" } + } + } + } +} +``` + +This matches articles whose related category has `metadata.color = "blue"`. + +### Combining Multiple Conditions + +Combine multiple `_json` filters at the top level with `_and` or `_or`: + +```json +{ + "_and": [ + { "metadata": { "_json": { "color": { "_eq": "blue" } } } }, + { "metadata": { "_json": { "size": { "_gt": 10 } } } } + ] +} +``` + +You can also group conditions inside a single `_json` value using `_and` or `_or`: + +```json +{ + "metadata": { + "_json": { + "_and": [ + { "color": { "_eq": "blue" } }, + { "size": { "_gt": 10 } } + ] + } + } +} +``` + +### Dynamic Variables + +Dynamic filter variables like `$CURRENT_USER` and `$NOW` work inside `_json` inner values. They are resolved before the filter runs, so they apply in permission rules and regular queries. + +### Database-Specific Behavior + +**PostgreSQL numeric comparisons** + +PostgreSQL extracts JSON scalars as `text`. Directus automatically casts to a numeric type when the filter value is a number or an array of numbers, so `_gt`, `_lt`, `_between`, and related operators work correctly in those cases. If you supply a numeric comparison with a string value (for example `{"version":{"_gt":"9"}}`), the comparison remains lexicographic. Use a numeric literal to get numeric comparison. + +**SQLite** + +SQLite may return `0` or `1` instead of boolean values when the path resolves to a boolean. + +**MSSQL / Oracle** + +Both dialects always return scalar values as strings regardless of the original JSON type. Apply any needed coercion in your application. + +### GraphQL + +Each `json`-typed field in a filter input type accepts `_json`, `_null`, and `_nnull`. The `_json` value is an object mapping JSON path strings to standard filter operators. + +#### Simple Equality Filter + +Path keys that are valid GraphQL identifiers (letters, digits, and `_`, with no dots or brackets) can be written inline: + +```graphql +{ + articles(filter: { metadata: { _json: { color: { _eq: "blue" } } } }) { + id + title + } +} +``` + +#### Filter With Operators + +```graphql +{ + articles( + filter: { + metadata: { + _json: { + color: { _in: ["red", "blue"] } + brand: { _nnull: true } + description: { _contains: "premium" } + } + } + } + ) { + id + title + } +} +``` + +#### Paths With Dots or Brackets + +GraphQL input-object keys must be valid identifiers, so paths containing dots, brackets, or starting with `[` cannot be written inline. This includes: + +- `settings.theme` (dot-separated nested path) +- `tags[0]` (bracket index notation) +- `[0].test` (path starting with an array index) + +Pass the `_json` value as a typed variable instead: + +```graphql +query FilterByNestedPath($jsonFilter: JSON) { + articles(filter: { metadata: { _json: $jsonFilter } }) { + id + title + } +} +``` + +Variables: + +```json +{ + "jsonFilter": { + "settings.theme": { "_eq": "dark" }, + "tags[0]": { "_eq": "electronics" }, + "[0].test": { "_null": false } + } +} +``` + +#### Combining `_json` With `_and` / `_or` + +```graphql +{ + articles( + filter: { + _and: [ + { metadata: { _json: { color: { _eq: "blue" } } } } + { metadata: { _json: { level: { _gte: 3 } } } } + ] + } + ) { + id + title + } +} +``` + +#### Relational JSON Filter + +```graphql +{ + articles(filter: { category_id: { metadata: { _json: { color: { _eq: "blue" } } } } }) { + id + title + category_id { + name + } + } +} +``` + +### TypeScript SDK + +The `_json` operator is available on any field in the `filter` object. The SDK types it as `Record>`. The server enforces at runtime that `_json` is only valid on `json`-typed fields. + +#### Simple Equality + +```typescript +const items = await client.request( + readItems('articles', { + filter: { + metadata: { + _json: { color: { _eq: 'blue' } }, + }, + }, + }), +); +``` + +#### Multiple Path Conditions in One `_json` + +```typescript +const items = await client.request( + readItems('articles', { + filter: { + metadata: { + _json: { + color: { _eq: 'red' }, + brand: { _in: ['BrandX', 'BrandY'] }, + level: { _gte: 3 }, + }, + }, + }, + }), +); +``` + +#### Dot-Notation and Bracket Paths + +Path keys with dots or brackets are plain strings. No special SDK handling is needed: + +```typescript +const items = await client.request( + readItems('articles', { + filter: { + metadata: { + _json: { + 'settings.theme': { _eq: 'dark' }, + 'tags[0]': { _eq: 'electronics' }, + }, + }, + }, + }), +); +``` + +#### Relational `_json` Filter + +```typescript +const items = await client.request( + readItems('articles', { + filter: { + category_id: { + metadata: { + _json: { color: { _eq: 'blue' } }, + }, + }, + }, + }), +); +``` diff --git a/content/_partials/json-function.md b/content/_partials/json-function.md index 37e2bb782..c2d999724 100644 --- a/content/_partials/json-function.md +++ b/content/_partials/json-function.md @@ -5,8 +5,9 @@ The `json(field, path)` function extracts the value from the specified path in a ::callout{icon="material-symbols:warning-rounded" color="warning"} -**Supported Paramaters** -the `json(field, path)` function is currently only supported for use in the `fields` query parameter. +**Supported Parameters** + +The `json(field, path)` function is not supported in the `filter`. For filtering JSON fields, use the [`_json` filter operator](/guides/connect/filter-rules) instead. :: @@ -213,3 +214,207 @@ Similarly, because MySQL and MariaDB path conversion uses dot-notation (`$.key.s **Oracle** - Similar to MSSQL will also return scalar values as **strings**, regardless of the original JSON type (number, boolean, etc.). A JSON number `3.14` will be returned as `"3.14"`. + +### GraphQL + +Each `json`-typed field exposes a `json(path: String!)` sub-field inside `{fieldName}_func`. The `path` argument is required. The return type is `JSON` (any scalar, object, or array). + +`{fieldName}_func` already exists for the `count` sub-field. `json` sits alongside it in the same selection. + +#### Simple Scalar Extraction + +```graphql +{ + articles { + id + title + metadata_func { + json(path: "color") + } + } +} +``` + +```json +{ + "data": { + "articles": [ + { "id": 1, "title": "An Article", "metadata_func": { "json": "blue" } } + ] + } +} +``` + +#### Multiple Paths From the Same Field + +Request multiple paths inside a single `{fieldName}_func` selection by using **field aliases** on the `json` sub-field: + +```graphql +{ + articles { + id + metadata_func { + color: json(path: "color") + theme: json(path: "settings.theme") + firstTag: json(path: "tags[0]") + } + } +} +``` + +```json +{ + "data": { + "articles": [ + { + "id": 1, + "metadata_func": { + "color": "blue", + "theme": "dark", + "firstTag": "electronics" + } + } + ] + } +} +``` + +#### Extracting an Object or Array + +When the path points to an object or array rather than a scalar, the full value is returned as parsed JSON: + +```graphql +{ + articles { + id + metadata_func { + dimensions: json(path: "dimensions") + tags: json(path: "tags") + } + } +} +``` + +```json +{ + "data": { + "articles": [ + { + "id": 1, + "metadata_func": { + "dimensions": { "width": 10, "height": 20, "depth": 5 }, + "tags": ["electronics", "premium", "new"] + } + } + ] + } +} +``` + +#### Relational JSON Extraction + +For a Many-to-One relation, request the `json` function on the related collection's field: + +```graphql +{ + articles { + id + category_id { + name + metadata_func { + color: json(path: "color") + } + } + } +} +``` + +### TypeScript SDK + +The SDK accepts `json(fieldName, path)` strings in the `fields` array. The first argument is constrained by TypeScript to fields typed as `json` in your schema. An invalid field name produces a compile-time error. The path (second argument) is a plain `string` and is not validated at compile time. + +The SDK also computes the response alias type automatically from the literal string, so extracted values are fully typed with no manual type extensions needed. The alias rule is `{field}_{path}_json` with `.`, `[`, and `]` replaced by `_`. + +```typescript +import { createDirectus, readItems, rest } from '@directus/sdk'; + +interface Article { + id: number; + title: string; + metadata: 'json' | null; // type literal 'json' tells the SDK this is a json field +} + +interface Schema { + articles: Article[]; +} + +const client = createDirectus('https://directus.example.com').with(rest()); + +const items = await client.request( + readItems('articles', { + fields: ['id', 'title', 'json(metadata, color)'], + }), +); + +// metadata_color_json is automatically typed as JsonValue | null — no cast needed +const color = items[0].metadata_color_json; +``` + +#### Multiple Paths + +```typescript +const items = await client.request( + readItems('articles', { + fields: ['id', 'title', 'json(metadata, color)', 'json(metadata, settings.theme)', 'json(metadata, tags[0])'], + }), +); + +// All aliases are typed directly on items: +// items[0].metadata_color_json // JsonValue | null +// items[0].metadata_settings_theme_json // JsonValue | null +// items[0].metadata_tags_0_json // JsonValue | null +``` + +#### Via a Relational Field + +Pass `json(field, path)` inside a relational field object. The extracted alias appears typed on the related item. + +```typescript +const items = await client.request( + readItems('articles', { + fields: ['id', 'title', { category_id: ['name', 'json(metadata, color)'] }], + }), +); + +const color = items[0].category_id.metadata_color_json; +``` + +#### Compile-Time Safety + +The SDK enforces that the first argument must be a `json`-typed field, and that the output alias is typed. Non-json fields produce a TypeScript error: + +```typescript +// valid: metadata is a json field; metadata_color_json is typed as JsonValue | null +readItems('articles', { fields: ['json(metadata, color)'] }); + +// compile error: title is a string field, not json +readItems('articles', { fields: ['json(title, color)'] }); +``` + +::callout{icon="material-symbols:info-outline"} +**Alias Typing Requires Literal Field Arrays** + +Alias typing only works when the `fields` array is an inline literal or typed `as const`. If the array is built dynamically at runtime, TypeScript widens it to `string[]` and the aliases are not present in the inferred return type. +:: + +#### IDE Autocomplete + +When typing inside the `fields` array, the SDK provides partial autocomplete for the `json()` function. For each `json`-typed field in your schema, the IDE offers `json(fieldName, ` as a completion, positioning the cursor ready for the path argument: + +```typescript +// Typing 'json(' in the fields array surfaces: +// json(metadata, ← cursor positioned here, type your path then close with ) +readItems('articles', { fields: ['json(metadata, color)'] }); +``` + +This works via TypeScript's template-literal completion (TypeScript >= 4.7). Only `json`-typed fields appear as suggestions. The path argument is a free string and has no completion hints. diff --git a/content/_partials/query-functions.md b/content/_partials/query-functions.md index 7ec38a641..85cd4319f 100644 --- a/content/_partials/query-functions.md +++ b/content/_partials/query-functions.md @@ -13,4 +13,3 @@ The syntax for using a function is `function(field)`. | `minute` | Extract the minute from a datetime/date/timestamp field | | `second` | Extract the second from a datetime/date/timestamp field | | `count` | Extract the number of items from a JSON array or relational field | -| `json` | Extract a specific value from a JSON field using path notation | diff --git a/content/guides/04.connect/2.filter-rules.md b/content/guides/04.connect/2.filter-rules.md index f94b5ba8c..3c41877ba 100644 --- a/content/guides/04.connect/2.filter-rules.md +++ b/content/guides/04.connect/2.filter-rules.md @@ -35,6 +35,7 @@ Filters are used in permissions, validations, and automations, as well as throug | `_nbetween` | Is not between two values (inclusive) | | `_empty` | Is empty (`null` or falsy) | | `_nempty` | Isn't empty (`null` or falsy) | +| `_json` [5] | Compare values inside a JSON document | | `_intersects` [2] | Intersects a point | | `_nintersects` [2] | Doesn't intersect a point | | `_intersects_bbox` [2] | Intersects a bounding box | @@ -46,7 +47,8 @@ Filters are used in permissions, validations, and automations, as well as throug [1] Compared value is not strictly typed for numeric values, allowing comparisons between numbers and their string representations.
[2] Only available on geometry fields.
[3] Only available in validation permissions.
-[4] Only available on One to Many relationship fields. +[4] Only available on One to Many relationship fields.
+[5] Only available on JSON fields. See the `_json` section below. ## Filter Syntax @@ -300,6 +302,8 @@ You can group multiple rules using the `_and` or `_or` logical operators. Each l ``` :: +:partial{content="json-filter"} + ## Follow Syntax Filters allow you to query relations from collections directly. From 4b888b9c8c6550eea752d4b488cbcb56d3008471 Mon Sep 17 00:00:00 2001 From: Brainslug Date: Mon, 20 Apr 2026 18:43:20 +0200 Subject: [PATCH 02/18] set up a separate page for json queries --- content/_partials/json-filter.md | 270 ------- content/_partials/json-function.md | 428 +---------- content/guides/04.connect/2.filter-rules.md | 41 +- .../guides/04.connect/3.query-parameters.md | 18 +- content/guides/04.connect/7.json-queries.md | 691 ++++++++++++++++++ 5 files changed, 767 insertions(+), 681 deletions(-) delete mode 100644 content/_partials/json-filter.md create mode 100644 content/guides/04.connect/7.json-queries.md diff --git a/content/_partials/json-filter.md b/content/_partials/json-filter.md deleted file mode 100644 index 300aabe6f..000000000 --- a/content/_partials/json-filter.md +++ /dev/null @@ -1,270 +0,0 @@ -## The `_json` Filter Operator - -The `_json` operator filters items by values inside a JSON field. It accepts an object mapping JSON paths to standard filter operators, letting you compare specific keys or array elements without loading the full document. - -`_json` is only valid on `json`-typed fields. - -### Syntax - -``` -?filter={"field":{"_json":{"path":{"_operator":value}}}} -``` - -**Examples:** - -``` -?filter={"metadata":{"_json":{"color":{"_eq":"blue"}}}} -?filter={"metadata":{"_json":{"price":{"_gte":100}}}} -?filter={"metadata":{"_json":{"tags[0]":{"_null":true}}}} -?filter={"metadata":{"_json":{"settings.theme":{"_contains":"dark"}}}} -``` - -Path keys inside `_json` use the same dot-and-bracket notation as the [`json(field, path)` function](/guides/connect/query-parameters). - -### Supported Inner Operators - -| Category | Operators | -| --------------- | --------------------------------------------------------- | -| Equality | `_eq`, `_neq`, `_ieq`, `_nieq` | -| Null | `_null`, `_nnull` | -| Set | `_in`, `_nin` | -| String | `_contains`, `_ncontains`, `_icontains`, `_nicontains` | -| Prefix / Suffix | `_starts_with`, `_ends_with` (plus `_i` variants) | -| Numeric | `_gt`, `_gte`, `_lt`, `_lte`, `_between`, `_nbetween` | -| Empty | `_empty`, `_nempty` | - -### Relational JSON Filtering - -`_json` nests under relational keys the same way other filters do. To filter on a JSON field belonging to a related item, nest `_json` under the relation name: - -```json -{ - "category_id": { - "metadata": { - "_json": { - "color": { "_eq": "blue" } - } - } - } -} -``` - -This matches articles whose related category has `metadata.color = "blue"`. - -### Combining Multiple Conditions - -Combine multiple `_json` filters at the top level with `_and` or `_or`: - -```json -{ - "_and": [ - { "metadata": { "_json": { "color": { "_eq": "blue" } } } }, - { "metadata": { "_json": { "size": { "_gt": 10 } } } } - ] -} -``` - -You can also group conditions inside a single `_json` value using `_and` or `_or`: - -```json -{ - "metadata": { - "_json": { - "_and": [ - { "color": { "_eq": "blue" } }, - { "size": { "_gt": 10 } } - ] - } - } -} -``` - -### Dynamic Variables - -Dynamic filter variables like `$CURRENT_USER` and `$NOW` work inside `_json` inner values. They are resolved before the filter runs, so they apply in permission rules and regular queries. - -### Database-Specific Behavior - -**PostgreSQL numeric comparisons** - -PostgreSQL extracts JSON scalars as `text`. Directus automatically casts to a numeric type when the filter value is a number or an array of numbers, so `_gt`, `_lt`, `_between`, and related operators work correctly in those cases. If you supply a numeric comparison with a string value (for example `{"version":{"_gt":"9"}}`), the comparison remains lexicographic. Use a numeric literal to get numeric comparison. - -**SQLite** - -SQLite may return `0` or `1` instead of boolean values when the path resolves to a boolean. - -**MSSQL / Oracle** - -Both dialects always return scalar values as strings regardless of the original JSON type. Apply any needed coercion in your application. - -### GraphQL - -Each `json`-typed field in a filter input type accepts `_json`, `_null`, and `_nnull`. The `_json` value is an object mapping JSON path strings to standard filter operators. - -#### Simple Equality Filter - -Path keys that are valid GraphQL identifiers (letters, digits, and `_`, with no dots or brackets) can be written inline: - -```graphql -{ - articles(filter: { metadata: { _json: { color: { _eq: "blue" } } } }) { - id - title - } -} -``` - -#### Filter With Operators - -```graphql -{ - articles( - filter: { - metadata: { - _json: { - color: { _in: ["red", "blue"] } - brand: { _nnull: true } - description: { _contains: "premium" } - } - } - } - ) { - id - title - } -} -``` - -#### Paths With Dots or Brackets - -GraphQL input-object keys must be valid identifiers, so paths containing dots, brackets, or starting with `[` cannot be written inline. This includes: - -- `settings.theme` (dot-separated nested path) -- `tags[0]` (bracket index notation) -- `[0].test` (path starting with an array index) - -Pass the `_json` value as a typed variable instead: - -```graphql -query FilterByNestedPath($jsonFilter: JSON) { - articles(filter: { metadata: { _json: $jsonFilter } }) { - id - title - } -} -``` - -Variables: - -```json -{ - "jsonFilter": { - "settings.theme": { "_eq": "dark" }, - "tags[0]": { "_eq": "electronics" }, - "[0].test": { "_null": false } - } -} -``` - -#### Combining `_json` With `_and` / `_or` - -```graphql -{ - articles( - filter: { - _and: [ - { metadata: { _json: { color: { _eq: "blue" } } } } - { metadata: { _json: { level: { _gte: 3 } } } } - ] - } - ) { - id - title - } -} -``` - -#### Relational JSON Filter - -```graphql -{ - articles(filter: { category_id: { metadata: { _json: { color: { _eq: "blue" } } } } }) { - id - title - category_id { - name - } - } -} -``` - -### TypeScript SDK - -The `_json` operator is available on any field in the `filter` object. The SDK types it as `Record>`. The server enforces at runtime that `_json` is only valid on `json`-typed fields. - -#### Simple Equality - -```typescript -const items = await client.request( - readItems('articles', { - filter: { - metadata: { - _json: { color: { _eq: 'blue' } }, - }, - }, - }), -); -``` - -#### Multiple Path Conditions in One `_json` - -```typescript -const items = await client.request( - readItems('articles', { - filter: { - metadata: { - _json: { - color: { _eq: 'red' }, - brand: { _in: ['BrandX', 'BrandY'] }, - level: { _gte: 3 }, - }, - }, - }, - }), -); -``` - -#### Dot-Notation and Bracket Paths - -Path keys with dots or brackets are plain strings. No special SDK handling is needed: - -```typescript -const items = await client.request( - readItems('articles', { - filter: { - metadata: { - _json: { - 'settings.theme': { _eq: 'dark' }, - 'tags[0]': { _eq: 'electronics' }, - }, - }, - }, - }), -); -``` - -#### Relational `_json` Filter - -```typescript -const items = await client.request( - readItems('articles', { - filter: { - category_id: { - metadata: { - _json: { color: { _eq: 'blue' } }, - }, - }, - }, - }), -); -``` diff --git a/content/_partials/json-function.md b/content/_partials/json-function.md index c2d999724..ab5d5939c 100644 --- a/content/_partials/json-function.md +++ b/content/_partials/json-function.md @@ -1,420 +1,38 @@ -## The `json(field, path)` Function +### JSON Function -The `json(field, path)` function extracts the value from the specified path in a JSON field and returns it as a separate field in the query response. It is used in the `fields` query parameter alongside regular field names and other field functions. +Use the `json(field, path)` function to extract a specific value from a JSON field and return it as a separate field in the query response. Paths use dot notation for object keys and bracket notation for array indices. +::code-group -::callout{icon="material-symbols:warning-rounded" color="warning"} - -**Supported Parameters** - -The `json(field, path)` function is not supported in the `filter`. For filtering JSON fields, use the [`_json` filter operator](/guides/connect/filter-rules) instead. - -:: - -### Syntax - -``` -json(field, path) -``` - -- **`field`** — the name of a JSON column in the collection (or a relational path to one, see [Relational Queries](#relational-queries)). -- **`path`** — a dot-and-bracket notation path to the value you want to extract from within the JSON document. - -Both arguments are required and separated by a comma. - -### Path Notation - -Paths use dot notation for object keys and bracket notation for array indices. - -| Pattern | Example | Meaning | -|---|---|---| -| `key` | `color` | Top-level key | -| `a.b.c` | `settings.theme.color` | Nested keys | -| `[n]` | `tags[0]` | Array element at index `n` | -| `a[n].b` | `items[0].name` | Mixed object/array access | - -**Examples:** - -``` -json(metadata, color) → top-level key -json(metadata, settings.theme) → nested object -json(data, items[0].name) → array element property -json(data, [0]) → first element of a top-level array -``` - -### Response Format - -Extracted values are returned as additional fields on each item using auto-generated aliases. The alias follows the pattern: - -``` -{field}_{path}_json -``` - -Special characters in the path (`[`, `]`, `.`) are replaced with underscores. For example: - -| Request field | Response key | -|---|---| -| `json(metadata, color)` | `metadata_color_json` | -| `json(metadata, settings.priority)` | `metadata_settings_priority_json` | -| `json(data, items[0].name)` | `data_items_0_name_json` | - -#### Example Request and Response - -```http -GET /items/articles?fields=id,title,json(metadata, color)&sort=title -``` - -```json -{ - "data": [ - { - "id": 1, - "title": "An Article", - "metadata_color_json": "blue" - } - ] -} -``` - -### Relational Queries - -`json(field, path)` can traverse relational fields to extract JSON values from related items. The relational path goes inside the first argument, before the JSON field name. - -#### Many-to-One (M2O) - -``` -json(relation.json_field, path) -``` - -The extracted value is returned nested under the relational key in the response, alongside any other requested fields from that relation. - -```http -GET /items/articles?fields=id,title,category_id.name,json(category_id.metadata, color) -``` - -```json -{ - "data": [ - { - "id": 1, - "title": "An Article", - "category_id": { - "name": "Tech", - "metadata_color_json": "blue" - } - } - ] -} -``` - -Multiple `json(field, path)` extractions from the same relation are grouped under the same relational key: - -```http -GET /items/articles?fields=id,json(category_id.metadata, color),json(category_id.metadata, icon) -``` - -```json -{ - "data": [ - { - "category_id": { - "metadata_color_json": "blue", - "metadata_icon_json": "laptop" - } - } - ] -} -``` - -#### One-to-Many (O2M) - -For O2M relations, each related item returns its own extracted value. The response is an array of objects, each containing the extracted key. - -```http -GET /items/articles/1?fields=id,json(comments.data, type) -``` - -```json -{ - "data": { - "id": 1, - "comments": [ - { "data_type_json": "review" }, - { "data_type_json": "feedback" }, - { "data_type_json": "question" } - ] - } -} -``` - -#### Many-to-Any (M2A) - -For M2A relations, use the standard Directus collection scope syntax inside the first argument: - -``` -json(relation.item:collection_name.json_field, path) -``` - -```http -GET /items/shapes/1?fields=id,json(children.item:circles.metadata, color) -``` - -### Relational Depth Limit - -`json(field, path)` will enforce a maximum relational depth (`MAX_RELATIONAL_DEPTH`, default `10`) limit for the `field` argument. This depth is calculated irrespective of the Path depth limit mentioned below - -``` -json(category_id.metadata, a.b.c.d.e) -``` -This has a relational depth of **2** (`category_id` + `metadata`), regardless of how many segments are in the JSON path `a.b.c.d.e`. - -Exceeding the relational depth will return an error. - -### JSON Path Depth Limit - -In addition to a relation depth, `json(field, path)` will also enforce a path depth limit (`MAX_JSON_QUERY_DEPTH`, default `10`). This depth is calculated irrespective of the relational depth. - -``` -json(category_id.metadata, a[0].c.d.e.f.g.h.i.j) +```http [REST] +GET /items/articles?fields=id,title,json(metadata, color) ``` -The above example has a path depth of 10 and is allowed by default; adding one more segment exceeds the limit. - -Exceeding the path depth limit returns an error. - -### Unsupported Path Expressions - -The following path syntaxes are **not supported** and will return an error: - -| Expression | Example | -|---|---| -| Empty brackets (wildcard) | `items[]` | -| `[*]` wildcard | `items[*].name` | -| `*` glob | `items.*` | -| JSONPath predicates | `items[?(@.price > 10)]` | -| `@` current node | `@.name` | -| `$` root | `$.name` | - -### Object Keys with Special Characters - -The `json(field, path)` path syntax uses `.` as a separator between key segments. There is no escape mechanism for object keys that themselves contain dots, spaces, or other special characters. For example, if your JSON has a key `"first.name"`, there is no way to express that in the path — `json(data, first.name)` would be interpreted as nested access to key `first`, then key `name`. - -Similarly, because MySQL and MariaDB path conversion uses dot-notation (`$.key.subkey`), keys containing characters that are special in that context (e.g., spaces) may not be reachable. PostgreSQL's parameterized `->?` approach is more permissive for unusual key names, but the input path format still does not provide an escaping mechanism. - -### Database-Specific Exceptions - -**SQLite** - -- SQLite can return `0`/`1` isntead of `boolean` values. - -**MSSQL** - -- Will always returns scalar values as **strings (`NVARCHAR`)**, even when the original JSON value is a number or boolean. For example, a JSON integer `42` will be returned as the string `"42"`. Your application should perform type coercion as needed. - -**Oracle** - -- Similar to MSSQL will also return scalar values as **strings**, regardless of the original JSON type (number, boolean, etc.). A JSON number `3.14` will be returned as `"3.14"`. - -### GraphQL - -Each `json`-typed field exposes a `json(path: String!)` sub-field inside `{fieldName}_func`. The `path` argument is required. The return type is `JSON` (any scalar, object, or array). - -`{fieldName}_func` already exists for the `count` sub-field. `json` sits alongside it in the same selection. - -#### Simple Scalar Extraction - -```graphql -{ - articles { - id - title - metadata_func { - json(path: "color") - } - } -} -``` - -```json -{ - "data": { - "articles": [ - { "id": 1, "title": "An Article", "metadata_func": { "json": "blue" } } - ] - } -} -``` - -#### Multiple Paths From the Same Field - -Request multiple paths inside a single `{fieldName}_func` selection by using **field aliases** on the `json` sub-field: - -```graphql -{ - articles { - id - metadata_func { - color: json(path: "color") - theme: json(path: "settings.theme") - firstTag: json(path: "tags[0]") - } - } -} -``` - -```json -{ - "data": { - "articles": [ - { - "id": 1, - "metadata_func": { - "color": "blue", - "theme": "dark", - "firstTag": "electronics" - } - } - ] - } -} -``` - -#### Extracting an Object or Array - -When the path points to an object or array rather than a scalar, the full value is returned as parsed JSON: - -```graphql -{ - articles { - id - metadata_func { - dimensions: json(path: "dimensions") - tags: json(path: "tags") - } - } -} -``` - -```json -{ - "data": { - "articles": [ - { - "id": 1, - "metadata_func": { - "dimensions": { "width": 10, "height": 20, "depth": 5 }, - "tags": ["electronics", "premium", "new"] - } - } - ] - } -} -``` - -#### Relational JSON Extraction - -For a Many-to-One relation, request the `json` function on the related collection's field: - -```graphql -{ - articles { - id - category_id { - name - metadata_func { - color: json(path: "color") - } - } - } -} -``` - -### TypeScript SDK - -The SDK accepts `json(fieldName, path)` strings in the `fields` array. The first argument is constrained by TypeScript to fields typed as `json` in your schema. An invalid field name produces a compile-time error. The path (second argument) is a plain `string` and is not validated at compile time. - -The SDK also computes the response alias type automatically from the literal string, so extracted values are fully typed with no manual type extensions needed. The alias rule is `{field}_{path}_json` with `.`, `[`, and `]` replaced by `_`. - -```typescript -import { createDirectus, readItems, rest } from '@directus/sdk'; - -interface Article { - id: number; - title: string; - metadata: 'json' | null; // type literal 'json' tells the SDK this is a json field +```graphql [GraphQL] +query { + articles { + id + title + metadata_func { + json(path: "color") + } + } } - -interface Schema { - articles: Article[]; -} - -const client = createDirectus('https://directus.example.com').with(rest()); - -const items = await client.request( - readItems('articles', { - fields: ['id', 'title', 'json(metadata, color)'], - }), -); - -// metadata_color_json is automatically typed as JsonValue | null — no cast needed -const color = items[0].metadata_color_json; ``` -#### Multiple Paths +```js [SDK] +import { createDirectus, rest, readItems } from '@directus/sdk'; +const directus = createDirectus('https://directus.example.com').with(rest()); -```typescript -const items = await client.request( - readItems('articles', { - fields: ['id', 'title', 'json(metadata, color)', 'json(metadata, settings.theme)', 'json(metadata, tags[0])'], - }), +const result = await directus.request( + readItems('articles', { + fields: ['id', 'title', 'json(metadata, color)'], + }) ); - -// All aliases are typed directly on items: -// items[0].metadata_color_json // JsonValue | null -// items[0].metadata_settings_theme_json // JsonValue | null -// items[0].metadata_tags_0_json // JsonValue | null -``` - -#### Via a Relational Field - -Pass `json(field, path)` inside a relational field object. The extracted alias appears typed on the related item. - -```typescript -const items = await client.request( - readItems('articles', { - fields: ['id', 'title', { category_id: ['name', 'json(metadata, color)'] }], - }), -); - -const color = items[0].category_id.metadata_color_json; ``` -#### Compile-Time Safety - -The SDK enforces that the first argument must be a `json`-typed field, and that the output alias is typed. Non-json fields produce a TypeScript error: - -```typescript -// valid: metadata is a json field; metadata_color_json is typed as JsonValue | null -readItems('articles', { fields: ['json(metadata, color)'] }); - -// compile error: title is a string field, not json -readItems('articles', { fields: ['json(title, color)'] }); -``` - -::callout{icon="material-symbols:info-outline"} -**Alias Typing Requires Literal Field Arrays** - -Alias typing only works when the `fields` array is an inline literal or typed `as const`. If the array is built dynamically at runtime, TypeScript widens it to `string[]` and the aliases are not present in the inferred return type. :: -#### IDE Autocomplete - -When typing inside the `fields` array, the SDK provides partial autocomplete for the `json()` function. For each `json`-typed field in your schema, the IDE offers `json(fieldName, ` as a completion, positioning the cursor ready for the path argument: - -```typescript -// Typing 'json(' in the fields array surfaces: -// json(metadata, ← cursor positioned here, type your path then close with ) -readItems('articles', { fields: ['json(metadata, color)'] }); -``` +See [JSON Queries](/guides/connect/json-queries) for path syntax, relational traversal, depth limits, and full GraphQL and TypeScript SDK details. -This works via TypeScript's template-literal completion (TypeScript >= 4.7). Only `json`-typed fields appear as suggestions. The path argument is a free string and has no completion hints. +The `json(field, path)` function is not supported in `filter`. For filtering JSON fields, use the [`_json` filter operator](/guides/connect/filter-rules) instead. diff --git a/content/guides/04.connect/2.filter-rules.md b/content/guides/04.connect/2.filter-rules.md index 3c41877ba..877ee3f3d 100644 --- a/content/guides/04.connect/2.filter-rules.md +++ b/content/guides/04.connect/2.filter-rules.md @@ -78,6 +78,45 @@ This filter checks the `title` field contains the case-sensitive substring 'Dire ``` :: +## The `_json` Filter Operator + +Use the `_json` operator to filter items by values inside a JSON field. It accepts an object mapping JSON paths to standard filter operators, letting you compare specific keys or array elements without loading the full document. `_json` is only valid on `json`-typed fields. + +::code-group + +```http [REST] +GET /items/articles + ?filter={"metadata":{"_json":{"color":{"_eq":"blue"}}}} +``` + +```graphql [GraphQL] +query { + articles(filter: { metadata: { _json: { color: { _eq: "blue" } } } }) { + id + title + } +} +``` + +```js [SDK] +import { createDirectus, rest, readItems } from '@directus/sdk'; +const directus = createDirectus('https://directus.example.com').with(rest()); + +const result = await directus.request( + readItems('articles', { + filter: { + metadata: { + _json: { color: { _eq: 'blue' } }, + }, + }, + }) +); +``` + +:: + +See [JSON Queries](/guides/connect/json-queries#the-_json-filter-operator) for supported operators, relational filtering, dotted and bracket path handling in GraphQL, and full details. + ## Relational Fields You can filter items based on related data by nesting field names in your query. This allows you to query items not just by their own fields, but also by values in related collections. @@ -302,8 +341,6 @@ You can group multiple rules using the `_and` or `_or` logical operators. Each l ``` :: -:partial{content="json-filter"} - ## Follow Syntax Filters allow you to query relations from collections directly. diff --git a/content/guides/04.connect/3.query-parameters.md b/content/guides/04.connect/3.query-parameters.md index bc18b5462..602e874d2 100644 --- a/content/guides/04.connect/3.query-parameters.md +++ b/content/guides/04.connect/3.query-parameters.md @@ -537,8 +537,11 @@ Saves the API response to a file. Valid values are `csv`, `json`, `xml`, `yaml`. Queries a version of a record by version key when [content versioning](/guides/content/content-versioning) is enabled on a collection. Applies only to single item retrieval. -```http [GET /items/posts/1] -?version=v1 +::code-group + +```http [REST] +GET /items/posts/1 + ?version=v1 ``` ```graphql [GraphQL] @@ -560,12 +563,17 @@ const result = await directus.request( ); ``` +:: + ## VersionRaw Specifies to return relational delta changes as a [detailed output](https://directus.io/docs/guides/connect/relations#creating-updating-deleting) on a version record. -```http [GET /items/posts/1] -?version=v1&versionRaw=true +::code-group + +```http [REST] +GET /items/posts/1 + ?version=v1&versionRaw=true ``` ```graphql [GraphQL] @@ -588,6 +596,8 @@ const result = await directus.request( ); ``` +:: + ## Functions :partial{content="query-functions"} diff --git a/content/guides/04.connect/7.json-queries.md b/content/guides/04.connect/7.json-queries.md new file mode 100644 index 000000000..bf256445d --- /dev/null +++ b/content/guides/04.connect/7.json-queries.md @@ -0,0 +1,691 @@ +--- +title: JSON Queries +description: Extract and filter values in JSON fields using the json(field, path) function and _json filter operator, with path notation, relational support, GraphQL, and TypeScript SDK. +--- + +Directus provides two complementary tools for working with JSON fields in queries: + +- The **`json(field, path)` function** extracts a specific value from a JSON document. It can be used in the `fields`, `sort`, and `alias` query parameters. +- The **`_json` filter operator** filters items by values inside a JSON document without loading the full document. Use it in the `filter` query parameter. + +Both use the same path notation and work across REST, GraphQL, and the TypeScript SDK. + +## Path Notation + +Paths use dot notation for object keys and bracket notation for array indices. + +| Pattern | Example | Meaning | +|---|---|---| +| `key` | `color` | Top-level key | +| `a.b.c` | `settings.theme.color` | Nested keys | +| `[n]` | `tags[0]` | Array element at index `n` | +| `a[n].b` | `items[0].name` | Mixed object/array access | + +**Examples:** + +``` +json(metadata, color) → top-level key +json(metadata, settings.theme) → nested object +json(data, items[0].name) → array element property +json(data, [0]) → first element of a top-level array +``` + +The following path syntaxes are **not supported** and return an error in both the function and filter operator: + +| Expression | Example | +|---|---| +| Empty brackets (wildcard) | `items[]` | +| `[*]` wildcard | `items[*].name` | +| `*` glob | `items.*` | +| JSONPath predicates | `items[?(@.price > 10)]` | +| `@` current node | `@.name` | +| `$` root | `$.name` | + +### Object Keys with Special Characters + +The path syntax uses `.` as a separator between key segments and has no escape mechanism. Object keys that contain dots, spaces, or other special characters cannot be reached. For example, a key `"first.name"` is interpreted as nested access to key `first`, then key `name`. + +## The `json(field, path)` Function + +The `json(field, path)` function extracts the value from the specified path in a JSON field. It can be used anywhere a field reference is accepted, including the `fields`, `sort`, and `alias` query parameters. + +::callout{icon="material-symbols:warning-rounded" color="warning"} + +**Not Supported in Filters** +The `json(field, path)` function is not supported in the `filter` query parameter. For filtering JSON fields, use the [`_json` filter operator](#the-_json-filter-operator) instead. + +:: + +### Syntax + +``` +json(field, path) +``` + +- **`field`** is the name of a JSON column in the collection (or a relational path to one). +- **`path`** is a dot-and-bracket notation path to the value you want to extract from within the JSON document. + +Both arguments are required and separated by a comma. + +In GraphQL, each `json`-typed field exposes a `json(path: String!)` sub-field inside `{fieldName}_func`. The `path` argument is required. The return type is `JSON` (any scalar, object, or array). `{fieldName}_func` already exists for the `count` sub-field; `json` sits alongside it in the same selection. + +The TypeScript SDK accepts `json(fieldName, path)` strings in the `fields` array. The first argument is constrained by TypeScript to fields typed as `json` in your schema. An invalid field name produces a type error. The SDK computes the response alias type automatically from the literal string, so extracted values are fully typed. + +### Response Format + +For REST and the SDK, extracted values are returned as additional fields on each item using auto-generated aliases. The alias follows the pattern: + +``` +{field}_{path}_json +``` + +Special characters in the path (`[`, `]`, `.`) are replaced with underscores. + +| Request field | Response key | +|---|---| +| `json(metadata, color)` | `metadata_color_json` | +| `json(metadata, settings.priority)` | `metadata_settings_priority_json` | +| `json(data, items[0].name)` | `data_items_0_name_json` | + +In GraphQL, the extracted value is returned inside `{fieldName}_func.json`. When requesting multiple paths from the same field, use GraphQL field aliases to distinguish them. + +### Basic Example + +::code-group + +```http [REST] +GET /items/articles?fields=id,title,json(metadata, color) +``` + +```graphql [GraphQL] +query { + articles { + id + title + metadata_func { + json(path: "color") + } + } +} +``` + +```js [SDK] +import { createDirectus, rest, readItems } from '@directus/sdk'; +const directus = createDirectus('https://directus.example.com').with(rest()); + +const result = await directus.request( + readItems('articles', { + fields: ['id', 'title', 'json(metadata, color)'], + }) +); +``` + +:: + +Response: + +::code-group + +```json [REST / SDK] +{ + "data": [ + { + "id": 1, + "title": "An Article", + "metadata_color_json": "blue" + } + ] +} +``` + +```json [GraphQL] +{ + "data": { + "articles": [ + { + "id": 1, + "title": "An Article", + "metadata_func": { "json": "blue" } + } + ] + } +} +``` + +:: + +### Multiple Paths + +Extract multiple values from the same JSON field in a single request. In GraphQL, use field aliases on the `json` sub-field to distinguish each extraction. + +::code-group + +```http [REST] +GET /items/articles?fields=id,json(metadata, color),json(metadata, settings.theme),json(metadata, tags[0]) +``` + +```graphql [GraphQL] +query { + articles { + id + metadata_func { + color: json(path: "color") + theme: json(path: "settings.theme") + firstTag: json(path: "tags[0]") + } + } +} +``` + +```js [SDK] +import { createDirectus, rest, readItems } from '@directus/sdk'; +const directus = createDirectus('https://directus.example.com').with(rest()); + +const result = await directus.request( + readItems('articles', { + fields: [ + 'id', + 'json(metadata, color)', + 'json(metadata, settings.theme)', + 'json(metadata, tags[0])', + ], + }) +); +``` + +:: + +### Extracting an Object or Array + +When the path points to an object or array rather than a scalar, the full value is returned as parsed JSON. + +::callout{icon="material-symbols:warning-rounded" color="warning"} + +**Non-Scalar Paths in Sort and Filter** +Sorting or filtering by a path that resolves to an object or array can produce unexpected results. The database compares the serialized form, which depends on dialect-specific JSON ordering and formatting. Use paths that resolve to a scalar value (string, number, boolean) for reliable sorting and filtering. + +:: + +::code-group + +```http [REST] +GET /items/articles?fields=id,json(metadata, dimensions),json(metadata, tags) +``` + +```graphql [GraphQL] +query { + articles { + id + metadata_func { + dimensions: json(path: "dimensions") + tags: json(path: "tags") + } + } +} +``` + +```js [SDK] +import { createDirectus, rest, readItems } from '@directus/sdk'; +const directus = createDirectus('https://directus.example.com').with(rest()); + +const result = await directus.request( + readItems('articles', { + fields: ['id', 'json(metadata, dimensions)', 'json(metadata, tags)'], + }) +); +``` + +:: + +### Relational Queries + +`json(field, path)` can traverse relational fields to extract JSON values from related items. The relational path goes inside the first argument, before the JSON field name. + +#### Many-to-One (M2O) + +Syntax: `json(relation.json_field, path)` + +The extracted value is returned nested under the relational key in the response, alongside any other requested fields from that relation. Multiple `json(field, path)` extractions from the same relation are grouped under the same relational key. + +::code-group + +```http [REST] +GET /items/articles?fields=id,title,category_id.name,json(category_id.metadata, color) +``` + +```graphql [GraphQL] +query { + articles { + id + title + category_id { + name + metadata_func { + color: json(path: "color") + } + } + } +} +``` + +```js [SDK] +import { createDirectus, rest, readItems } from '@directus/sdk'; +const directus = createDirectus('https://directus.example.com').with(rest()); + +const result = await directus.request( + readItems('articles', { + fields: ['id', 'title', { category_id: ['name', 'json(metadata, color)'] }], + }) +); +``` + +:: + +#### One-to-Many (O2M) + +For O2M relations, each related item returns its own extracted value. The response contains an array of objects, each with the extracted key. + +::code-group + +```http [REST] +GET /items/articles/1?fields=id,json(comments.data, type) +``` + +```graphql [GraphQL] +query { + articles_by_id(id: 1) { + id + comments { + data_func { + json(path: "type") + } + } + } +} +``` + +```js [SDK] +import { createDirectus, rest, readItem } from '@directus/sdk'; +const directus = createDirectus('https://directus.example.com').with(rest()); + +const result = await directus.request( + readItem('articles', 1, { + fields: ['id', { comments: ['json(data, type)'] }], + }) +); +``` + +:: + +#### Many-to-Any (M2A) + +For M2A relations, use the standard Directus collection scope syntax inside the first argument: `json(relation.item:collection_name.json_field, path)` + +::code-group + +```http [REST] +GET /items/shapes/1?fields=id,json(children.item:circles.metadata, color) +``` + +```graphql [GraphQL] +query { + shapes_by_id(id: 1) { + id + children { + item { + ... on circles { + metadata_func { + json(path: "color") + } + } + } + } + } +} +``` + +```js [SDK] +import { createDirectus, rest, readItem } from '@directus/sdk'; +const directus = createDirectus('https://directus.example.com').with(rest()); + +const result = await directus.request( + readItem('shapes', 1, { + fields: [ + 'id', + { + children: [ + { + item: { + circles: ['json(metadata, color)'], + }, + }, + ], + }, + ], + }) +); +``` + +:: + +### Depth Limits + +`json(field, path)` enforces two separate depth limits, calculated independently: + +- **Relational depth** (`MAX_RELATIONAL_DEPTH`, default `10`) limits how deep the relational path in the first argument can go. `json(category_id.metadata, a.b.c.d.e)` has a relational depth of 2 (`category_id` + `metadata`), regardless of the JSON path length. +- **Path depth** (`MAX_JSON_QUERY_DEPTH`, default `10`) limits how many segments the JSON path itself can contain. `json(category_id.metadata, a[0].c.d.e.f.g.h.i.j)` has a path depth of 10 and is allowed by default; one more segment exceeds the limit. + +Exceeding either limit returns an error. + +### TypeScript SDK Type Safety + +The SDK enforces that the first argument of `json()` must be a `json`-typed field. Non-json fields produce a TypeScript error. The output alias is typed automatically as `JsonValue | null` with no cast needed. + +```typescript +import { createDirectus, readItems, rest } from '@directus/sdk'; + +interface Article { + id: number; + title: string; + metadata: 'json' | null; // type literal 'json' tells the SDK this is a json field +} + +interface Schema { + articles: Article[]; +} + +const client = createDirectus('https://directus.example.com').with(rest()); + +// valid: metadata is a json field; metadata_color_json is typed as JsonValue | null +readItems('articles', { fields: ['json(metadata, color)'] }); + +// type error: title is a string field, not json +readItems('articles', { fields: ['json(title, color)'] }); +``` + +The alias rule is `{field}_{path}_json` with `.`, `[`, and `]` replaced by `_`. For a relational field, the extracted alias appears typed on the related item (for example, `items[0].category_id.metadata_color_json`). + +::callout{icon="material-symbols:info-outline"} +**Alias Typing Requires Literal Field Arrays** + +Alias typing only works when the `fields` array is an inline literal or typed `as const`. If the array is built dynamically at runtime, TypeScript widens it to `string[]` and the aliases are not present in the inferred return type. +:: + +When typing inside the `fields` array, the SDK provides partial autocomplete for the `json()` function. For each `json`-typed field in your schema, the IDE offers `json(fieldName, ` as a completion, positioning the cursor ready for the path argument. This works via TypeScript's template-literal completion (TypeScript >= 4.7). Only `json`-typed fields appear as suggestions; the path argument is a free string with no completion hints. + +## The `_json` Filter Operator + +The `_json` operator filters items by values inside a JSON field. It accepts an object mapping JSON paths to standard filter operators, letting you compare specific keys or array elements without loading the full document. + +`_json` is only valid on `json`-typed fields. + +### Syntax + +The `_json` value is an object where each key is a JSON path and each value is a standard filter operator object. + +``` +{ "field": { "_json": { "path": { "_operator": value } } } } +``` + +In GraphQL, input-object keys must be valid identifiers, so paths containing dots, brackets, or starting with `[` must be passed as a typed variable (see [Paths with Dots or Brackets](#paths-with-dots-or-brackets)). + +### Supported Inner Operators + +| Category | Operators | +| --------------- | --------------------------------------------------------- | +| Equality | `_eq`, `_neq`, `_ieq`, `_nieq` | +| Null | `_null`, `_nnull` | +| Set | `_in`, `_nin` | +| String | `_contains`, `_ncontains`, `_icontains`, `_nicontains` | +| Prefix / Suffix | `_starts_with`, `_ends_with` (plus `_i` variants) | +| Numeric | `_gt`, `_gte`, `_lt`, `_lte`, `_between`, `_nbetween` | +| Empty | `_empty`, `_nempty` | + +### Basic Example + +Filter articles where the `color` key inside the `metadata` JSON field equals `"blue"`. + +::code-group + +```http [REST] +GET /items/articles + ?filter={"metadata":{"_json":{"color":{"_eq":"blue"}}}} +``` + +```graphql [GraphQL] +query { + articles(filter: { metadata: { _json: { color: { _eq: "blue" } } } }) { + id + title + } +} +``` + +```js [SDK] +import { createDirectus, rest, readItems } from '@directus/sdk'; +const directus = createDirectus('https://directus.example.com').with(rest()); + +const result = await directus.request( + readItems('articles', { + filter: { + metadata: { + _json: { color: { _eq: 'blue' } }, + }, + }, + }) +); +``` + +:: + +### Multiple Path Conditions + +Combine several path conditions inside a single `_json` object. + +::code-group + +```http [REST] +GET /items/articles + ?filter={"metadata":{"_json":{"color":{"_eq":"red"},"brand":{"_in":["BrandX","BrandY"]},"level":{"_gte":3}}}} +``` + +```graphql [GraphQL] +query { + articles( + filter: { + metadata: { + _json: { + color: { _in: ["red", "blue"] } + brand: { _nnull: true } + description: { _contains: "premium" } + } + } + } + ) { + id + title + } +} +``` + +```js [SDK] +const result = await directus.request( + readItems('articles', { + filter: { + metadata: { + _json: { + color: { _eq: 'red' }, + brand: { _in: ['BrandX', 'BrandY'] }, + level: { _gte: 3 }, + }, + }, + }, + }) +); +``` + +:: + +### Paths with Dots or Brackets + +Path keys with dots (`settings.theme`), bracket indices (`tags[0]`), or paths starting with `[` are plain strings in REST and the SDK. In GraphQL, input-object keys must be valid identifiers, so pass the `_json` value as a typed variable instead. + +::code-group + +```http [REST] +GET /items/articles + ?filter={"metadata":{"_json":{"settings.theme":{"_eq":"dark"},"tags[0]":{"_eq":"electronics"}}}} +``` + +```graphql [GraphQL] +query FilterByNestedPath($jsonFilter: JSON) { + articles(filter: { metadata: { _json: $jsonFilter } }) { + id + title + } +} + +# Variables: +# { +# "jsonFilter": { +# "settings.theme": { "_eq": "dark" }, +# "tags[0]": { "_eq": "electronics" }, +# "[0].test": { "_null": false } +# } +# } +``` + +```js [SDK] +const result = await directus.request( + readItems('articles', { + filter: { + metadata: { + _json: { + 'settings.theme': { _eq: 'dark' }, + 'tags[0]': { _eq: 'electronics' }, + }, + }, + }, + }) +); +``` + +:: + +### Relational JSON Filtering + +`_json` nests under relational keys the same way other filters do. To filter on a JSON field belonging to a related item, nest `_json` under the relation name. + +::code-group + +```http [REST] +GET /items/articles + ?filter={"category_id":{"metadata":{"_json":{"color":{"_eq":"blue"}}}}} +``` + +```graphql [GraphQL] +query { + articles(filter: { category_id: { metadata: { _json: { color: { _eq: "blue" } } } } }) { + id + title + category_id { + name + } + } +} +``` + +```js [SDK] +const result = await directus.request( + readItems('articles', { + filter: { + category_id: { + metadata: { + _json: { color: { _eq: 'blue' } }, + }, + }, + }, + }) +); +``` + +:: + +### Combining Multiple Conditions + +Combine multiple `_json` filters at the top level with `_and` or `_or`. + +::code-group + +```http [REST] +GET /items/articles + ?filter={"_and":[{"metadata":{"_json":{"color":{"_eq":"blue"}}}},{"metadata":{"_json":{"size":{"_gt":10}}}}]} +``` + +```graphql [GraphQL] +query { + articles( + filter: { + _and: [ + { metadata: { _json: { color: { _eq: "blue" } } } } + { metadata: { _json: { level: { _gte: 3 } } } } + ] + } + ) { + id + title + } +} +``` + +```js [SDK] +const result = await directus.request( + readItems('articles', { + filter: { + _and: [ + { metadata: { _json: { color: { _eq: 'blue' } } } }, + { metadata: { _json: { size: { _gt: 10 } } } }, + ], + }, + }) +); +``` + +:: + +You can also group conditions inside a single `_json` value using `_and` or `_or`: + +```json +{ + "metadata": { + "_json": { + "_and": [ + { "color": { "_eq": "blue" } }, + { "size": { "_gt": 10 } } + ] + } + } +} +``` + +### Dynamic Variables + +Dynamic filter variables like `$CURRENT_USER` and `$NOW` work inside `_json` inner values. They are resolved before the filter runs, so they apply in permission rules and regular queries. + +## Database-Specific Notes + +**PostgreSQL** + +PostgreSQL extracts JSON scalars as `text`. For `_json` numeric comparisons, Directus automatically casts to a numeric type when the filter value is a number or an array of numbers, so `_gt`, `_lt`, `_between`, and related operators work correctly. If you supply a numeric comparison with a string value (for example `{"version":{"_gt":"9"}}`), the comparison remains lexicographic. Use a numeric literal to get numeric comparison. + +**SQLite** + +SQLite can return `0`/`1` instead of boolean values when the path resolves to a boolean. + +**MSSQL** + +Always returns scalar values as **strings (`NVARCHAR`)**, even when the original JSON value is a number or boolean. For example, a JSON integer `42` is returned as the string `"42"`. Your application should perform type coercion as needed. + +**Oracle** + +Similar to MSSQL, Oracle returns scalar values as **strings**, regardless of the original JSON type (number, boolean, etc.). A JSON number `3.14` is returned as `"3.14"`. From 23a60929cc22d731948d7117b393186c38e0174a Mon Sep 17 00:00:00 2001 From: Brainslug Date: Mon, 20 Apr 2026 22:42:15 +0200 Subject: [PATCH 03/18] add example responses --- content/guides/04.connect/7.json-queries.md | 233 ++++++++++++++++++++ 1 file changed, 233 insertions(+) diff --git a/content/guides/04.connect/7.json-queries.md b/content/guides/04.connect/7.json-queries.md index bf256445d..205969f8b 100644 --- a/content/guides/04.connect/7.json-queries.md +++ b/content/guides/04.connect/7.json-queries.md @@ -195,6 +195,42 @@ const result = await directus.request( :: +Response: + +::code-group + +```json [REST / SDK] +{ + "data": [ + { + "id": 1, + "metadata_color_json": "blue", + "metadata_settings_theme_json": "dark", + "metadata_tags_0_json": "featured" + } + ] +} +``` + +```json [GraphQL] +{ + "data": { + "articles": [ + { + "id": 1, + "metadata_func": { + "color": "blue", + "theme": "dark", + "firstTag": "featured" + } + } + ] + } +} +``` + +:: + ### Extracting an Object or Array When the path points to an object or array rather than a scalar, the full value is returned as parsed JSON. @@ -237,6 +273,40 @@ const result = await directus.request( :: +Response: + +::code-group + +```json [REST / SDK] +{ + "data": [ + { + "id": 1, + "metadata_dimensions_json": { "width": 100, "height": 50 }, + "metadata_tags_json": ["featured", "new"] + } + ] +} +``` + +```json [GraphQL] +{ + "data": { + "articles": [ + { + "id": 1, + "metadata_func": { + "dimensions": { "width": 100, "height": 50 }, + "tags": ["featured", "new"] + } + } + ] + } +} +``` + +:: + ### Relational Queries `json(field, path)` can traverse relational fields to extract JSON values from related items. The relational path goes inside the first argument, before the JSON field name. @@ -281,6 +351,44 @@ const result = await directus.request( :: +Response: + +::code-group + +```json [REST / SDK] +{ + "data": [ + { + "id": 1, + "title": "An Article", + "category_id": { + "name": "News", + "metadata_color_json": "blue" + } + } + ] +} +``` + +```json [GraphQL] +{ + "data": { + "articles": [ + { + "id": 1, + "title": "An Article", + "category_id": { + "name": "News", + "metadata_func": { "color": "blue" } + } + } + ] + } +} +``` + +:: + #### One-to-Many (O2M) For O2M relations, each related item returns its own extracted value. The response contains an array of objects, each with the extracted key. @@ -317,6 +425,38 @@ const result = await directus.request( :: +Response: + +::code-group + +```json [REST / SDK] +{ + "data": { + "id": 1, + "comments": [ + { "data_type_json": "comment" }, + { "data_type_json": "review" } + ] + } +} +``` + +```json [GraphQL] +{ + "data": { + "articles_by_id": { + "id": 1, + "comments": [ + { "data_func": { "json": "comment" } }, + { "data_func": { "json": "review" } } + ] + } + } +} +``` + +:: + #### Many-to-Any (M2A) For M2A relations, use the standard Directus collection scope syntax inside the first argument: `json(relation.item:collection_name.json_field, path)` @@ -368,6 +508,44 @@ const result = await directus.request( :: +Response: + +::code-group + +```json [REST / SDK] +{ + "data": { + "id": 1, + "children": [ + { + "item": { + "metadata_color_json": "red" + } + } + ] + } +} +``` + +```json [GraphQL] +{ + "data": { + "shapes_by_id": { + "id": 1, + "children": [ + { + "item": { + "metadata_func": { "color": "red" } + } + } + ] + } + } +} +``` + +:: + ### Depth Limits `json(field, path)` enforces two separate depth limits, calculated independently: @@ -478,6 +656,17 @@ const result = await directus.request( :: +Response: + +```json +{ + "data": [ + { "id": 1, "title": "An Article" }, + { "id": 4, "title": "Another Article" } + ] +} +``` + ### Multiple Path Conditions Combine several path conditions inside a single `_json` object. @@ -526,6 +715,16 @@ const result = await directus.request( :: +Response: + +```json +{ + "data": [ + { "id": 7, "title": "Premium Red Item" } + ] +} +``` + ### Paths with Dots or Brackets Path keys with dots (`settings.theme`), bracket indices (`tags[0]`), or paths starting with `[` are plain strings in REST and the SDK. In GraphQL, input-object keys must be valid identifiers, so pass the `_json` value as a typed variable instead. @@ -572,6 +771,16 @@ const result = await directus.request( :: +Response: + +```json +{ + "data": [ + { "id": 2, "title": "Dark Mode Electronics Review" } + ] +} +``` + ### Relational JSON Filtering `_json` nests under relational keys the same way other filters do. To filter on a JSON field belonging to a related item, nest `_json` under the relation name. @@ -611,6 +820,20 @@ const result = await directus.request( :: +Response: + +```json +{ + "data": [ + { + "id": 1, + "title": "An Article", + "category_id": { "name": "News" } + } + ] +} +``` + ### Combining Multiple Conditions Combine multiple `_json` filters at the top level with `_and` or `_or`. @@ -653,6 +876,16 @@ const result = await directus.request( :: +Response: + +```json +{ + "data": [ + { "id": 3, "title": "Large Blue Article" } + ] +} +``` + You can also group conditions inside a single `_json` value using `_and` or `_or`: ```json From 9d6295d865d3c264974c1f14f293a0f766d9a521 Mon Sep 17 00:00:00 2001 From: Brainslug Date: Tue, 21 Apr 2026 22:06:59 +0200 Subject: [PATCH 04/18] Make a json quickstart page instead --- content/guides/04.connect/7.json-queries.md | 850 +--------------- .../04.connect/8.json-queries-reference.md | 924 ++++++++++++++++++ 2 files changed, 976 insertions(+), 798 deletions(-) create mode 100644 content/guides/04.connect/8.json-queries-reference.md diff --git a/content/guides/04.connect/7.json-queries.md b/content/guides/04.connect/7.json-queries.md index 205969f8b..6f7492a37 100644 --- a/content/guides/04.connect/7.json-queries.md +++ b/content/guides/04.connect/7.json-queries.md @@ -1,14 +1,14 @@ --- title: JSON Queries -description: Extract and filter values in JSON fields using the json(field, path) function and _json filter operator, with path notation, relational support, GraphQL, and TypeScript SDK. +description: Quickstart for extracting and filtering values inside JSON fields with the json() function and _json filter operator. --- -Directus provides two complementary tools for working with JSON fields in queries: +Directus gives you two ways of working with JSON fields in queries: -- The **`json(field, path)` function** extracts a specific value from a JSON document. It can be used in the `fields`, `sort`, and `alias` query parameters. -- The **`_json` filter operator** filters items by values inside a JSON document without loading the full document. Use it in the `filter` query parameter. +- **`json(field, path)`** extracts a value from a JSON document. Use it in `fields`, `sort`, and `alias`. +- **`_json`** filters items by values inside a JSON document. Use it in `filter`. -Both use the same path notation and work across REST, GraphQL, and the TypeScript SDK. +Both use the same path notation and work across REST, GraphQL, and the SDK. ## Path Notation @@ -16,80 +16,23 @@ Paths use dot notation for object keys and bracket notation for array indices. | Pattern | Example | Meaning | |---|---|---| -| `key` | `color` | Top-level key | -| `a.b.c` | `settings.theme.color` | Nested keys | +| `key` | `color` | Top-level object key | +| `a.b.c` | `settings.theme.color` | Nested object keys | | `[n]` | `tags[0]` | Array element at index `n` | | `a[n].b` | `items[0].name` | Mixed object/array access | -**Examples:** +Wildcards (`*`, `[*]`) among other special characters are not supported. See the [reference](/guides/connect/json-queries-reference#path-notation) for the full list. -``` -json(metadata, color) → top-level key -json(metadata, settings.theme) → nested object -json(data, items[0].name) → array element property -json(data, [0]) → first element of a top-level array -``` - -The following path syntaxes are **not supported** and return an error in both the function and filter operator: - -| Expression | Example | -|---|---| -| Empty brackets (wildcard) | `items[]` | -| `[*]` wildcard | `items[*].name` | -| `*` glob | `items.*` | -| JSONPath predicates | `items[?(@.price > 10)]` | -| `@` current node | `@.name` | -| `$` root | `$.name` | - -### Object Keys with Special Characters - -The path syntax uses `.` as a separator between key segments and has no escape mechanism. Object keys that contain dots, spaces, or other special characters cannot be reached. For example, a key `"first.name"` is interpreted as nested access to key `first`, then key `name`. - -## The `json(field, path)` Function - -The `json(field, path)` function extracts the value from the specified path in a JSON field. It can be used anywhere a field reference is accepted, including the `fields`, `sort`, and `alias` query parameters. - -::callout{icon="material-symbols:warning-rounded" color="warning"} - -**Not Supported in Filters** -The `json(field, path)` function is not supported in the `filter` query parameter. For filtering JSON fields, use the [`_json` filter operator](#the-_json-filter-operator) instead. - -:: - -### Syntax +## Using the `json()` function +**Function Syntax:** ``` json(field, path) ``` -- **`field`** is the name of a JSON column in the collection (or a relational path to one). -- **`path`** is a dot-and-bracket notation path to the value you want to extract from within the JSON document. - -Both arguments are required and separated by a comma. - -In GraphQL, each `json`-typed field exposes a `json(path: String!)` sub-field inside `{fieldName}_func`. The `path` argument is required. The return type is `JSON` (any scalar, object, or array). `{fieldName}_func` already exists for the `count` sub-field; `json` sits alongside it in the same selection. - -The TypeScript SDK accepts `json(fieldName, path)` strings in the `fields` array. The first argument is constrained by TypeScript to fields typed as `json` in your schema. An invalid field name produces a type error. The SDK computes the response alias type automatically from the literal string, so extracted values are fully typed. - -### Response Format - -For REST and the SDK, extracted values are returned as additional fields on each item using auto-generated aliases. The alias follows the pattern: - -``` -{field}_{path}_json -``` - -Special characters in the path (`[`, `]`, `.`) are replaced with underscores. - -| Request field | Response key | -|---|---| -| `json(metadata, color)` | `metadata_color_json` | -| `json(metadata, settings.priority)` | `metadata_settings_priority_json` | -| `json(data, items[0].name)` | `data_items_0_name_json` | - -In GraphQL, the extracted value is returned inside `{fieldName}_func.json`. When requesting multiple paths from the same field, use GraphQL field aliases to distinguish them. +**Example:** -### Basic Example +Extract the `color` key from a `metadata` JSON field: ::code-group @@ -97,18 +40,6 @@ In GraphQL, the extracted value is returned inside `{fieldName}_func.json`. When GET /items/articles?fields=id,title,json(metadata, color) ``` -```graphql [GraphQL] -query { - articles { - id - title - metadata_func { - json(path: "color") - } - } -} -``` - ```js [SDK] import { createDirectus, rest, readItems } from '@directus/sdk'; const directus = createDirectus('https://directus.example.com').with(rest()); @@ -120,79 +51,18 @@ const result = await directus.request( ); ``` -:: - -Response: - -::code-group - -```json [REST / SDK] -{ - "data": [ - { - "id": 1, - "title": "An Article", - "metadata_color_json": "blue" - } - ] -} -``` - -```json [GraphQL] -{ - "data": { - "articles": [ - { - "id": 1, - "title": "An Article", - "metadata_func": { "json": "blue" } - } - ] - } -} -``` - -:: - -### Multiple Paths - -Extract multiple values from the same JSON field in a single request. In GraphQL, use field aliases on the `json` sub-field to distinguish each extraction. - -::code-group - -```http [REST] -GET /items/articles?fields=id,json(metadata, color),json(metadata, settings.theme),json(metadata, tags[0]) -``` - ```graphql [GraphQL] query { articles { id + title metadata_func { - color: json(path: "color") - theme: json(path: "settings.theme") - firstTag: json(path: "tags[0]") + json(path: "color") } } } ``` -```js [SDK] -import { createDirectus, rest, readItems } from '@directus/sdk'; -const directus = createDirectus('https://directus.example.com').with(rest()); - -const result = await directus.request( - readItems('articles', { - fields: [ - 'id', - 'json(metadata, color)', - 'json(metadata, settings.theme)', - 'json(metadata, tags[0])', - ], - }) -); -``` - :: Response: @@ -204,9 +74,8 @@ Response: "data": [ { "id": 1, - "metadata_color_json": "blue", - "metadata_settings_theme_json": "dark", - "metadata_tags_0_json": "featured" + "title": "An Article", + "metadata_color_json": "blue" } ] } @@ -218,11 +87,8 @@ Response: "articles": [ { "id": 1, - "metadata_func": { - "color": "blue", - "theme": "dark", - "firstTag": "featured" - } + "title": "An Article", + "metadata_func": { "json": "blue" } } ] } @@ -231,111 +97,32 @@ Response: :: -### Extracting an Object or Array - -When the path points to an object or array rather than a scalar, the full value is returned as parsed JSON. - -::callout{icon="material-symbols:warning-rounded" color="warning"} - -**Non-Scalar Paths in Sort and Filter** -Sorting or filtering by a path that resolves to an object or array can produce unexpected results. The database compares the serialized form, which depends on dialect-specific JSON ordering and formatting. Use paths that resolve to a scalar value (string, number, boolean) for reliable sorting and filtering. - -:: - -::code-group - -```http [REST] -GET /items/articles?fields=id,json(metadata, dimensions),json(metadata, tags) -``` - -```graphql [GraphQL] -query { - articles { - id - metadata_func { - dimensions: json(path: "dimensions") - tags: json(path: "tags") - } - } -} -``` - -```js [SDK] -import { createDirectus, rest, readItems } from '@directus/sdk'; -const directus = createDirectus('https://directus.example.com').with(rest()); - -const result = await directus.request( - readItems('articles', { - fields: ['id', 'json(metadata, dimensions)', 'json(metadata, tags)'], - }) -); -``` - -:: - -Response: +For REST and the SDK, the extracted value is returned under the alias `{field}_{path}_json`, with `.`, `[`, and `]` replaced by underscores. -::code-group +## Filtering with `_json` -```json [REST / SDK] -{ - "data": [ - { - "id": 1, - "metadata_dimensions_json": { "width": 100, "height": 50 }, - "metadata_tags_json": ["featured", "new"] - } - ] -} +**Operator Syntax:** ``` - -```json [GraphQL] { - "data": { - "articles": [ - { - "id": 1, - "metadata_func": { - "dimensions": { "width": 100, "height": 50 }, - "tags": ["featured", "new"] - } + "field": { + "_json": { + "path": { + "_operator": value } - ] + } } } ``` -:: - -### Relational Queries - -`json(field, path)` can traverse relational fields to extract JSON values from related items. The relational path goes inside the first argument, before the JSON field name. +**Example:** -#### Many-to-One (M2O) - -Syntax: `json(relation.json_field, path)` - -The extracted value is returned nested under the relational key in the response, alongside any other requested fields from that relation. Multiple `json(field, path)` extractions from the same relation are grouped under the same relational key. +Find articles where the `color` key inside `metadata` equals `"blue"`: ::code-group ```http [REST] -GET /items/articles?fields=id,title,category_id.name,json(category_id.metadata, color) -``` - -```graphql [GraphQL] -query { - articles { - id - title - category_id { - name - metadata_func { - color: json(path: "color") - } - } - } -} +GET /items/articles + ?filter={"metadata":{"_json":{"color":{"_eq":"blue"}}}} ``` ```js [SDK] @@ -344,581 +131,48 @@ const directus = createDirectus('https://directus.example.com').with(rest()); const result = await directus.request( readItems('articles', { - fields: ['id', 'title', { category_id: ['name', 'json(metadata, color)'] }], + filter: { + metadata: { + _json: { color: { _eq: 'blue' } }, + }, + }, }) ); ``` -:: - -Response: - -::code-group - -```json [REST / SDK] -{ - "data": [ - { - "id": 1, - "title": "An Article", - "category_id": { - "name": "News", - "metadata_color_json": "blue" - } - } - ] -} -``` - -```json [GraphQL] -{ - "data": { - "articles": [ - { - "id": 1, - "title": "An Article", - "category_id": { - "name": "News", - "metadata_func": { "color": "blue" } - } - } - ] - } -} -``` - -:: - -#### One-to-Many (O2M) - -For O2M relations, each related item returns its own extracted value. The response contains an array of objects, each with the extracted key. - -::code-group - -```http [REST] -GET /items/articles/1?fields=id,json(comments.data, type) -``` - ```graphql [GraphQL] query { - articles_by_id(id: 1) { + articles(filter: { metadata: { _json: { color: { _eq: "blue" } } } }) { id - comments { - data_func { - json(path: "type") - } - } + title } } ``` -```js [SDK] -import { createDirectus, rest, readItem } from '@directus/sdk'; -const directus = createDirectus('https://directus.example.com').with(rest()); - -const result = await directus.request( - readItem('articles', 1, { - fields: ['id', { comments: ['json(data, type)'] }], - }) -); -``` - :: Response: -::code-group - -```json [REST / SDK] -{ - "data": { - "id": 1, - "comments": [ - { "data_type_json": "comment" }, - { "data_type_json": "review" } - ] - } -} -``` - -```json [GraphQL] +```json { - "data": { - "articles_by_id": { - "id": 1, - "comments": [ - { "data_func": { "json": "comment" } }, - { "data_func": { "json": "review" } } - ] - } - } + "data": [ + { "id": 1, "title": "An Article" }, + { "id": 4, "title": "Another Article" } + ] } ``` -:: - -#### Many-to-Any (M2A) - -For M2A relations, use the standard Directus collection scope syntax inside the first argument: `json(relation.item:collection_name.json_field, path)` - -::code-group - -```http [REST] -GET /items/shapes/1?fields=id,json(children.item:circles.metadata, color) -``` - -```graphql [GraphQL] -query { - shapes_by_id(id: 1) { - id - children { - item { - ... on circles { - metadata_func { - json(path: "color") - } - } - } - } - } -} -``` - -```js [SDK] -import { createDirectus, rest, readItem } from '@directus/sdk'; -const directus = createDirectus('https://directus.example.com').with(rest()); - -const result = await directus.request( - readItem('shapes', 1, { - fields: [ - 'id', - { - children: [ - { - item: { - circles: ['json(metadata, color)'], - }, - }, - ], - }, - ], - }) -); -``` - -:: - -Response: - -::code-group - -```json [REST / SDK] -{ - "data": { - "id": 1, - "children": [ - { - "item": { - "metadata_color_json": "red" - } - } - ] - } -} -``` - -```json [GraphQL] -{ - "data": { - "shapes_by_id": { - "id": 1, - "children": [ - { - "item": { - "metadata_func": { "color": "red" } - } - } - ] - } - } -} -``` - -:: - -### Depth Limits - -`json(field, path)` enforces two separate depth limits, calculated independently: - -- **Relational depth** (`MAX_RELATIONAL_DEPTH`, default `10`) limits how deep the relational path in the first argument can go. `json(category_id.metadata, a.b.c.d.e)` has a relational depth of 2 (`category_id` + `metadata`), regardless of the JSON path length. -- **Path depth** (`MAX_JSON_QUERY_DEPTH`, default `10`) limits how many segments the JSON path itself can contain. `json(category_id.metadata, a[0].c.d.e.f.g.h.i.j)` has a path depth of 10 and is allowed by default; one more segment exceeds the limit. - -Exceeding either limit returns an error. - -### TypeScript SDK Type Safety - -The SDK enforces that the first argument of `json()` must be a `json`-typed field. Non-json fields produce a TypeScript error. The output alias is typed automatically as `JsonValue | null` with no cast needed. - -```typescript -import { createDirectus, readItems, rest } from '@directus/sdk'; - -interface Article { - id: number; - title: string; - metadata: 'json' | null; // type literal 'json' tells the SDK this is a json field -} - -interface Schema { - articles: Article[]; -} - -const client = createDirectus('https://directus.example.com').with(rest()); - -// valid: metadata is a json field; metadata_color_json is typed as JsonValue | null -readItems('articles', { fields: ['json(metadata, color)'] }); - -// type error: title is a string field, not json -readItems('articles', { fields: ['json(title, color)'] }); -``` - -The alias rule is `{field}_{path}_json` with `.`, `[`, and `]` replaced by `_`. For a relational field, the extracted alias appears typed on the related item (for example, `items[0].category_id.metadata_color_json`). - -::callout{icon="material-symbols:info-outline"} -**Alias Typing Requires Literal Field Arrays** - -Alias typing only works when the `fields` array is an inline literal or typed `as const`. If the array is built dynamically at runtime, TypeScript widens it to `string[]` and the aliases are not present in the inferred return type. -:: - -When typing inside the `fields` array, the SDK provides partial autocomplete for the `json()` function. For each `json`-typed field in your schema, the IDE offers `json(fieldName, ` as a completion, positioning the cursor ready for the path argument. This works via TypeScript's template-literal completion (TypeScript >= 4.7). Only `json`-typed fields appear as suggestions; the path argument is a free string with no completion hints. - -## The `_json` Filter Operator - -The `_json` operator filters items by values inside a JSON field. It accepts an object mapping JSON paths to standard filter operators, letting you compare specific keys or array elements without loading the full document. - -`_json` is only valid on `json`-typed fields. - -### Syntax - -The `_json` value is an object where each key is a JSON path and each value is a standard filter operator object. - -``` -{ "field": { "_json": { "path": { "_operator": value } } } } -``` - -In GraphQL, input-object keys must be valid identifiers, so paths containing dots, brackets, or starting with `[` must be passed as a typed variable (see [Paths with Dots or Brackets](#paths-with-dots-or-brackets)). - -### Supported Inner Operators - -| Category | Operators | -| --------------- | --------------------------------------------------------- | -| Equality | `_eq`, `_neq`, `_ieq`, `_nieq` | -| Null | `_null`, `_nnull` | -| Set | `_in`, `_nin` | -| String | `_contains`, `_ncontains`, `_icontains`, `_nicontains` | -| Prefix / Suffix | `_starts_with`, `_ends_with` (plus `_i` variants) | -| Numeric | `_gt`, `_gte`, `_lt`, `_lte`, `_between`, `_nbetween` | -| Empty | `_empty`, `_nempty` | - -### Basic Example - -Filter articles where the `color` key inside the `metadata` JSON field equals `"blue"`. - -::code-group - -```http [REST] -GET /items/articles - ?filter={"metadata":{"_json":{"color":{"_eq":"blue"}}}} -``` - -```graphql [GraphQL] -query { - articles(filter: { metadata: { _json: { color: { _eq: "blue" } } } }) { - id - title - } -} -``` - -```js [SDK] -import { createDirectus, rest, readItems } from '@directus/sdk'; -const directus = createDirectus('https://directus.example.com').with(rest()); - -const result = await directus.request( - readItems('articles', { - filter: { - metadata: { - _json: { color: { _eq: 'blue' } }, - }, - }, - }) -); -``` - -:: - -Response: - -```json -{ - "data": [ - { "id": 1, "title": "An Article" }, - { "id": 4, "title": "Another Article" } - ] -} -``` - -### Multiple Path Conditions - -Combine several path conditions inside a single `_json` object. - -::code-group - -```http [REST] -GET /items/articles - ?filter={"metadata":{"_json":{"color":{"_eq":"red"},"brand":{"_in":["BrandX","BrandY"]},"level":{"_gte":3}}}} -``` - -```graphql [GraphQL] -query { - articles( - filter: { - metadata: { - _json: { - color: { _in: ["red", "blue"] } - brand: { _nnull: true } - description: { _contains: "premium" } - } - } - } - ) { - id - title - } -} -``` - -```js [SDK] -const result = await directus.request( - readItems('articles', { - filter: { - metadata: { - _json: { - color: { _eq: 'red' }, - brand: { _in: ['BrandX', 'BrandY'] }, - level: { _gte: 3 }, - }, - }, - }, - }) -); -``` - -:: - -Response: - -```json -{ - "data": [ - { "id": 7, "title": "Premium Red Item" } - ] -} -``` - -### Paths with Dots or Brackets - -Path keys with dots (`settings.theme`), bracket indices (`tags[0]`), or paths starting with `[` are plain strings in REST and the SDK. In GraphQL, input-object keys must be valid identifiers, so pass the `_json` value as a typed variable instead. - -::code-group - -```http [REST] -GET /items/articles - ?filter={"metadata":{"_json":{"settings.theme":{"_eq":"dark"},"tags[0]":{"_eq":"electronics"}}}} -``` - -```graphql [GraphQL] -query FilterByNestedPath($jsonFilter: JSON) { - articles(filter: { metadata: { _json: $jsonFilter } }) { - id - title - } -} - -# Variables: -# { -# "jsonFilter": { -# "settings.theme": { "_eq": "dark" }, -# "tags[0]": { "_eq": "electronics" }, -# "[0].test": { "_null": false } -# } -# } -``` - -```js [SDK] -const result = await directus.request( - readItems('articles', { - filter: { - metadata: { - _json: { - 'settings.theme': { _eq: 'dark' }, - 'tags[0]': { _eq: 'electronics' }, - }, - }, - }, - }) -); -``` - -:: - -Response: - -```json -{ - "data": [ - { "id": 2, "title": "Dark Mode Electronics Review" } - ] -} -``` - -### Relational JSON Filtering - -`_json` nests under relational keys the same way other filters do. To filter on a JSON field belonging to a related item, nest `_json` under the relation name. - -::code-group - -```http [REST] -GET /items/articles - ?filter={"category_id":{"metadata":{"_json":{"color":{"_eq":"blue"}}}}} -``` - -```graphql [GraphQL] -query { - articles(filter: { category_id: { metadata: { _json: { color: { _eq: "blue" } } } } }) { - id - title - category_id { - name - } - } -} -``` - -```js [SDK] -const result = await directus.request( - readItems('articles', { - filter: { - category_id: { - metadata: { - _json: { color: { _eq: 'blue' } }, - }, - }, - }, - }) -); -``` - -:: - -Response: - -```json -{ - "data": [ - { - "id": 1, - "title": "An Article", - "category_id": { "name": "News" } - } - ] -} -``` - -### Combining Multiple Conditions - -Combine multiple `_json` filters at the top level with `_and` or `_or`. - -::code-group - -```http [REST] -GET /items/articles - ?filter={"_and":[{"metadata":{"_json":{"color":{"_eq":"blue"}}}},{"metadata":{"_json":{"size":{"_gt":10}}}}]} -``` - -```graphql [GraphQL] -query { - articles( - filter: { - _and: [ - { metadata: { _json: { color: { _eq: "blue" } } } } - { metadata: { _json: { level: { _gte: 3 } } } } - ] - } - ) { - id - title - } -} -``` - -```js [SDK] -const result = await directus.request( - readItems('articles', { - filter: { - _and: [ - { metadata: { _json: { color: { _eq: 'blue' } } } }, - { metadata: { _json: { size: { _gt: 10 } } } }, - ], - }, - }) -); -``` - -:: - -Response: - -```json -{ - "data": [ - { "id": 3, "title": "Large Blue Article" } - ] -} -``` - -You can also group conditions inside a single `_json` value using `_and` or `_or`: - -```json -{ - "metadata": { - "_json": { - "_and": [ - { "color": { "_eq": "blue" } }, - { "size": { "_gt": 10 } } - ] - } - } -} -``` - -### Dynamic Variables - -Dynamic filter variables like `$CURRENT_USER` and `$NOW` work inside `_json` inner values. They are resolved before the filter runs, so they apply in permission rules and regular queries. - -## Database-Specific Notes - -**PostgreSQL** - -PostgreSQL extracts JSON scalars as `text`. For `_json` numeric comparisons, Directus automatically casts to a numeric type when the filter value is a number or an array of numbers, so `_gt`, `_lt`, `_between`, and related operators work correctly. If you supply a numeric comparison with a string value (for example `{"version":{"_gt":"9"}}`), the comparison remains lexicographic. Use a numeric literal to get numeric comparison. - -**SQLite** - -SQLite can return `0`/`1` instead of boolean values when the path resolves to a boolean. -**MSSQL** +The `_json` operator supports most filter operators, with the exception of itself, geometric, regex, and relational operators (`_json`, `_intersects`, `_intersects_bbox`, `_regex`, `_some`, and `_none`). +See the [reference](/guides/connect/json-queries-reference#supported-inner-operators) for more details. -Always returns scalar values as **strings (`NVARCHAR`)**, even when the original JSON value is a number or boolean. For example, a JSON integer `42` is returned as the string `"42"`. Your application should perform type coercion as needed. +## More information -**Oracle** +The [JSON Queries Reference](/guides/connect/json-queries-reference) covers: -Similar to MSSQL, Oracle returns scalar values as **strings**, regardless of the original JSON type (number, boolean, etc.). A JSON number `3.14` is returned as `"3.14"`. +- Extracting multiple paths in one request +- Relational queries (M2O, O2M, M2A) +- Paths with dots or brackets in GraphQL +- Combining conditions with `_and` / `_or` +- Depth limits and SDK type safety +- Database-specific behavior (PostgreSQL, SQLite, MSSQL, Oracle) diff --git a/content/guides/04.connect/8.json-queries-reference.md b/content/guides/04.connect/8.json-queries-reference.md new file mode 100644 index 000000000..205969f8b --- /dev/null +++ b/content/guides/04.connect/8.json-queries-reference.md @@ -0,0 +1,924 @@ +--- +title: JSON Queries +description: Extract and filter values in JSON fields using the json(field, path) function and _json filter operator, with path notation, relational support, GraphQL, and TypeScript SDK. +--- + +Directus provides two complementary tools for working with JSON fields in queries: + +- The **`json(field, path)` function** extracts a specific value from a JSON document. It can be used in the `fields`, `sort`, and `alias` query parameters. +- The **`_json` filter operator** filters items by values inside a JSON document without loading the full document. Use it in the `filter` query parameter. + +Both use the same path notation and work across REST, GraphQL, and the TypeScript SDK. + +## Path Notation + +Paths use dot notation for object keys and bracket notation for array indices. + +| Pattern | Example | Meaning | +|---|---|---| +| `key` | `color` | Top-level key | +| `a.b.c` | `settings.theme.color` | Nested keys | +| `[n]` | `tags[0]` | Array element at index `n` | +| `a[n].b` | `items[0].name` | Mixed object/array access | + +**Examples:** + +``` +json(metadata, color) → top-level key +json(metadata, settings.theme) → nested object +json(data, items[0].name) → array element property +json(data, [0]) → first element of a top-level array +``` + +The following path syntaxes are **not supported** and return an error in both the function and filter operator: + +| Expression | Example | +|---|---| +| Empty brackets (wildcard) | `items[]` | +| `[*]` wildcard | `items[*].name` | +| `*` glob | `items.*` | +| JSONPath predicates | `items[?(@.price > 10)]` | +| `@` current node | `@.name` | +| `$` root | `$.name` | + +### Object Keys with Special Characters + +The path syntax uses `.` as a separator between key segments and has no escape mechanism. Object keys that contain dots, spaces, or other special characters cannot be reached. For example, a key `"first.name"` is interpreted as nested access to key `first`, then key `name`. + +## The `json(field, path)` Function + +The `json(field, path)` function extracts the value from the specified path in a JSON field. It can be used anywhere a field reference is accepted, including the `fields`, `sort`, and `alias` query parameters. + +::callout{icon="material-symbols:warning-rounded" color="warning"} + +**Not Supported in Filters** +The `json(field, path)` function is not supported in the `filter` query parameter. For filtering JSON fields, use the [`_json` filter operator](#the-_json-filter-operator) instead. + +:: + +### Syntax + +``` +json(field, path) +``` + +- **`field`** is the name of a JSON column in the collection (or a relational path to one). +- **`path`** is a dot-and-bracket notation path to the value you want to extract from within the JSON document. + +Both arguments are required and separated by a comma. + +In GraphQL, each `json`-typed field exposes a `json(path: String!)` sub-field inside `{fieldName}_func`. The `path` argument is required. The return type is `JSON` (any scalar, object, or array). `{fieldName}_func` already exists for the `count` sub-field; `json` sits alongside it in the same selection. + +The TypeScript SDK accepts `json(fieldName, path)` strings in the `fields` array. The first argument is constrained by TypeScript to fields typed as `json` in your schema. An invalid field name produces a type error. The SDK computes the response alias type automatically from the literal string, so extracted values are fully typed. + +### Response Format + +For REST and the SDK, extracted values are returned as additional fields on each item using auto-generated aliases. The alias follows the pattern: + +``` +{field}_{path}_json +``` + +Special characters in the path (`[`, `]`, `.`) are replaced with underscores. + +| Request field | Response key | +|---|---| +| `json(metadata, color)` | `metadata_color_json` | +| `json(metadata, settings.priority)` | `metadata_settings_priority_json` | +| `json(data, items[0].name)` | `data_items_0_name_json` | + +In GraphQL, the extracted value is returned inside `{fieldName}_func.json`. When requesting multiple paths from the same field, use GraphQL field aliases to distinguish them. + +### Basic Example + +::code-group + +```http [REST] +GET /items/articles?fields=id,title,json(metadata, color) +``` + +```graphql [GraphQL] +query { + articles { + id + title + metadata_func { + json(path: "color") + } + } +} +``` + +```js [SDK] +import { createDirectus, rest, readItems } from '@directus/sdk'; +const directus = createDirectus('https://directus.example.com').with(rest()); + +const result = await directus.request( + readItems('articles', { + fields: ['id', 'title', 'json(metadata, color)'], + }) +); +``` + +:: + +Response: + +::code-group + +```json [REST / SDK] +{ + "data": [ + { + "id": 1, + "title": "An Article", + "metadata_color_json": "blue" + } + ] +} +``` + +```json [GraphQL] +{ + "data": { + "articles": [ + { + "id": 1, + "title": "An Article", + "metadata_func": { "json": "blue" } + } + ] + } +} +``` + +:: + +### Multiple Paths + +Extract multiple values from the same JSON field in a single request. In GraphQL, use field aliases on the `json` sub-field to distinguish each extraction. + +::code-group + +```http [REST] +GET /items/articles?fields=id,json(metadata, color),json(metadata, settings.theme),json(metadata, tags[0]) +``` + +```graphql [GraphQL] +query { + articles { + id + metadata_func { + color: json(path: "color") + theme: json(path: "settings.theme") + firstTag: json(path: "tags[0]") + } + } +} +``` + +```js [SDK] +import { createDirectus, rest, readItems } from '@directus/sdk'; +const directus = createDirectus('https://directus.example.com').with(rest()); + +const result = await directus.request( + readItems('articles', { + fields: [ + 'id', + 'json(metadata, color)', + 'json(metadata, settings.theme)', + 'json(metadata, tags[0])', + ], + }) +); +``` + +:: + +Response: + +::code-group + +```json [REST / SDK] +{ + "data": [ + { + "id": 1, + "metadata_color_json": "blue", + "metadata_settings_theme_json": "dark", + "metadata_tags_0_json": "featured" + } + ] +} +``` + +```json [GraphQL] +{ + "data": { + "articles": [ + { + "id": 1, + "metadata_func": { + "color": "blue", + "theme": "dark", + "firstTag": "featured" + } + } + ] + } +} +``` + +:: + +### Extracting an Object or Array + +When the path points to an object or array rather than a scalar, the full value is returned as parsed JSON. + +::callout{icon="material-symbols:warning-rounded" color="warning"} + +**Non-Scalar Paths in Sort and Filter** +Sorting or filtering by a path that resolves to an object or array can produce unexpected results. The database compares the serialized form, which depends on dialect-specific JSON ordering and formatting. Use paths that resolve to a scalar value (string, number, boolean) for reliable sorting and filtering. + +:: + +::code-group + +```http [REST] +GET /items/articles?fields=id,json(metadata, dimensions),json(metadata, tags) +``` + +```graphql [GraphQL] +query { + articles { + id + metadata_func { + dimensions: json(path: "dimensions") + tags: json(path: "tags") + } + } +} +``` + +```js [SDK] +import { createDirectus, rest, readItems } from '@directus/sdk'; +const directus = createDirectus('https://directus.example.com').with(rest()); + +const result = await directus.request( + readItems('articles', { + fields: ['id', 'json(metadata, dimensions)', 'json(metadata, tags)'], + }) +); +``` + +:: + +Response: + +::code-group + +```json [REST / SDK] +{ + "data": [ + { + "id": 1, + "metadata_dimensions_json": { "width": 100, "height": 50 }, + "metadata_tags_json": ["featured", "new"] + } + ] +} +``` + +```json [GraphQL] +{ + "data": { + "articles": [ + { + "id": 1, + "metadata_func": { + "dimensions": { "width": 100, "height": 50 }, + "tags": ["featured", "new"] + } + } + ] + } +} +``` + +:: + +### Relational Queries + +`json(field, path)` can traverse relational fields to extract JSON values from related items. The relational path goes inside the first argument, before the JSON field name. + +#### Many-to-One (M2O) + +Syntax: `json(relation.json_field, path)` + +The extracted value is returned nested under the relational key in the response, alongside any other requested fields from that relation. Multiple `json(field, path)` extractions from the same relation are grouped under the same relational key. + +::code-group + +```http [REST] +GET /items/articles?fields=id,title,category_id.name,json(category_id.metadata, color) +``` + +```graphql [GraphQL] +query { + articles { + id + title + category_id { + name + metadata_func { + color: json(path: "color") + } + } + } +} +``` + +```js [SDK] +import { createDirectus, rest, readItems } from '@directus/sdk'; +const directus = createDirectus('https://directus.example.com').with(rest()); + +const result = await directus.request( + readItems('articles', { + fields: ['id', 'title', { category_id: ['name', 'json(metadata, color)'] }], + }) +); +``` + +:: + +Response: + +::code-group + +```json [REST / SDK] +{ + "data": [ + { + "id": 1, + "title": "An Article", + "category_id": { + "name": "News", + "metadata_color_json": "blue" + } + } + ] +} +``` + +```json [GraphQL] +{ + "data": { + "articles": [ + { + "id": 1, + "title": "An Article", + "category_id": { + "name": "News", + "metadata_func": { "color": "blue" } + } + } + ] + } +} +``` + +:: + +#### One-to-Many (O2M) + +For O2M relations, each related item returns its own extracted value. The response contains an array of objects, each with the extracted key. + +::code-group + +```http [REST] +GET /items/articles/1?fields=id,json(comments.data, type) +``` + +```graphql [GraphQL] +query { + articles_by_id(id: 1) { + id + comments { + data_func { + json(path: "type") + } + } + } +} +``` + +```js [SDK] +import { createDirectus, rest, readItem } from '@directus/sdk'; +const directus = createDirectus('https://directus.example.com').with(rest()); + +const result = await directus.request( + readItem('articles', 1, { + fields: ['id', { comments: ['json(data, type)'] }], + }) +); +``` + +:: + +Response: + +::code-group + +```json [REST / SDK] +{ + "data": { + "id": 1, + "comments": [ + { "data_type_json": "comment" }, + { "data_type_json": "review" } + ] + } +} +``` + +```json [GraphQL] +{ + "data": { + "articles_by_id": { + "id": 1, + "comments": [ + { "data_func": { "json": "comment" } }, + { "data_func": { "json": "review" } } + ] + } + } +} +``` + +:: + +#### Many-to-Any (M2A) + +For M2A relations, use the standard Directus collection scope syntax inside the first argument: `json(relation.item:collection_name.json_field, path)` + +::code-group + +```http [REST] +GET /items/shapes/1?fields=id,json(children.item:circles.metadata, color) +``` + +```graphql [GraphQL] +query { + shapes_by_id(id: 1) { + id + children { + item { + ... on circles { + metadata_func { + json(path: "color") + } + } + } + } + } +} +``` + +```js [SDK] +import { createDirectus, rest, readItem } from '@directus/sdk'; +const directus = createDirectus('https://directus.example.com').with(rest()); + +const result = await directus.request( + readItem('shapes', 1, { + fields: [ + 'id', + { + children: [ + { + item: { + circles: ['json(metadata, color)'], + }, + }, + ], + }, + ], + }) +); +``` + +:: + +Response: + +::code-group + +```json [REST / SDK] +{ + "data": { + "id": 1, + "children": [ + { + "item": { + "metadata_color_json": "red" + } + } + ] + } +} +``` + +```json [GraphQL] +{ + "data": { + "shapes_by_id": { + "id": 1, + "children": [ + { + "item": { + "metadata_func": { "color": "red" } + } + } + ] + } + } +} +``` + +:: + +### Depth Limits + +`json(field, path)` enforces two separate depth limits, calculated independently: + +- **Relational depth** (`MAX_RELATIONAL_DEPTH`, default `10`) limits how deep the relational path in the first argument can go. `json(category_id.metadata, a.b.c.d.e)` has a relational depth of 2 (`category_id` + `metadata`), regardless of the JSON path length. +- **Path depth** (`MAX_JSON_QUERY_DEPTH`, default `10`) limits how many segments the JSON path itself can contain. `json(category_id.metadata, a[0].c.d.e.f.g.h.i.j)` has a path depth of 10 and is allowed by default; one more segment exceeds the limit. + +Exceeding either limit returns an error. + +### TypeScript SDK Type Safety + +The SDK enforces that the first argument of `json()` must be a `json`-typed field. Non-json fields produce a TypeScript error. The output alias is typed automatically as `JsonValue | null` with no cast needed. + +```typescript +import { createDirectus, readItems, rest } from '@directus/sdk'; + +interface Article { + id: number; + title: string; + metadata: 'json' | null; // type literal 'json' tells the SDK this is a json field +} + +interface Schema { + articles: Article[]; +} + +const client = createDirectus('https://directus.example.com').with(rest()); + +// valid: metadata is a json field; metadata_color_json is typed as JsonValue | null +readItems('articles', { fields: ['json(metadata, color)'] }); + +// type error: title is a string field, not json +readItems('articles', { fields: ['json(title, color)'] }); +``` + +The alias rule is `{field}_{path}_json` with `.`, `[`, and `]` replaced by `_`. For a relational field, the extracted alias appears typed on the related item (for example, `items[0].category_id.metadata_color_json`). + +::callout{icon="material-symbols:info-outline"} +**Alias Typing Requires Literal Field Arrays** + +Alias typing only works when the `fields` array is an inline literal or typed `as const`. If the array is built dynamically at runtime, TypeScript widens it to `string[]` and the aliases are not present in the inferred return type. +:: + +When typing inside the `fields` array, the SDK provides partial autocomplete for the `json()` function. For each `json`-typed field in your schema, the IDE offers `json(fieldName, ` as a completion, positioning the cursor ready for the path argument. This works via TypeScript's template-literal completion (TypeScript >= 4.7). Only `json`-typed fields appear as suggestions; the path argument is a free string with no completion hints. + +## The `_json` Filter Operator + +The `_json` operator filters items by values inside a JSON field. It accepts an object mapping JSON paths to standard filter operators, letting you compare specific keys or array elements without loading the full document. + +`_json` is only valid on `json`-typed fields. + +### Syntax + +The `_json` value is an object where each key is a JSON path and each value is a standard filter operator object. + +``` +{ "field": { "_json": { "path": { "_operator": value } } } } +``` + +In GraphQL, input-object keys must be valid identifiers, so paths containing dots, brackets, or starting with `[` must be passed as a typed variable (see [Paths with Dots or Brackets](#paths-with-dots-or-brackets)). + +### Supported Inner Operators + +| Category | Operators | +| --------------- | --------------------------------------------------------- | +| Equality | `_eq`, `_neq`, `_ieq`, `_nieq` | +| Null | `_null`, `_nnull` | +| Set | `_in`, `_nin` | +| String | `_contains`, `_ncontains`, `_icontains`, `_nicontains` | +| Prefix / Suffix | `_starts_with`, `_ends_with` (plus `_i` variants) | +| Numeric | `_gt`, `_gte`, `_lt`, `_lte`, `_between`, `_nbetween` | +| Empty | `_empty`, `_nempty` | + +### Basic Example + +Filter articles where the `color` key inside the `metadata` JSON field equals `"blue"`. + +::code-group + +```http [REST] +GET /items/articles + ?filter={"metadata":{"_json":{"color":{"_eq":"blue"}}}} +``` + +```graphql [GraphQL] +query { + articles(filter: { metadata: { _json: { color: { _eq: "blue" } } } }) { + id + title + } +} +``` + +```js [SDK] +import { createDirectus, rest, readItems } from '@directus/sdk'; +const directus = createDirectus('https://directus.example.com').with(rest()); + +const result = await directus.request( + readItems('articles', { + filter: { + metadata: { + _json: { color: { _eq: 'blue' } }, + }, + }, + }) +); +``` + +:: + +Response: + +```json +{ + "data": [ + { "id": 1, "title": "An Article" }, + { "id": 4, "title": "Another Article" } + ] +} +``` + +### Multiple Path Conditions + +Combine several path conditions inside a single `_json` object. + +::code-group + +```http [REST] +GET /items/articles + ?filter={"metadata":{"_json":{"color":{"_eq":"red"},"brand":{"_in":["BrandX","BrandY"]},"level":{"_gte":3}}}} +``` + +```graphql [GraphQL] +query { + articles( + filter: { + metadata: { + _json: { + color: { _in: ["red", "blue"] } + brand: { _nnull: true } + description: { _contains: "premium" } + } + } + } + ) { + id + title + } +} +``` + +```js [SDK] +const result = await directus.request( + readItems('articles', { + filter: { + metadata: { + _json: { + color: { _eq: 'red' }, + brand: { _in: ['BrandX', 'BrandY'] }, + level: { _gte: 3 }, + }, + }, + }, + }) +); +``` + +:: + +Response: + +```json +{ + "data": [ + { "id": 7, "title": "Premium Red Item" } + ] +} +``` + +### Paths with Dots or Brackets + +Path keys with dots (`settings.theme`), bracket indices (`tags[0]`), or paths starting with `[` are plain strings in REST and the SDK. In GraphQL, input-object keys must be valid identifiers, so pass the `_json` value as a typed variable instead. + +::code-group + +```http [REST] +GET /items/articles + ?filter={"metadata":{"_json":{"settings.theme":{"_eq":"dark"},"tags[0]":{"_eq":"electronics"}}}} +``` + +```graphql [GraphQL] +query FilterByNestedPath($jsonFilter: JSON) { + articles(filter: { metadata: { _json: $jsonFilter } }) { + id + title + } +} + +# Variables: +# { +# "jsonFilter": { +# "settings.theme": { "_eq": "dark" }, +# "tags[0]": { "_eq": "electronics" }, +# "[0].test": { "_null": false } +# } +# } +``` + +```js [SDK] +const result = await directus.request( + readItems('articles', { + filter: { + metadata: { + _json: { + 'settings.theme': { _eq: 'dark' }, + 'tags[0]': { _eq: 'electronics' }, + }, + }, + }, + }) +); +``` + +:: + +Response: + +```json +{ + "data": [ + { "id": 2, "title": "Dark Mode Electronics Review" } + ] +} +``` + +### Relational JSON Filtering + +`_json` nests under relational keys the same way other filters do. To filter on a JSON field belonging to a related item, nest `_json` under the relation name. + +::code-group + +```http [REST] +GET /items/articles + ?filter={"category_id":{"metadata":{"_json":{"color":{"_eq":"blue"}}}}} +``` + +```graphql [GraphQL] +query { + articles(filter: { category_id: { metadata: { _json: { color: { _eq: "blue" } } } } }) { + id + title + category_id { + name + } + } +} +``` + +```js [SDK] +const result = await directus.request( + readItems('articles', { + filter: { + category_id: { + metadata: { + _json: { color: { _eq: 'blue' } }, + }, + }, + }, + }) +); +``` + +:: + +Response: + +```json +{ + "data": [ + { + "id": 1, + "title": "An Article", + "category_id": { "name": "News" } + } + ] +} +``` + +### Combining Multiple Conditions + +Combine multiple `_json` filters at the top level with `_and` or `_or`. + +::code-group + +```http [REST] +GET /items/articles + ?filter={"_and":[{"metadata":{"_json":{"color":{"_eq":"blue"}}}},{"metadata":{"_json":{"size":{"_gt":10}}}}]} +``` + +```graphql [GraphQL] +query { + articles( + filter: { + _and: [ + { metadata: { _json: { color: { _eq: "blue" } } } } + { metadata: { _json: { level: { _gte: 3 } } } } + ] + } + ) { + id + title + } +} +``` + +```js [SDK] +const result = await directus.request( + readItems('articles', { + filter: { + _and: [ + { metadata: { _json: { color: { _eq: 'blue' } } } }, + { metadata: { _json: { size: { _gt: 10 } } } }, + ], + }, + }) +); +``` + +:: + +Response: + +```json +{ + "data": [ + { "id": 3, "title": "Large Blue Article" } + ] +} +``` + +You can also group conditions inside a single `_json` value using `_and` or `_or`: + +```json +{ + "metadata": { + "_json": { + "_and": [ + { "color": { "_eq": "blue" } }, + { "size": { "_gt": 10 } } + ] + } + } +} +``` + +### Dynamic Variables + +Dynamic filter variables like `$CURRENT_USER` and `$NOW` work inside `_json` inner values. They are resolved before the filter runs, so they apply in permission rules and regular queries. + +## Database-Specific Notes + +**PostgreSQL** + +PostgreSQL extracts JSON scalars as `text`. For `_json` numeric comparisons, Directus automatically casts to a numeric type when the filter value is a number or an array of numbers, so `_gt`, `_lt`, `_between`, and related operators work correctly. If you supply a numeric comparison with a string value (for example `{"version":{"_gt":"9"}}`), the comparison remains lexicographic. Use a numeric literal to get numeric comparison. + +**SQLite** + +SQLite can return `0`/`1` instead of boolean values when the path resolves to a boolean. + +**MSSQL** + +Always returns scalar values as **strings (`NVARCHAR`)**, even when the original JSON value is a number or boolean. For example, a JSON integer `42` is returned as the string `"42"`. Your application should perform type coercion as needed. + +**Oracle** + +Similar to MSSQL, Oracle returns scalar values as **strings**, regardless of the original JSON type (number, boolean, etc.). A JSON number `3.14` is returned as `"3.14"`. From a9ae67ae0f8999dd9d1b72ff64678d1eff5060e7 Mon Sep 17 00:00:00 2001 From: Brainslug Date: Tue, 21 Apr 2026 22:14:05 +0200 Subject: [PATCH 05/18] remove over explanation from the filter rules page --- content/guides/04.connect/2.filter-rules.md | 41 +-------------------- 1 file changed, 1 insertion(+), 40 deletions(-) diff --git a/content/guides/04.connect/2.filter-rules.md b/content/guides/04.connect/2.filter-rules.md index 877ee3f3d..6b065cc8b 100644 --- a/content/guides/04.connect/2.filter-rules.md +++ b/content/guides/04.connect/2.filter-rules.md @@ -48,7 +48,7 @@ Filters are used in permissions, validations, and automations, as well as throug [2] Only available on geometry fields.
[3] Only available in validation permissions.
[4] Only available on One to Many relationship fields.
-[5] Only available on JSON fields. See the `_json` section below. +[5] Only available on JSON fields. See [JSON Queries](/guides/connect/json-queries) for usage and examples. ## Filter Syntax @@ -78,45 +78,6 @@ This filter checks the `title` field contains the case-sensitive substring 'Dire ``` :: -## The `_json` Filter Operator - -Use the `_json` operator to filter items by values inside a JSON field. It accepts an object mapping JSON paths to standard filter operators, letting you compare specific keys or array elements without loading the full document. `_json` is only valid on `json`-typed fields. - -::code-group - -```http [REST] -GET /items/articles - ?filter={"metadata":{"_json":{"color":{"_eq":"blue"}}}} -``` - -```graphql [GraphQL] -query { - articles(filter: { metadata: { _json: { color: { _eq: "blue" } } } }) { - id - title - } -} -``` - -```js [SDK] -import { createDirectus, rest, readItems } from '@directus/sdk'; -const directus = createDirectus('https://directus.example.com').with(rest()); - -const result = await directus.request( - readItems('articles', { - filter: { - metadata: { - _json: { color: { _eq: 'blue' } }, - }, - }, - }) -); -``` - -:: - -See [JSON Queries](/guides/connect/json-queries#the-_json-filter-operator) for supported operators, relational filtering, dotted and bracket path handling in GraphQL, and full details. - ## Relational Fields You can filter items based on related data by nesting field names in your query. This allows you to query items not just by their own fields, but also by values in related collections. From 99f054170c71130baafd1735ee4e8600640e0164 Mon Sep 17 00:00:00 2001 From: Brainslug Date: Tue, 21 Apr 2026 22:21:25 +0200 Subject: [PATCH 06/18] moved path notation header down --- content/guides/04.connect/7.json-queries.md | 26 ++++++++++----------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/content/guides/04.connect/7.json-queries.md b/content/guides/04.connect/7.json-queries.md index 6f7492a37..c4a983a8a 100644 --- a/content/guides/04.connect/7.json-queries.md +++ b/content/guides/04.connect/7.json-queries.md @@ -10,19 +10,6 @@ Directus gives you two ways of working with JSON fields in queries: Both use the same path notation and work across REST, GraphQL, and the SDK. -## Path Notation - -Paths use dot notation for object keys and bracket notation for array indices. - -| Pattern | Example | Meaning | -|---|---|---| -| `key` | `color` | Top-level object key | -| `a.b.c` | `settings.theme.color` | Nested object keys | -| `[n]` | `tags[0]` | Array element at index `n` | -| `a[n].b` | `items[0].name` | Mixed object/array access | - -Wildcards (`*`, `[*]`) among other special characters are not supported. See the [reference](/guides/connect/json-queries-reference#path-notation) for the full list. - ## Using the `json()` function **Function Syntax:** @@ -166,6 +153,19 @@ Response: The `_json` operator supports most filter operators, with the exception of itself, geometric, regex, and relational operators (`_json`, `_intersects`, `_intersects_bbox`, `_regex`, `_some`, and `_none`). See the [reference](/guides/connect/json-queries-reference#supported-inner-operators) for more details. +## Path Notation + +Paths use dot notation for object keys and bracket notation for array indices. + +| Pattern | Example | Meaning | +|---|---|---| +| `key` | `color` | Top-level object key | +| `a.b.c` | `settings.theme.color` | Nested object keys | +| `[n]` | `tags[0]` | Array element at index `n` | +| `a[n].b` | `items[0].name` | Mixed object/array access | + +Wildcards (`*`, `[*]`) among other special characters are not supported. See the [reference](/guides/connect/json-queries-reference#path-notation) for the full list. + ## More information The [JSON Queries Reference](/guides/connect/json-queries-reference) covers: From 48d42a61f0c31951fad426a6a89026f567059720 Mon Sep 17 00:00:00 2001 From: Brainslug Date: Tue, 21 Apr 2026 22:33:07 +0200 Subject: [PATCH 07/18] inline and simplify the description on the query parameters page --- content/_partials/json-function.md | 38 ------------------- .../guides/04.connect/3.query-parameters.md | 6 ++- 2 files changed, 5 insertions(+), 39 deletions(-) delete mode 100644 content/_partials/json-function.md diff --git a/content/_partials/json-function.md b/content/_partials/json-function.md deleted file mode 100644 index ab5d5939c..000000000 --- a/content/_partials/json-function.md +++ /dev/null @@ -1,38 +0,0 @@ -### JSON Function - -Use the `json(field, path)` function to extract a specific value from a JSON field and return it as a separate field in the query response. Paths use dot notation for object keys and bracket notation for array indices. - -::code-group - -```http [REST] -GET /items/articles?fields=id,title,json(metadata, color) -``` - -```graphql [GraphQL] -query { - articles { - id - title - metadata_func { - json(path: "color") - } - } -} -``` - -```js [SDK] -import { createDirectus, rest, readItems } from '@directus/sdk'; -const directus = createDirectus('https://directus.example.com').with(rest()); - -const result = await directus.request( - readItems('articles', { - fields: ['id', 'title', 'json(metadata, color)'], - }) -); -``` - -:: - -See [JSON Queries](/guides/connect/json-queries) for path syntax, relational traversal, depth limits, and full GraphQL and TypeScript SDK details. - -The `json(field, path)` function is not supported in `filter`. For filtering JSON fields, use the [`_json` filter operator](/guides/connect/filter-rules) instead. diff --git a/content/guides/04.connect/3.query-parameters.md b/content/guides/04.connect/3.query-parameters.md index 602e874d2..9ffd90b9c 100644 --- a/content/guides/04.connect/3.query-parameters.md +++ b/content/guides/04.connect/3.query-parameters.md @@ -634,7 +634,11 @@ const result = await directus.request( ``` :: -:partial{content="json-function"} +### The JSON Function + +Extract a specific value from a JSON field and return it as a separate field in the response. See [Using the `json(field, path)` function](/guides/connect/json-queries#using-the-json-function) for full path syntax and examples. + +This function cannot be used in `filter`. To filter on JSON values, use the [`_json` filter operator](/guides/connect/json-queries#filtering-with-_json) instead. ## Backlink From 8ddff4242b3c4d943b0745f7a0498a325fe9fbe7 Mon Sep 17 00:00:00 2001 From: Brainslug Date: Tue, 21 Apr 2026 22:37:23 +0200 Subject: [PATCH 08/18] small tweaks --- .../04.connect/8.json-queries-reference.md | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/content/guides/04.connect/8.json-queries-reference.md b/content/guides/04.connect/8.json-queries-reference.md index 205969f8b..87e7368d1 100644 --- a/content/guides/04.connect/8.json-queries-reference.md +++ b/content/guides/04.connect/8.json-queries-reference.md @@ -1,14 +1,17 @@ --- -title: JSON Queries -description: Extract and filter values in JSON fields using the json(field, path) function and _json filter operator, with path notation, relational support, GraphQL, and TypeScript SDK. +title: JSON Queries Reference +description: Complete reference for the json(field, path) function and _json filter operator, covering path notation, relational queries, GraphQL, the SDK, depth limits, and database-specific behavior. +navigation: false --- -Directus provides two complementary tools for working with JSON fields in queries: +This is the full reference for querying JSON fields in Directus. For a short introduction with basic syntax and examples, see [JSON Queries](/guides/connect/json-queries). + +Directus provides two ways of working with JSON fields in queries: - The **`json(field, path)` function** extracts a specific value from a JSON document. It can be used in the `fields`, `sort`, and `alias` query parameters. - The **`_json` filter operator** filters items by values inside a JSON document without loading the full document. Use it in the `filter` query parameter. -Both use the same path notation and work across REST, GraphQL, and the TypeScript SDK. +Both use the same path notation and work across REST, GraphQL, and the SDK. ## Path Notation @@ -69,7 +72,7 @@ Both arguments are required and separated by a comma. In GraphQL, each `json`-typed field exposes a `json(path: String!)` sub-field inside `{fieldName}_func`. The `path` argument is required. The return type is `JSON` (any scalar, object, or array). `{fieldName}_func` already exists for the `count` sub-field; `json` sits alongside it in the same selection. -The TypeScript SDK accepts `json(fieldName, path)` strings in the `fields` array. The first argument is constrained by TypeScript to fields typed as `json` in your schema. An invalid field name produces a type error. The SDK computes the response alias type automatically from the literal string, so extracted values are fully typed. +The SDK accepts `json(fieldName, path)` strings in the `fields` array. The first argument is constrained by TypeScript to fields typed as `json` in your schema. An invalid field name produces a type error. The SDK computes the response alias type automatically from the literal string, so extracted values are fully typed. ### Response Format @@ -555,7 +558,7 @@ Response: Exceeding either limit returns an error. -### TypeScript SDK Type Safety +### SDK Type Safety The SDK enforces that the first argument of `json()` must be a `json`-typed field. Non-json fields produce a TypeScript error. The output alias is typed automatically as `JsonValue | null` with no cast needed. @@ -609,15 +612,14 @@ In GraphQL, input-object keys must be valid identifiers, so paths containing dot ### Supported Inner Operators -| Category | Operators | -| --------------- | --------------------------------------------------------- | -| Equality | `_eq`, `_neq`, `_ieq`, `_nieq` | -| Null | `_null`, `_nnull` | -| Set | `_in`, `_nin` | -| String | `_contains`, `_ncontains`, `_icontains`, `_nicontains` | -| Prefix / Suffix | `_starts_with`, `_ends_with` (plus `_i` variants) | -| Numeric | `_gt`, `_gte`, `_lt`, `_lte`, `_between`, `_nbetween` | -| Empty | `_empty`, `_nempty` | +`_json` supports most standard filter operators. The following operators are **not** supported: + +| Category | Operators | +| ----------- | ---------------------------------- | +| JSON | `_json` | +| Geometric | `_intersects`, `_intersects_bbox` | +| Regex | `_regex` | +| Relational | `_some`, `_none` | ### Basic Example From 257e19b47145691cd35108c7c9ecf7e26ac8a77a Mon Sep 17 00:00:00 2001 From: judda <44623501+ComfortablyCoding@users.noreply.github.com> Date: Fri, 29 May 2026 10:13:41 -0400 Subject: [PATCH 09/18] Apply suggestion from @ComfortablyCoding --- content/guides/04.connect/7.json-queries.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/guides/04.connect/7.json-queries.md b/content/guides/04.connect/7.json-queries.md index c4a983a8a..7f3d6100a 100644 --- a/content/guides/04.connect/7.json-queries.md +++ b/content/guides/04.connect/7.json-queries.md @@ -3,7 +3,7 @@ title: JSON Queries description: Quickstart for extracting and filtering values inside JSON fields with the json() function and _json filter operator. --- -Directus gives you two ways of working with JSON fields in queries: +Directus provides two ways of working with JSON fields in queries: - **`json(field, path)`** extracts a value from a JSON document. Use it in `fields`, `sort`, and `alias`. - **`_json`** filters items by values inside a JSON document. Use it in `filter`. From db8fbe2b06a3ebcf91931d556e369c1118c0db0d Mon Sep 17 00:00:00 2001 From: judda <44623501+ComfortablyCoding@users.noreply.github.com> Date: Fri, 29 May 2026 10:14:12 -0400 Subject: [PATCH 10/18] Apply suggestion from @ComfortablyCoding --- content/guides/04.connect/7.json-queries.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/guides/04.connect/7.json-queries.md b/content/guides/04.connect/7.json-queries.md index 7f3d6100a..b1e8a7f5a 100644 --- a/content/guides/04.connect/7.json-queries.md +++ b/content/guides/04.connect/7.json-queries.md @@ -5,7 +5,7 @@ description: Quickstart for extracting and filtering values inside JSON fields w Directus provides two ways of working with JSON fields in queries: -- **`json(field, path)`** extracts a value from a JSON document. Use it in `fields`, `sort`, and `alias`. +- **`json(field, path)`**: A selection function to extract a value from a JSON document. It can be used in the `fields`, `sort`, and `alias` parameters. - **`_json`** filters items by values inside a JSON document. Use it in `filter`. Both use the same path notation and work across REST, GraphQL, and the SDK. From 252d5d625350e8c7f66c82b8e35ab61b345c7376 Mon Sep 17 00:00:00 2001 From: judda <44623501+ComfortablyCoding@users.noreply.github.com> Date: Fri, 29 May 2026 10:15:05 -0400 Subject: [PATCH 11/18] Apply suggestion from @ComfortablyCoding --- content/guides/04.connect/7.json-queries.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/guides/04.connect/7.json-queries.md b/content/guides/04.connect/7.json-queries.md index b1e8a7f5a..6a2b2162a 100644 --- a/content/guides/04.connect/7.json-queries.md +++ b/content/guides/04.connect/7.json-queries.md @@ -6,7 +6,7 @@ description: Quickstart for extracting and filtering values inside JSON fields w Directus provides two ways of working with JSON fields in queries: - **`json(field, path)`**: A selection function to extract a value from a JSON document. It can be used in the `fields`, `sort`, and `alias` parameters. -- **`_json`** filters items by values inside a JSON document. Use it in `filter`. +- **`_json`**: A filter operator that lets you filter records based on values within a JSON document. It can be used as an operator in the `filter` parameter. Both use the same path notation and work across REST, GraphQL, and the SDK. From 01ddd44f0081ebb43467c1287f1d9be185f1f053 Mon Sep 17 00:00:00 2001 From: judda <44623501+ComfortablyCoding@users.noreply.github.com> Date: Fri, 29 May 2026 10:15:34 -0400 Subject: [PATCH 12/18] Apply suggestion from @ComfortablyCoding --- content/guides/04.connect/7.json-queries.md | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/content/guides/04.connect/7.json-queries.md b/content/guides/04.connect/7.json-queries.md index 6a2b2162a..bfad395a3 100644 --- a/content/guides/04.connect/7.json-queries.md +++ b/content/guides/04.connect/7.json-queries.md @@ -168,11 +168,4 @@ Wildcards (`*`, `[*]`) among other special characters are not supported. See the ## More information -The [JSON Queries Reference](/guides/connect/json-queries-reference) covers: - -- Extracting multiple paths in one request -- Relational queries (M2O, O2M, M2A) -- Paths with dots or brackets in GraphQL -- Combining conditions with `_and` / `_or` -- Depth limits and SDK type safety -- Database-specific behavior (PostgreSQL, SQLite, MSSQL, Oracle) +For advanced usage, expanded explanations etc, see [JSON Queries Reference](/guides/connect/json-queries-reference) From 7a99d8d42e82684b4164dc3654a7496d1057a4b1 Mon Sep 17 00:00:00 2001 From: daedalus <44623501+ComfortablyCoding@users.noreply.github.com> Date: Fri, 29 May 2026 10:42:30 -0400 Subject: [PATCH 13/18] wordage --- content/guides/04.connect/7.json-queries.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/content/guides/04.connect/7.json-queries.md b/content/guides/04.connect/7.json-queries.md index bfad395a3..90b56f3b1 100644 --- a/content/guides/04.connect/7.json-queries.md +++ b/content/guides/04.connect/7.json-queries.md @@ -150,7 +150,7 @@ Response: ``` -The `_json` operator supports most filter operators, with the exception of itself, geometric, regex, and relational operators (`_json`, `_intersects`, `_intersects_bbox`, `_regex`, `_some`, and `_none`). +The `_json` operator supports most filter operators, except geometric, regex, and relational ones, plus itself (`_json`, `_intersects`, `_intersects_bbox`, `_regex`, `_some`, and `_none`). See the [reference](/guides/connect/json-queries-reference#supported-inner-operators) for more details. ## Path Notation @@ -159,7 +159,7 @@ Paths use dot notation for object keys and bracket notation for array indices. | Pattern | Example | Meaning | |---|---|---| -| `key` | `color` | Top-level object key | +| `key` | `color` | Top-level key | | `a.b.c` | `settings.theme.color` | Nested object keys | | `[n]` | `tags[0]` | Array element at index `n` | | `a[n].b` | `items[0].name` | Mixed object/array access | @@ -168,4 +168,4 @@ Wildcards (`*`, `[*]`) among other special characters are not supported. See the ## More information -For advanced usage, expanded explanations etc, see [JSON Queries Reference](/guides/connect/json-queries-reference) +For advanced usage and expanded explanations, see [JSON Queries Reference](/guides/connect/json-queries-reference). From a0facb94be57122739ae30c07f8f3a7cc4bf1954 Mon Sep 17 00:00:00 2001 From: daedalus <44623501+ComfortablyCoding@users.noreply.github.com> Date: Fri, 29 May 2026 10:45:18 -0400 Subject: [PATCH 14/18] wordage --- content/guides/04.connect/8.json-queries-reference.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/content/guides/04.connect/8.json-queries-reference.md b/content/guides/04.connect/8.json-queries-reference.md index 87e7368d1..48fd07296 100644 --- a/content/guides/04.connect/8.json-queries-reference.md +++ b/content/guides/04.connect/8.json-queries-reference.md @@ -686,9 +686,9 @@ query { filter: { metadata: { _json: { - color: { _in: ["red", "blue"] } - brand: { _nnull: true } - description: { _contains: "premium" } + color: { _eq: "red" } + brand: { _in: ["BrandX", "BrandY"] } + level: { _gte: 3 } } } } @@ -853,7 +853,7 @@ query { filter: { _and: [ { metadata: { _json: { color: { _eq: "blue" } } } } - { metadata: { _json: { level: { _gte: 3 } } } } + { metadata: { _json: { size: { _gt: 10 } } } } ] } ) { From da1b9edb5d01bc49f70661ebd434e8e6e247bda4 Mon Sep 17 00:00:00 2001 From: daedalus <44623501+ComfortablyCoding@users.noreply.github.com> Date: Fri, 29 May 2026 10:54:58 -0400 Subject: [PATCH 15/18] nest under json nav --- .../1.quickstart.md} | 113 +-- .../2.advanced-querying.md} | 722 +++++++++--------- 2 files changed, 415 insertions(+), 420 deletions(-) rename content/guides/04.connect/{7.json-queries.md => 7.json/1.quickstart.md} (56%) rename content/guides/04.connect/{8.json-queries-reference.md => 7.json/2.advanced-querying.md} (61%) diff --git a/content/guides/04.connect/7.json-queries.md b/content/guides/04.connect/7.json/1.quickstart.md similarity index 56% rename from content/guides/04.connect/7.json-queries.md rename to content/guides/04.connect/7.json/1.quickstart.md index 90b56f3b1..4efce3ae5 100644 --- a/content/guides/04.connect/7.json-queries.md +++ b/content/guides/04.connect/7.json/1.quickstart.md @@ -1,5 +1,5 @@ --- -title: JSON Queries +title: Quickstart description: Quickstart for extracting and filtering values inside JSON fields with the json() function and _json filter operator. --- @@ -13,6 +13,7 @@ Both use the same path notation and work across REST, GraphQL, and the SDK. ## Using the `json()` function **Function Syntax:** + ``` json(field, path) ``` @@ -28,25 +29,25 @@ GET /items/articles?fields=id,title,json(metadata, color) ``` ```js [SDK] -import { createDirectus, rest, readItems } from '@directus/sdk'; -const directus = createDirectus('https://directus.example.com').with(rest()); +import { createDirectus, rest, readItems } from "@directus/sdk"; +const directus = createDirectus("https://directus.example.com").with(rest()); const result = await directus.request( - readItems('articles', { - fields: ['id', 'title', 'json(metadata, color)'], - }) + readItems("articles", { + fields: ["id", "title", "json(metadata, color)"], + }), ); ``` ```graphql [GraphQL] query { - articles { - id - title - metadata_func { - json(path: "color") - } - } + articles { + id + title + metadata_func { + json(path: "color") + } + } } ``` @@ -58,27 +59,27 @@ Response: ```json [REST / SDK] { - "data": [ - { - "id": 1, - "title": "An Article", - "metadata_color_json": "blue" - } - ] + "data": [ + { + "id": 1, + "title": "An Article", + "metadata_color_json": "blue" + } + ] } ``` ```json [GraphQL] { - "data": { - "articles": [ - { - "id": 1, - "title": "An Article", - "metadata_func": { "json": "blue" } - } - ] - } + "data": { + "articles": [ + { + "id": 1, + "title": "An Article", + "metadata_func": { "json": "blue" } + } + ] + } } ``` @@ -89,6 +90,7 @@ For REST and the SDK, the extracted value is returned under the alias `{field}_{ ## Filtering with `_json` **Operator Syntax:** + ``` { "field": { @@ -113,26 +115,26 @@ GET /items/articles ``` ```js [SDK] -import { createDirectus, rest, readItems } from '@directus/sdk'; -const directus = createDirectus('https://directus.example.com').with(rest()); +import { createDirectus, rest, readItems } from "@directus/sdk"; +const directus = createDirectus("https://directus.example.com").with(rest()); const result = await directus.request( - readItems('articles', { - filter: { - metadata: { - _json: { color: { _eq: 'blue' } }, - }, - }, - }) + readItems("articles", { + filter: { + metadata: { + _json: { color: { _eq: "blue" } }, + }, + }, + }), ); ``` ```graphql [GraphQL] query { - articles(filter: { metadata: { _json: { color: { _eq: "blue" } } } }) { - id - title - } + articles(filter: { metadata: { _json: { color: { _eq: "blue" } } } }) { + id + title + } } ``` @@ -142,30 +144,29 @@ Response: ```json { - "data": [ - { "id": 1, "title": "An Article" }, - { "id": 4, "title": "Another Article" } - ] + "data": [ + { "id": 1, "title": "An Article" }, + { "id": 4, "title": "Another Article" } + ] } ``` - The `_json` operator supports most filter operators, except geometric, regex, and relational ones, plus itself (`_json`, `_intersects`, `_intersects_bbox`, `_regex`, `_some`, and `_none`). -See the [reference](/guides/connect/json-queries-reference#supported-inner-operators) for more details. +See the [reference](/guides/connect/json/advanced-querying#supported-inner-operators) for more details. ## Path Notation Paths use dot notation for object keys and bracket notation for array indices. -| Pattern | Example | Meaning | -|---|---|---| -| `key` | `color` | Top-level key | -| `a.b.c` | `settings.theme.color` | Nested object keys | -| `[n]` | `tags[0]` | Array element at index `n` | -| `a[n].b` | `items[0].name` | Mixed object/array access | +| Pattern | Example | Meaning | +| -------- | ---------------------- | -------------------------- | +| `key` | `color` | Top-level key | +| `a.b.c` | `settings.theme.color` | Nested object keys | +| `[n]` | `tags[0]` | Array element at index `n` | +| `a[n].b` | `items[0].name` | Mixed object/array access | -Wildcards (`*`, `[*]`) among other special characters are not supported. See the [reference](/guides/connect/json-queries-reference#path-notation) for the full list. +Wildcards (`*`, `[*]`) among other special characters are not supported. See the [reference](/guides/connect/json/advanced-querying#path-notation) for the full list. ## More information -For advanced usage and expanded explanations, see [JSON Queries Reference](/guides/connect/json-queries-reference). +For advanced usage and expanded explanations, see [JSON Queries Reference](/guides/connect/json/advanced-querying). diff --git a/content/guides/04.connect/8.json-queries-reference.md b/content/guides/04.connect/7.json/2.advanced-querying.md similarity index 61% rename from content/guides/04.connect/8.json-queries-reference.md rename to content/guides/04.connect/7.json/2.advanced-querying.md index 48fd07296..6fd48ed49 100644 --- a/content/guides/04.connect/8.json-queries-reference.md +++ b/content/guides/04.connect/7.json/2.advanced-querying.md @@ -1,10 +1,9 @@ --- -title: JSON Queries Reference +title: Advanced Querying description: Complete reference for the json(field, path) function and _json filter operator, covering path notation, relational queries, GraphQL, the SDK, depth limits, and database-specific behavior. -navigation: false --- -This is the full reference for querying JSON fields in Directus. For a short introduction with basic syntax and examples, see [JSON Queries](/guides/connect/json-queries). +This is the full reference for querying JSON fields in Directus. For a short introduction with basic syntax and examples, see [JSON Queries](/guides/connect/json/quickstart). Directus provides two ways of working with JSON fields in queries: @@ -17,12 +16,12 @@ Both use the same path notation and work across REST, GraphQL, and the SDK. Paths use dot notation for object keys and bracket notation for array indices. -| Pattern | Example | Meaning | -|---|---|---| -| `key` | `color` | Top-level key | -| `a.b.c` | `settings.theme.color` | Nested keys | -| `[n]` | `tags[0]` | Array element at index `n` | -| `a[n].b` | `items[0].name` | Mixed object/array access | +| Pattern | Example | Meaning | +| -------- | ---------------------- | -------------------------- | +| `key` | `color` | Top-level key | +| `a.b.c` | `settings.theme.color` | Nested keys | +| `[n]` | `tags[0]` | Array element at index `n` | +| `a[n].b` | `items[0].name` | Mixed object/array access | **Examples:** @@ -35,14 +34,14 @@ json(data, [0]) → first element of a top-level array The following path syntaxes are **not supported** and return an error in both the function and filter operator: -| Expression | Example | -|---|---| -| Empty brackets (wildcard) | `items[]` | -| `[*]` wildcard | `items[*].name` | -| `*` glob | `items.*` | -| JSONPath predicates | `items[?(@.price > 10)]` | -| `@` current node | `@.name` | -| `$` root | `$.name` | +| Expression | Example | +| ------------------------- | ------------------------ | +| Empty brackets (wildcard) | `items[]` | +| `[*]` wildcard | `items[*].name` | +| `*` glob | `items.*` | +| JSONPath predicates | `items[?(@.price > 10)]` | +| `@` current node | `@.name` | +| `$` root | `$.name` | ### Object Keys with Special Characters @@ -54,7 +53,7 @@ The `json(field, path)` function extracts the value from the specified path in a ::callout{icon="material-symbols:warning-rounded" color="warning"} -**Not Supported in Filters** +**Not Supported in Filters** The `json(field, path)` function is not supported in the `filter` query parameter. For filtering JSON fields, use the [`_json` filter operator](#the-_json-filter-operator) instead. :: @@ -84,11 +83,11 @@ For REST and the SDK, extracted values are returned as additional fields on each Special characters in the path (`[`, `]`, `.`) are replaced with underscores. -| Request field | Response key | -|---|---| -| `json(metadata, color)` | `metadata_color_json` | +| Request field | Response key | +| ----------------------------------- | --------------------------------- | +| `json(metadata, color)` | `metadata_color_json` | | `json(metadata, settings.priority)` | `metadata_settings_priority_json` | -| `json(data, items[0].name)` | `data_items_0_name_json` | +| `json(data, items[0].name)` | `data_items_0_name_json` | In GraphQL, the extracted value is returned inside `{fieldName}_func.json`. When requesting multiple paths from the same field, use GraphQL field aliases to distinguish them. @@ -102,24 +101,24 @@ GET /items/articles?fields=id,title,json(metadata, color) ```graphql [GraphQL] query { - articles { - id - title - metadata_func { - json(path: "color") - } - } + articles { + id + title + metadata_func { + json(path: "color") + } + } } ``` ```js [SDK] -import { createDirectus, rest, readItems } from '@directus/sdk'; -const directus = createDirectus('https://directus.example.com').with(rest()); +import { createDirectus, rest, readItems } from "@directus/sdk"; +const directus = createDirectus("https://directus.example.com").with(rest()); const result = await directus.request( - readItems('articles', { - fields: ['id', 'title', 'json(metadata, color)'], - }) + readItems("articles", { + fields: ["id", "title", "json(metadata, color)"], + }), ); ``` @@ -131,27 +130,27 @@ Response: ```json [REST / SDK] { - "data": [ - { - "id": 1, - "title": "An Article", - "metadata_color_json": "blue" - } - ] + "data": [ + { + "id": 1, + "title": "An Article", + "metadata_color_json": "blue" + } + ] } ``` ```json [GraphQL] { - "data": { - "articles": [ - { - "id": 1, - "title": "An Article", - "metadata_func": { "json": "blue" } - } - ] - } + "data": { + "articles": [ + { + "id": 1, + "title": "An Article", + "metadata_func": { "json": "blue" } + } + ] + } } ``` @@ -169,30 +168,30 @@ GET /items/articles?fields=id,json(metadata, color),json(metadata, settings.them ```graphql [GraphQL] query { - articles { - id - metadata_func { - color: json(path: "color") - theme: json(path: "settings.theme") - firstTag: json(path: "tags[0]") - } - } + articles { + id + metadata_func { + color: json(path: "color") + theme: json(path: "settings.theme") + firstTag: json(path: "tags[0]") + } + } } ``` ```js [SDK] -import { createDirectus, rest, readItems } from '@directus/sdk'; -const directus = createDirectus('https://directus.example.com').with(rest()); +import { createDirectus, rest, readItems } from "@directus/sdk"; +const directus = createDirectus("https://directus.example.com").with(rest()); const result = await directus.request( - readItems('articles', { - fields: [ - 'id', - 'json(metadata, color)', - 'json(metadata, settings.theme)', - 'json(metadata, tags[0])', - ], - }) + readItems("articles", { + fields: [ + "id", + "json(metadata, color)", + "json(metadata, settings.theme)", + "json(metadata, tags[0])", + ], + }), ); ``` @@ -204,31 +203,31 @@ Response: ```json [REST / SDK] { - "data": [ - { - "id": 1, - "metadata_color_json": "blue", - "metadata_settings_theme_json": "dark", - "metadata_tags_0_json": "featured" - } - ] + "data": [ + { + "id": 1, + "metadata_color_json": "blue", + "metadata_settings_theme_json": "dark", + "metadata_tags_0_json": "featured" + } + ] } ``` ```json [GraphQL] { - "data": { - "articles": [ - { - "id": 1, - "metadata_func": { - "color": "blue", - "theme": "dark", - "firstTag": "featured" - } - } - ] - } + "data": { + "articles": [ + { + "id": 1, + "metadata_func": { + "color": "blue", + "theme": "dark", + "firstTag": "featured" + } + } + ] + } } ``` @@ -240,7 +239,7 @@ When the path points to an object or array rather than a scalar, the full value ::callout{icon="material-symbols:warning-rounded" color="warning"} -**Non-Scalar Paths in Sort and Filter** +**Non-Scalar Paths in Sort and Filter** Sorting or filtering by a path that resolves to an object or array can produce unexpected results. The database compares the serialized form, which depends on dialect-specific JSON ordering and formatting. Use paths that resolve to a scalar value (string, number, boolean) for reliable sorting and filtering. :: @@ -253,24 +252,24 @@ GET /items/articles?fields=id,json(metadata, dimensions),json(metadata, tags) ```graphql [GraphQL] query { - articles { - id - metadata_func { - dimensions: json(path: "dimensions") - tags: json(path: "tags") - } - } + articles { + id + metadata_func { + dimensions: json(path: "dimensions") + tags: json(path: "tags") + } + } } ``` ```js [SDK] -import { createDirectus, rest, readItems } from '@directus/sdk'; -const directus = createDirectus('https://directus.example.com').with(rest()); +import { createDirectus, rest, readItems } from "@directus/sdk"; +const directus = createDirectus("https://directus.example.com").with(rest()); const result = await directus.request( - readItems('articles', { - fields: ['id', 'json(metadata, dimensions)', 'json(metadata, tags)'], - }) + readItems("articles", { + fields: ["id", "json(metadata, dimensions)", "json(metadata, tags)"], + }), ); ``` @@ -282,29 +281,29 @@ Response: ```json [REST / SDK] { - "data": [ - { - "id": 1, - "metadata_dimensions_json": { "width": 100, "height": 50 }, - "metadata_tags_json": ["featured", "new"] - } - ] + "data": [ + { + "id": 1, + "metadata_dimensions_json": { "width": 100, "height": 50 }, + "metadata_tags_json": ["featured", "new"] + } + ] } ``` ```json [GraphQL] { - "data": { - "articles": [ - { - "id": 1, - "metadata_func": { - "dimensions": { "width": 100, "height": 50 }, - "tags": ["featured", "new"] - } - } - ] - } + "data": { + "articles": [ + { + "id": 1, + "metadata_func": { + "dimensions": { "width": 100, "height": 50 }, + "tags": ["featured", "new"] + } + } + ] + } } ``` @@ -328,27 +327,27 @@ GET /items/articles?fields=id,title,category_id.name,json(category_id.metadata, ```graphql [GraphQL] query { - articles { - id - title - category_id { - name - metadata_func { - color: json(path: "color") - } - } - } + articles { + id + title + category_id { + name + metadata_func { + color: json(path: "color") + } + } + } } ``` ```js [SDK] -import { createDirectus, rest, readItems } from '@directus/sdk'; -const directus = createDirectus('https://directus.example.com').with(rest()); +import { createDirectus, rest, readItems } from "@directus/sdk"; +const directus = createDirectus("https://directus.example.com").with(rest()); const result = await directus.request( - readItems('articles', { - fields: ['id', 'title', { category_id: ['name', 'json(metadata, color)'] }], - }) + readItems("articles", { + fields: ["id", "title", { category_id: ["name", "json(metadata, color)"] }], + }), ); ``` @@ -360,33 +359,33 @@ Response: ```json [REST / SDK] { - "data": [ - { - "id": 1, - "title": "An Article", - "category_id": { - "name": "News", - "metadata_color_json": "blue" - } - } - ] + "data": [ + { + "id": 1, + "title": "An Article", + "category_id": { + "name": "News", + "metadata_color_json": "blue" + } + } + ] } ``` ```json [GraphQL] { - "data": { - "articles": [ - { - "id": 1, - "title": "An Article", - "category_id": { - "name": "News", - "metadata_func": { "color": "blue" } - } - } - ] - } + "data": { + "articles": [ + { + "id": 1, + "title": "An Article", + "category_id": { + "name": "News", + "metadata_func": { "color": "blue" } + } + } + ] + } } ``` @@ -404,25 +403,25 @@ GET /items/articles/1?fields=id,json(comments.data, type) ```graphql [GraphQL] query { - articles_by_id(id: 1) { - id - comments { - data_func { - json(path: "type") - } - } - } + articles_by_id(id: 1) { + id + comments { + data_func { + json(path: "type") + } + } + } } ``` ```js [SDK] -import { createDirectus, rest, readItem } from '@directus/sdk'; -const directus = createDirectus('https://directus.example.com').with(rest()); +import { createDirectus, rest, readItem } from "@directus/sdk"; +const directus = createDirectus("https://directus.example.com").with(rest()); const result = await directus.request( - readItem('articles', 1, { - fields: ['id', { comments: ['json(data, type)'] }], - }) + readItem("articles", 1, { + fields: ["id", { comments: ["json(data, type)"] }], + }), ); ``` @@ -434,27 +433,27 @@ Response: ```json [REST / SDK] { - "data": { - "id": 1, - "comments": [ - { "data_type_json": "comment" }, - { "data_type_json": "review" } - ] - } + "data": { + "id": 1, + "comments": [ + { "data_type_json": "comment" }, + { "data_type_json": "review" } + ] + } } ``` ```json [GraphQL] { - "data": { - "articles_by_id": { - "id": 1, - "comments": [ - { "data_func": { "json": "comment" } }, - { "data_func": { "json": "review" } } - ] - } - } + "data": { + "articles_by_id": { + "id": 1, + "comments": [ + { "data_func": { "json": "comment" } }, + { "data_func": { "json": "review" } } + ] + } + } } ``` @@ -472,40 +471,40 @@ GET /items/shapes/1?fields=id,json(children.item:circles.metadata, color) ```graphql [GraphQL] query { - shapes_by_id(id: 1) { - id - children { - item { - ... on circles { - metadata_func { - json(path: "color") - } - } - } - } - } + shapes_by_id(id: 1) { + id + children { + item { + ... on circles { + metadata_func { + json(path: "color") + } + } + } + } + } } ``` ```js [SDK] -import { createDirectus, rest, readItem } from '@directus/sdk'; -const directus = createDirectus('https://directus.example.com').with(rest()); +import { createDirectus, rest, readItem } from "@directus/sdk"; +const directus = createDirectus("https://directus.example.com").with(rest()); const result = await directus.request( - readItem('shapes', 1, { - fields: [ - 'id', - { - children: [ - { - item: { - circles: ['json(metadata, color)'], - }, - }, - ], - }, - ], - }) + readItem("shapes", 1, { + fields: [ + "id", + { + children: [ + { + item: { + circles: ["json(metadata, color)"], + }, + }, + ], + }, + ], + }), ); ``` @@ -517,33 +516,33 @@ Response: ```json [REST / SDK] { - "data": { - "id": 1, - "children": [ - { - "item": { - "metadata_color_json": "red" - } - } - ] - } + "data": { + "id": 1, + "children": [ + { + "item": { + "metadata_color_json": "red" + } + } + ] + } } ``` ```json [GraphQL] { - "data": { - "shapes_by_id": { - "id": 1, - "children": [ - { - "item": { - "metadata_func": { "color": "red" } - } - } - ] - } - } + "data": { + "shapes_by_id": { + "id": 1, + "children": [ + { + "item": { + "metadata_func": { "color": "red" } + } + } + ] + } + } } ``` @@ -563,25 +562,27 @@ Exceeding either limit returns an error. The SDK enforces that the first argument of `json()` must be a `json`-typed field. Non-json fields produce a TypeScript error. The output alias is typed automatically as `JsonValue | null` with no cast needed. ```typescript -import { createDirectus, readItems, rest } from '@directus/sdk'; +import { createDirectus, readItems, rest } from "@directus/sdk"; interface Article { - id: number; - title: string; - metadata: 'json' | null; // type literal 'json' tells the SDK this is a json field + id: number; + title: string; + metadata: "json" | null; // type literal 'json' tells the SDK this is a json field } interface Schema { - articles: Article[]; + articles: Article[]; } -const client = createDirectus('https://directus.example.com').with(rest()); +const client = createDirectus("https://directus.example.com").with( + rest(), +); // valid: metadata is a json field; metadata_color_json is typed as JsonValue | null -readItems('articles', { fields: ['json(metadata, color)'] }); +readItems("articles", { fields: ["json(metadata, color)"] }); // type error: title is a string field, not json -readItems('articles', { fields: ['json(title, color)'] }); +readItems("articles", { fields: ["json(title, color)"] }); ``` The alias rule is `{field}_{path}_json` with `.`, `[`, and `]` replaced by `_`. For a relational field, the extracted alias appears typed on the related item (for example, `items[0].category_id.metadata_color_json`). @@ -614,12 +615,12 @@ In GraphQL, input-object keys must be valid identifiers, so paths containing dot `_json` supports most standard filter operators. The following operators are **not** supported: -| Category | Operators | -| ----------- | ---------------------------------- | -| JSON | `_json` | -| Geometric | `_intersects`, `_intersects_bbox` | -| Regex | `_regex` | -| Relational | `_some`, `_none` | +| Category | Operators | +| ---------- | --------------------------------- | +| JSON | `_json` | +| Geometric | `_intersects`, `_intersects_bbox` | +| Regex | `_regex` | +| Relational | `_some`, `_none` | ### Basic Example @@ -634,25 +635,25 @@ GET /items/articles ```graphql [GraphQL] query { - articles(filter: { metadata: { _json: { color: { _eq: "blue" } } } }) { - id - title - } + articles(filter: { metadata: { _json: { color: { _eq: "blue" } } } }) { + id + title + } } ``` ```js [SDK] -import { createDirectus, rest, readItems } from '@directus/sdk'; -const directus = createDirectus('https://directus.example.com').with(rest()); +import { createDirectus, rest, readItems } from "@directus/sdk"; +const directus = createDirectus("https://directus.example.com").with(rest()); const result = await directus.request( - readItems('articles', { - filter: { - metadata: { - _json: { color: { _eq: 'blue' } }, - }, - }, - }) + readItems("articles", { + filter: { + metadata: { + _json: { color: { _eq: "blue" } }, + }, + }, + }), ); ``` @@ -662,10 +663,10 @@ Response: ```json { - "data": [ - { "id": 1, "title": "An Article" }, - { "id": 4, "title": "Another Article" } - ] + "data": [ + { "id": 1, "title": "An Article" }, + { "id": 4, "title": "Another Article" } + ] } ``` @@ -682,36 +683,36 @@ GET /items/articles ```graphql [GraphQL] query { - articles( - filter: { - metadata: { - _json: { - color: { _eq: "red" } - brand: { _in: ["BrandX", "BrandY"] } - level: { _gte: 3 } - } - } - } - ) { - id - title - } + articles( + filter: { + metadata: { + _json: { + color: { _eq: "red" } + brand: { _in: ["BrandX", "BrandY"] } + level: { _gte: 3 } + } + } + } + ) { + id + title + } } ``` ```js [SDK] const result = await directus.request( - readItems('articles', { - filter: { - metadata: { - _json: { - color: { _eq: 'red' }, - brand: { _in: ['BrandX', 'BrandY'] }, - level: { _gte: 3 }, - }, - }, - }, - }) + readItems("articles", { + filter: { + metadata: { + _json: { + color: { _eq: "red" }, + brand: { _in: ["BrandX", "BrandY"] }, + level: { _gte: 3 }, + }, + }, + }, + }), ); ``` @@ -721,9 +722,7 @@ Response: ```json { - "data": [ - { "id": 7, "title": "Premium Red Item" } - ] + "data": [{ "id": 7, "title": "Premium Red Item" }] } ``` @@ -740,10 +739,10 @@ GET /items/articles ```graphql [GraphQL] query FilterByNestedPath($jsonFilter: JSON) { - articles(filter: { metadata: { _json: $jsonFilter } }) { - id - title - } + articles(filter: { metadata: { _json: $jsonFilter } }) { + id + title + } } # Variables: @@ -758,16 +757,16 @@ query FilterByNestedPath($jsonFilter: JSON) { ```js [SDK] const result = await directus.request( - readItems('articles', { - filter: { - metadata: { - _json: { - 'settings.theme': { _eq: 'dark' }, - 'tags[0]': { _eq: 'electronics' }, - }, - }, - }, - }) + readItems("articles", { + filter: { + metadata: { + _json: { + "settings.theme": { _eq: "dark" }, + "tags[0]": { _eq: "electronics" }, + }, + }, + }, + }), ); ``` @@ -777,9 +776,7 @@ Response: ```json { - "data": [ - { "id": 2, "title": "Dark Mode Electronics Review" } - ] + "data": [{ "id": 2, "title": "Dark Mode Electronics Review" }] } ``` @@ -796,27 +793,29 @@ GET /items/articles ```graphql [GraphQL] query { - articles(filter: { category_id: { metadata: { _json: { color: { _eq: "blue" } } } } }) { - id - title - category_id { - name - } - } + articles( + filter: { category_id: { metadata: { _json: { color: { _eq: "blue" } } } } } + ) { + id + title + category_id { + name + } + } } ``` ```js [SDK] const result = await directus.request( - readItems('articles', { - filter: { - category_id: { - metadata: { - _json: { color: { _eq: 'blue' } }, - }, - }, - }, - }) + readItems("articles", { + filter: { + category_id: { + metadata: { + _json: { color: { _eq: "blue" } }, + }, + }, + }, + }), ); ``` @@ -826,13 +825,13 @@ Response: ```json { - "data": [ - { - "id": 1, - "title": "An Article", - "category_id": { "name": "News" } - } - ] + "data": [ + { + "id": 1, + "title": "An Article", + "category_id": { "name": "News" } + } + ] } ``` @@ -849,30 +848,30 @@ GET /items/articles ```graphql [GraphQL] query { - articles( - filter: { - _and: [ - { metadata: { _json: { color: { _eq: "blue" } } } } - { metadata: { _json: { size: { _gt: 10 } } } } - ] - } - ) { - id - title - } + articles( + filter: { + _and: [ + { metadata: { _json: { color: { _eq: "blue" } } } } + { metadata: { _json: { size: { _gt: 10 } } } } + ] + } + ) { + id + title + } } ``` ```js [SDK] const result = await directus.request( - readItems('articles', { - filter: { - _and: [ - { metadata: { _json: { color: { _eq: 'blue' } } } }, - { metadata: { _json: { size: { _gt: 10 } } } }, - ], - }, - }) + readItems("articles", { + filter: { + _and: [ + { metadata: { _json: { color: { _eq: "blue" } } } }, + { metadata: { _json: { size: { _gt: 10 } } } }, + ], + }, + }), ); ``` @@ -882,9 +881,7 @@ Response: ```json { - "data": [ - { "id": 3, "title": "Large Blue Article" } - ] + "data": [{ "id": 3, "title": "Large Blue Article" }] } ``` @@ -892,14 +889,11 @@ You can also group conditions inside a single `_json` value using `_and` or `_or ```json { - "metadata": { - "_json": { - "_and": [ - { "color": { "_eq": "blue" } }, - { "size": { "_gt": 10 } } - ] - } - } + "metadata": { + "_json": { + "_and": [{ "color": { "_eq": "blue" } }, { "size": { "_gt": 10 } }] + } + } } ``` From a42390ff5ebcdb79d5af02676fee3662b6dd5643 Mon Sep 17 00:00:00 2001 From: daedalus <44623501+ComfortablyCoding@users.noreply.github.com> Date: Fri, 29 May 2026 11:10:42 -0400 Subject: [PATCH 16/18] quickstart wordage pass 2 --- .../guides/04.connect/7.json/1.quickstart.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/content/guides/04.connect/7.json/1.quickstart.md b/content/guides/04.connect/7.json/1.quickstart.md index 4efce3ae5..88ab204f9 100644 --- a/content/guides/04.connect/7.json/1.quickstart.md +++ b/content/guides/04.connect/7.json/1.quickstart.md @@ -1,12 +1,13 @@ --- +stableId: 6f7ab223-9812-4f22-b386-bab6189f6639 title: Quickstart description: Quickstart for extracting and filtering values inside JSON fields with the json() function and _json filter operator. --- Directus provides two ways of working with JSON fields in queries: -- **`json(field, path)`**: A selection function to extract a value from a JSON document. It can be used in the `fields`, `sort`, and `alias` parameters. -- **`_json`**: A filter operator that lets you filter records based on values within a JSON document. It can be used as an operator in the `filter` parameter. +- [`json(field, path)`](/guides/connect/json/quickstart#using-the-json-function): A selection function to extract a value from a JSON document. It can be used in the `fields`, `sort`, and `alias` parameters. +- [`_json`](/guides/connect/json/quickstart#filtering-with-_json): A filter operator that lets you filter records based on values within a JSON document. It can be used within the `filter` parameter. Both use the same path notation and work across REST, GraphQL, and the SDK. @@ -85,7 +86,7 @@ Response: :: -For REST and the SDK, the extracted value is returned under the alias `{field}_{path}_json`, with `.`, `[`, and `]` replaced by underscores. +For REST and SDK, the extracted value is returned under the alias `{field}_{path}_json` with `.`, `[`, and `]` replaced by underscores. ## Filtering with `_json` @@ -151,8 +152,7 @@ Response: } ``` -The `_json` operator supports most filter operators, except geometric, regex, and relational ones, plus itself (`_json`, `_intersects`, `_intersects_bbox`, `_regex`, `_some`, and `_none`). -See the [reference](/guides/connect/json/advanced-querying#supported-inner-operators) for more details. +Refer to the [Supported Inner Operations](/guides/connect/json/advanced-querying#supported-inner-operators) section for a list of available operators within the `_json` operator. ## Path Notation @@ -160,13 +160,13 @@ Paths use dot notation for object keys and bracket notation for array indices. | Pattern | Example | Meaning | | -------- | ---------------------- | -------------------------- | -| `key` | `color` | Top-level key | -| `a.b.c` | `settings.theme.color` | Nested object keys | +| `key` | `color` | Top-level object key | +| `a.b.c` | `settings.theme.color` | Nested object key | | `[n]` | `tags[0]` | Array element at index `n` | | `a[n].b` | `items[0].name` | Mixed object/array access | -Wildcards (`*`, `[*]`) among other special characters are not supported. See the [reference](/guides/connect/json/advanced-querying#path-notation) for the full list. +Wildcards (`*`, `[*]`) and other special characters are not currently supported. See the [advanced usage path notation](/guides/connect/json/advanced-querying#path-notation) section for a complete list of unsupported characters. ## More information -For advanced usage and expanded explanations, see [JSON Queries Reference](/guides/connect/json/advanced-querying). +For advanced usage details and additional examples, see [Advanced JSON Querying](/guides/connect/json/advanced-querying). From 17165d1acedd3789389eb9b2e35197048e9a95e3 Mon Sep 17 00:00:00 2001 From: daedalus <44623501+ComfortablyCoding@users.noreply.github.com> Date: Fri, 29 May 2026 12:58:01 -0400 Subject: [PATCH 17/18] advanced usage wordage update --- .../guides/04.connect/7.json/1.quickstart.md | 2 +- .../04.connect/7.json/2.advanced-querying.md | 141 ++++++++++-------- 2 files changed, 80 insertions(+), 63 deletions(-) diff --git a/content/guides/04.connect/7.json/1.quickstart.md b/content/guides/04.connect/7.json/1.quickstart.md index 88ab204f9..72c03bd39 100644 --- a/content/guides/04.connect/7.json/1.quickstart.md +++ b/content/guides/04.connect/7.json/1.quickstart.md @@ -165,7 +165,7 @@ Paths use dot notation for object keys and bracket notation for array indices. | `[n]` | `tags[0]` | Array element at index `n` | | `a[n].b` | `items[0].name` | Mixed object/array access | -Wildcards (`*`, `[*]`) and other special characters are not currently supported. See the [advanced usage path notation](/guides/connect/json/advanced-querying#path-notation) section for a complete list of unsupported characters. +Wildcards (`*`, `[*]`) and other special characters are not currently supported. See the [Unsupported Path Expressions](/guides/connect/json/advanced-querying#unsupported-path-expressions) section for a complete list of unsupported characters. ## More information diff --git a/content/guides/04.connect/7.json/2.advanced-querying.md b/content/guides/04.connect/7.json/2.advanced-querying.md index 6fd48ed49..d50c01de0 100644 --- a/content/guides/04.connect/7.json/2.advanced-querying.md +++ b/content/guides/04.connect/7.json/2.advanced-querying.md @@ -1,16 +1,10 @@ --- +stableId: 26102b15-bee8-4e86-8f1f-4d74f1ea9cb0 title: Advanced Querying -description: Complete reference for the json(field, path) function and _json filter operator, covering path notation, relational queries, GraphQL, the SDK, depth limits, and database-specific behavior. +description: Advanced JSON querying for the `json(field, path)` function and `_json` filter operator, including path notation, relational queries, GraphQL support, SDK usage, depth limits, and database-specific behavior. --- -This is the full reference for querying JSON fields in Directus. For a short introduction with basic syntax and examples, see [JSON Queries](/guides/connect/json/quickstart). - -Directus provides two ways of working with JSON fields in queries: - -- The **`json(field, path)` function** extracts a specific value from a JSON document. It can be used in the `fields`, `sort`, and `alias` query parameters. -- The **`_json` filter operator** filters items by values inside a JSON document without loading the full document. Use it in the `filter` query parameter. - -Both use the same path notation and work across REST, GraphQL, and the SDK. +This page covers advanced JSON querying in Directus. For a brief introduction with basic syntax and examples, see the [quickstart](/guides/connect/json/quickstart). ## Path Notation @@ -18,21 +12,36 @@ Paths use dot notation for object keys and bracket notation for array indices. | Pattern | Example | Meaning | | -------- | ---------------------- | -------------------------- | -| `key` | `color` | Top-level key | -| `a.b.c` | `settings.theme.color` | Nested keys | +| `key` | `color` | Top-level object key | +| `a.b.c` | `settings.theme.color` | Nested object key | | `[n]` | `tags[0]` | Array element at index `n` | | `a[n].b` | `items[0].name` | Mixed object/array access | **Examples:** +::code-group + +```[Field Selection] +json(metadata, settings.theme) ``` -json(metadata, color) → top-level key -json(metadata, settings.theme) → nested object -json(data, items[0].name) → array element property -json(data, [0]) → first element of a top-level array + +```[Filtering] +{ + "metadata": { + "_json": { + "settings.theme": { + "_eq":"blue" + } + } + } +} ``` -The following path syntaxes are **not supported** and return an error in both the function and filter operator: +:: + +### Unsupported Path Expressions + +The following path syntaxes are **not supported** and and will result in an error if used | Expression | Example | | ------------------------- | ------------------------ | @@ -43,19 +52,17 @@ The following path syntaxes are **not supported** and return an error in both th | `@` current node | `@.name` | | `$` root | `$.name` | -### Object Keys with Special Characters +### Non-Alphanumeric Characters in Object Keys -The path syntax uses `.` as a separator between key segments and has no escape mechanism. Object keys that contain dots, spaces, or other special characters cannot be reached. For example, a key `"first.name"` is interpreted as nested access to key `first`, then key `name`. +The path syntax uses `.` to separate key segments and does not provide an escape mechanism. As a result, object keys that contain dots, spaces, or other special characters cannot be accessed. For example, the key `"first.name"` is interpreted as access to the nested key `name` inside the key `first`. ## The `json(field, path)` Function -The `json(field, path)` function extracts the value from the specified path in a JSON field. It can be used anywhere a field reference is accepted, including the `fields`, `sort`, and `alias` query parameters. +The `json(field, path)` function retrieves the value at the specified path within a JSON document. It can be used wherever a field reference is accepted, including the `fields`, `sort`, and `alias` query parameters. ::callout{icon="material-symbols:warning-rounded" color="warning"} - **Not Supported in Filters** -The `json(field, path)` function is not supported in the `filter` query parameter. For filtering JSON fields, use the [`_json` filter operator](#the-_json-filter-operator) instead. - +The `json(field, path)` function is not supported in the `filter` query parameter. For filtering JSON fields, use the [`_json` filter operator](#the-_json-filter-operator). :: ### Syntax @@ -64,24 +71,28 @@ The `json(field, path)` function is not supported in the `filter` query paramete json(field, path) ``` -- **`field`** is the name of a JSON column in the collection (or a relational path to one). -- **`path`** is a dot-and-bracket notation path to the value you want to extract from within the JSON document. +- `field` (**required**): The name of a JSON column in the collection, or a relational path leading to one. +- `path` (**required**): A dot-and-bracket notation path used to extract a specific value from within the JSON document. -Both arguments are required and separated by a comma. - -In GraphQL, each `json`-typed field exposes a `json(path: String!)` sub-field inside `{fieldName}_func`. The `path` argument is required. The return type is `JSON` (any scalar, object, or array). `{fieldName}_func` already exists for the `count` sub-field; `json` sits alongside it in the same selection. +::callout{icon="material-symbols:info-outline" color="info"} +In GraphQL, each `json` type field exposes a `json(path: String!)` sub-field within `{fieldName}_func` which should be used instead. The return type is `JSON`, which can be a scalar, object, or array. +:: -The SDK accepts `json(fieldName, path)` strings in the `fields` array. The first argument is constrained by TypeScript to fields typed as `json` in your schema. An invalid field name produces a type error. The SDK computes the response alias type automatically from the literal string, so extracted values are fully typed. +::callout{icon="material-symbols:info-outline" color="info"} +The SDK supports a type safe `json(field, path)` expression within its `fields` array, see [SDK Type Safety](#sdk-type-safety) for more deatils. +:: ### Response Format -For REST and the SDK, extracted values are returned as additional fields on each item using auto-generated aliases. The alias follows the pattern: +For REST and the SDK, extracted values are returned as additional fields on each item using auto-generated aliases. + +The alias follows the pattern: ``` {field}_{path}_json ``` -Special characters in the path (`[`, `]`, `.`) are replaced with underscores. +Path segments are normalized by replacing special characters (e.g. `[`, `]`, `.`) with underscores. | Request field | Response key | | ----------------------------------- | --------------------------------- | @@ -89,7 +100,9 @@ Special characters in the path (`[`, `]`, `.`) are replaced with underscores. | `json(metadata, settings.priority)` | `metadata_settings_priority_json` | | `json(data, items[0].name)` | `data_items_0_name_json` | -In GraphQL, the extracted value is returned inside `{fieldName}_func.json`. When requesting multiple paths from the same field, use GraphQL field aliases to distinguish them. +::callout{icon="material-symbols:warning-rounded" color="warning"} +In GraphQL, the extracted value is returned under `{fieldName}_func.json`. When requesting multiple paths for the same field, use GraphQL field aliases to distinguish them. +:: ### Basic Example @@ -158,7 +171,7 @@ Response: ### Multiple Paths -Extract multiple values from the same JSON field in a single request. In GraphQL, use field aliases on the `json` sub-field to distinguish each extraction. +Extract multiple values from a single JSON field in one request. In GraphQL, use field aliases on the `json` sub-field to differentiate each extracted value. ::code-group @@ -311,13 +324,13 @@ Response: ### Relational Queries -`json(field, path)` can traverse relational fields to extract JSON values from related items. The relational path goes inside the first argument, before the JSON field name. +`json(field, path)` can traverse relational fields to extract JSON values from related items. The relational path is included in the first argument, before the JSON field name. #### Many-to-One (M2O) Syntax: `json(relation.json_field, path)` -The extracted value is returned nested under the relational key in the response, alongside any other requested fields from that relation. Multiple `json(field, path)` extractions from the same relation are grouped under the same relational key. +The extracted value is returned nested under the relational key in the response, alongside other requested fields from the same relation. Multiple `json(field, path)` extractions in the same relation are grouped under the same relational key. ::code-group @@ -393,6 +406,8 @@ Response: #### One-to-Many (O2M) +Syntax: `json(relation.json_field, path)` + For O2M relations, each related item returns its own extracted value. The response contains an array of objects, each with the extracted key. ::code-group @@ -461,7 +476,9 @@ Response: #### Many-to-Any (M2A) -For M2A relations, use the standard Directus collection scope syntax inside the first argument: `json(relation.item:collection_name.json_field, path)` +Syntax: `json(relation.item:collection_name.json_field, path)` + +M2A relations, use the standard Directus collection scope syntax inside the first argument. ::code-group @@ -550,16 +567,20 @@ Response: ### Depth Limits -`json(field, path)` enforces two separate depth limits, calculated independently: +`json(field, path)` enforces two independent depth limits: -- **Relational depth** (`MAX_RELATIONAL_DEPTH`, default `10`) limits how deep the relational path in the first argument can go. `json(category_id.metadata, a.b.c.d.e)` has a relational depth of 2 (`category_id` + `metadata`), regardless of the JSON path length. -- **Path depth** (`MAX_JSON_QUERY_DEPTH`, default `10`) limits how many segments the JSON path itself can contain. `json(category_id.metadata, a[0].c.d.e.f.g.h.i.j)` has a path depth of 10 and is allowed by default; one more segment exceeds the limit. +- **Relational depth** (`MAX_RELATIONAL_DEPTH`, default `10`): Limits how deeply relational selections can go in the `field` argument. For example, `json(category_id.metadata, a.b.c.d.e)` has a relational depth of 2 (`category_id` + `metadata`), regardless of the JSON path length. +- **Path depth** (`MAX_JSON_QUERY_DEPTH`, default `10`): Limits the number of segments allowed in the `path` argument. For example, `json(category_id.metadata, a[0].c.d.e.f.g.h.i.j)` has a path depth of 10 and is allowed by default; adding one more segment would exceed the limit. -Exceeding either limit returns an error. +::callout{icon="material-symbols:warning-rounded" color="warning"} +Exceeding either of these limits will result in an error. +:: ### SDK Type Safety -The SDK enforces that the first argument of `json()` must be a `json`-typed field. Non-json fields produce a TypeScript error. The output alias is typed automatically as `JsonValue | null` with no cast needed. +The SDK enforces that the `field` argument must be a `json` typed field from your schema, using a non-json field will result in a TypeScript error. The output alias is automatically typed as `JsonValue | null`, with no casting required. + +Within the fields array, the SDK also provide partial autocomplete for the `json()` expression. For each `json` typed field in your schema, the IDE offers `json(fieldName, ` as a completion, positioning the cursor ready for the path argument. This works via TypeScript's template-literal completion (TypeScript >= 4.7). The path argument is a free string with no completion hints. ```typescript import { createDirectus, readItems, rest } from "@directus/sdk"; @@ -585,26 +606,22 @@ readItems("articles", { fields: ["json(metadata, color)"] }); readItems("articles", { fields: ["json(title, color)"] }); ``` -The alias rule is `{field}_{path}_json` with `.`, `[`, and `]` replaced by `_`. For a relational field, the extracted alias appears typed on the related item (for example, `items[0].category_id.metadata_color_json`). +The alias rule follows the expected REST [response format](#response-format). For a relational field, the extracted alias appears typed on the related item (e.g. `items[0].category_id.metadata_color_json`). ::callout{icon="material-symbols:info-outline"} -**Alias Typing Requires Literal Field Arrays** - -Alias typing only works when the `fields` array is an inline literal or typed `as const`. If the array is built dynamically at runtime, TypeScript widens it to `string[]` and the aliases are not present in the inferred return type. +**Alias Typing Requires Literal Field Arrays** Alias typing only works when the `fields` array is an inline literal or typed `as const`. If the array is built dynamically at runtime, TypeScript widens it to `string[]` and the aliases are not present in the inferred return type. :: -When typing inside the `fields` array, the SDK provides partial autocomplete for the `json()` function. For each `json`-typed field in your schema, the IDE offers `json(fieldName, ` as a completion, positioning the cursor ready for the path argument. This works via TypeScript's template-literal completion (TypeScript >= 4.7). Only `json`-typed fields appear as suggestions; the path argument is a free string with no completion hints. - ## The `_json` Filter Operator The `_json` operator filters items by values inside a JSON field. It accepts an object mapping JSON paths to standard filter operators, letting you compare specific keys or array elements without loading the full document. -`_json` is only valid on `json`-typed fields. +::callout{icon="material-symbols:warning-rounded" color="warning"} +`_json` is only valid on `json` typed fields. +:: ### Syntax -The `_json` value is an object where each key is a JSON path and each value is a standard filter operator object. - ``` { "field": { "_json": { "path": { "_operator": value } } } } ``` @@ -613,7 +630,7 @@ In GraphQL, input-object keys must be valid identifiers, so paths containing dot ### Supported Inner Operators -`_json` supports most standard filter operators. The following operators are **not** supported: +The `_json` operator supports all standard filter operators **except** the following: | Category | Operators | | ---------- | --------------------------------- | @@ -782,7 +799,7 @@ Response: ### Relational JSON Filtering -`_json` nests under relational keys the same way other filters do. To filter on a JSON field belonging to a related item, nest `_json` under the relation name. +`_json` is nested under relational keys in the same way as other filters. To filter a JSON field on a related item, place `_json` under the relevant relation name. ::code-group @@ -837,7 +854,7 @@ Response: ### Combining Multiple Conditions -Combine multiple `_json` filters at the top level with `_and` or `_or`. +Combine multiple `_json` filters at the top level using `_and` or `_or`. ::code-group @@ -885,7 +902,7 @@ Response: } ``` -You can also group conditions inside a single `_json` value using `_and` or `_or`: +Conditions can also be grouped within the `_json` operator using `_and` or `_or`: ```json { @@ -899,22 +916,22 @@ You can also group conditions inside a single `_json` value using `_and` or `_or ### Dynamic Variables -Dynamic filter variables like `$CURRENT_USER` and `$NOW` work inside `_json` inner values. They are resolved before the filter runs, so they apply in permission rules and regular queries. +Dynamic filter variables (e.g. `$CURRENT_USER`, `$NOW` etc) are supported within `_json` values. These variables are resolved before the filter is executed, allowing them to be used in permission rules and standard queries. ## Database-Specific Notes -**PostgreSQL** +### PostgreSQL -PostgreSQL extracts JSON scalars as `text`. For `_json` numeric comparisons, Directus automatically casts to a numeric type when the filter value is a number or an array of numbers, so `_gt`, `_lt`, `_between`, and related operators work correctly. If you supply a numeric comparison with a string value (for example `{"version":{"_gt":"9"}}`), the comparison remains lexicographic. Use a numeric literal to get numeric comparison. +PostgreSQL returns JSON scalar values as `text`. For numeric comparisons in `_json`, Directus automatically casts values to a numeric type when the filter input is a number or number array, ensuring operators (e.g. `_gt`, `_lt`, `_between` etc) work as expected. If an expected numeric comparison is set with a string value (e.g. `{"version":{"_gt":"9"}}`), the comparison is instead performed lexicographically. Use numeric literals to ensure numeric comparison. -**SQLite** +### SQLite -SQLite can return `0`/`1` instead of boolean values when the path resolves to a boolean. +SQLite will return `0` / `1` instead of boolean values when the resolved path is a boolean. -**MSSQL** +### MSSQL -Always returns scalar values as **strings (`NVARCHAR`)**, even when the original JSON value is a number or boolean. For example, a JSON integer `42` is returned as the string `"42"`. Your application should perform type coercion as needed. +Scalar values are always returned as **strings (`NVARCHAR`)**, even if the original JSON value is a number or boolean. For example, a JSON integer `42` is returned as `"42"`. Applications should perform any type coercion as needed. -**Oracle** +### Oracle -Similar to MSSQL, Oracle returns scalar values as **strings**, regardless of the original JSON type (number, boolean, etc.). A JSON number `3.14` is returned as `"3.14"`. +Like MSSQL, Oracle returns scalar values as **strings**, regardless of the original JSON type being a number or boolean. For example, a JSON number `3.14` is returned as `"3.14"`. From 0004851b339e69b439a5b5a775ef5955b7a2f425 Mon Sep 17 00:00:00 2001 From: daedalus <44623501+ComfortablyCoding@users.noreply.github.com> Date: Fri, 29 May 2026 13:16:18 -0400 Subject: [PATCH 18/18] update stale links --- content/guides/04.connect/2.filter-rules.md | 2 +- content/guides/04.connect/3.query-parameters.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/content/guides/04.connect/2.filter-rules.md b/content/guides/04.connect/2.filter-rules.md index f8bf71159..08610d737 100644 --- a/content/guides/04.connect/2.filter-rules.md +++ b/content/guides/04.connect/2.filter-rules.md @@ -49,7 +49,7 @@ Filters are used in permissions, validations, and automations, as well as throug [2] Only available on geometry fields.
[3] Only available in validation permissions.
[4] Only available on One to Many relationship fields.
-[5] Only available on JSON fields. See [JSON Queries](/guides/connect/json-queries) for usage and examples. +[5] Only available on JSON fields, see the [JSON Querying Quickstart](/guides/connect/json/quickstart) for usage and examples. ## Filter Syntax diff --git a/content/guides/04.connect/3.query-parameters.md b/content/guides/04.connect/3.query-parameters.md index e4492f984..4169ab9a5 100644 --- a/content/guides/04.connect/3.query-parameters.md +++ b/content/guides/04.connect/3.query-parameters.md @@ -668,9 +668,9 @@ const result = await directus.request( ### The JSON Function -Extract a specific value from a JSON field and return it as a separate field in the response. See [Using the `json(field, path)` function](/guides/connect/json-queries#using-the-json-function) for full path syntax and examples. +Extract a specific value from a JSON field and return it as a separate field in the response. See [Using the `json(field, path)` function](/guides/connect/json/quickstart#using-the-json-function) for full path syntax and examples. -This function cannot be used in `filter`. To filter on JSON values, use the [`_json` filter operator](/guides/connect/json-queries#filtering-with-_json) instead. +This function cannot be used in `filter`. To filter on JSON values, use the [`_json` filter operator](/guides/connect/json/quickstart#filtering-with-_json) instead. ## Backlink