diff --git a/content/_partials/json-function.md b/content/_partials/json-function.md
deleted file mode 100644
index 37e2bb782..000000000
--- a/content/_partials/json-function.md
+++ /dev/null
@@ -1,215 +0,0 @@
-## The `json(field, path)` 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.
-
-
-::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.
-
-::
-
-### 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)
-```
-
-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"`.
diff --git a/content/_partials/query-functions.md b/content/_partials/query-functions.md
index 351d3a2ad..434d897f5 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/time/timestamp field |
| `second` | Extract the second from a datetime/time/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 a32268d86..08610d737 100644
--- a/content/guides/04.connect/2.filter-rules.md
+++ b/content/guides/04.connect/2.filter-rules.md
@@ -36,6 +36,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 |
@@ -47,7 +48,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 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 a27e30616..4169ab9a5 100644
--- a/content/guides/04.connect/3.query-parameters.md
+++ b/content/guides/04.connect/3.query-parameters.md
@@ -8,11 +8,12 @@ Most Directus API endpoints can use global query parameters to alter the data th
## Fields
-Specify which fields are returned. This parameter also supports dot notation to request nested relational fields, and wildcards (*) to include all fields at a specific depth.
+Specify which fields are returned. This parameter also supports dot notation to request nested relational fields, and wildcards (\*) to include all fields at a specific depth.
::code-group
+
```http [REST]
GET /items/posts
?fields=first_name,last_name,avatar.description
@@ -24,20 +25,21 @@ Use native GraphQL queries.
```json [SDK]
{
- "fields": ["first_name", "last_name", { "avatar": ["description"] }]
+ "fields": ["first_name", "last_name", { "avatar": ["description"] }]
}
```
+
::
::callout{icon="material-symbols:info-outline"}
**Examples**
-| Value | Description |
+| Value | Description |
| ---------------------- | ------------------------------------------------------------------ |
-| `first_name,last_name` | Return only the `first_name` and `last_name` fields. |
-| `title,author.name` | Return `title` and the related `author` item's `name` field. |
-| `*` | Return all fields. |
-| `*.*` | Return all fields and all immediately related fields. |
-| `*,images.*` | Return all fields and all fields within the `images` relationship. |
+| `first_name,last_name` | Return only the `first_name` and `last_name` fields. |
+| `title,author.name` | Return `title` and the related `author` item's `name` field. |
+| `*` | Return all fields. |
+| `*.*` | Return all fields and all immediately related fields. |
+| `*,images.*` | Return all fields and all fields within the `images` relationship. |
::
::callout{icon="material-symbols:info-outline"}
@@ -53,17 +55,19 @@ As Many to Any (M2A) fields have nested data from multiple collections, you are
**Example**
In an `posts` collection there is a Many to Any field called `sections` that points to `headings`, `paragraphs`, and `videos`. Different fields should be fetched from each related collection.
- ::code-group
- ```http [REST]
- GET /items/posts
- ?fields[]=title
- &fields[]=sections.item:headings.title
- &fields[]=sections.item:headings.level
- &fields[]=sections.item:paragraphs.body
- &fields[]=sections.item:videos.source
- ```
+ ::code-group
+ ```http [REST]
+
+GET /items/posts
+?fields[]=title
+&fields[]=sections.item:headings.title
+&fields[]=sections.item:headings.level
+&fields[]=sections.item:paragraphs.body
+&fields[]=sections.item:videos.source
+
+`````
- ```graphql [GraphQL]
+````graphql [GraphQL]
# Use can use native GraphQL Union types.
query {
@@ -127,32 +131,33 @@ GET /items/posts
GET /items/posts
?filter={ "title": { "_eq": "Hello" }}
-```
+`````
```graphql [GraphQL]
query {
- posts(filter: { title: { _eq: "Hello" } }) {
- id
- }
+ posts(filter: { title: { _eq: "Hello" } }) {
+ id
+ }
}
# Attribute names in GraphQL cannot contain the `:` character. If you are filtering Many to Any fields, you will need to replace it with a double underscore.
```
```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('posts', {
- filter: {
- title: {
- _eq: 'Hello',
- },
- },
- })
+ readItems("posts", {
+ filter: {
+ title: {
+ _eq: "Hello",
+ },
+ },
+ }),
);
```
+
::
## Search
@@ -168,22 +173,23 @@ GET /items/posts
```graphql [GraphQL]
query {
- posts(search: "Directus") {
- id
- }
+ posts(search: "Directus") {
+ id
+ }
}
```
```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('posts', {
- search: 'Directus',
- })
+ readItems("posts", {
+ search: "Directus",
+ }),
);
```
+
::
## Sort
@@ -193,6 +199,7 @@ const result = await directus.request(
What fields to sort results by. Sorting defaults to ascending, but appending a `-` will reverse this. Fields are prioritized by the order in the parameter. The dot notation is used to sort with values of related fields.
::code-group
+
```http [REST]
GET /items/posts
?sort=sort,-date_created,author.name
@@ -200,22 +207,23 @@ GET /items/posts
```graphql [GraphQL]
query {
- posts(sort: ["sort", "-date_created", "author.name"]) {
- id
- }
+ posts(sort: ["sort", "-date_created", "author.name"]) {
+ id
+ }
}
```
```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('posts', {
- sort: ['sort', '-date_created', 'author.name'],
- })
+ readItems("posts", {
+ sort: ["sort", "-date_created", "author.name"],
+ }),
);
```
+
::
## Limit
@@ -223,6 +231,7 @@ const result = await directus.request(
Set the maximum number of items that will be returned. The default limit is set to `100`. `-1` will return all items.
::code-group
+
```http [REST]
GET /items/posts
?limit=50
@@ -230,22 +239,23 @@ GET /items/posts
```graphql [GraphQL]
query {
- posts(limit: 50) {
- id
- }
+ posts(limit: 50) {
+ id
+ }
}
```
```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('posts', {
- limit: 50,
- })
+ readItems("posts", {
+ limit: 50,
+ }),
);
```
+
::
::callout{icon="material-symbols:info-outline"}
@@ -260,6 +270,7 @@ The maximum number of items that can be requested on the API can be configured u
Skip the specified number of items in the response. This parameter can be used for pagination.
::code-group
+
```http [REST]
GET /items/posts
?offset=100
@@ -267,22 +278,23 @@ GET /items/posts
```graphql [GraphQL]
query {
- posts(offset: 100) {
- id
- }
+ posts(offset: 100) {
+ id
+ }
}
```
```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('posts', {
- offset: 100,
- })
+ readItems("posts", {
+ offset: 100,
+ }),
);
```
+
::
## Page
@@ -290,6 +302,7 @@ const result = await directus.request(
An alternative to `offset`. Returned values are the value of `limit` multiplied by `page`. The first page is `1`.
::code-group
+
```http [REST]
GET /items/posts
?page=2
@@ -297,22 +310,23 @@ GET /items/posts
```graphql [GraphQL]
query {
- posts(page: 2) {
- id
- }
+ posts(page: 2) {
+ id
+ }
}
```
```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('posts', {
- page: 2,
- })
+ readItems("posts", {
+ page: 2,
+ }),
);
```
+
::
## Aggregate
@@ -332,6 +346,7 @@ Aggregate functions allow you to perform calculations on a set of values, return
| `countAll` | Equivalent to `?aggregate[count]=*` (GraphQL only) |
::code-group
+
```http [REST]
GET /items/posts
?aggregate[count]=*
@@ -339,22 +354,23 @@ GET /items/posts
```graphql [GraphQL]
query {
- posts_aggregated {
- countAll
- }
+ posts_aggregated {
+ countAll
+ }
}
```
```js [SDK]
-import { createDirectus, rest, aggregate } from '@directus/sdk';
-const directus = createDirectus('https://directus.example.com').with(rest());
+import { createDirectus, rest, aggregate } from "@directus/sdk";
+const directus = createDirectus("https://directus.example.com").with(rest());
const result = await directus.request(
- aggregate('posts', {
- aggregate: { count: '*' },
- })
+ aggregate("posts", {
+ aggregate: { count: "*" },
+ }),
);
```
+
::
## GroupBy
@@ -364,6 +380,7 @@ Grouping allows for running aggregate functions based on a shared value, rather
You can group by multiple fields simultaneously. Combined with the functions, this allows for aggregate reporting per year-month-date.
::code-group
+
```http [REST]
GET /items/posts
?aggregate[count]=views,comments
@@ -384,18 +401,19 @@ query {
```
```js [SDK]
-import { createDirectus, rest, aggregate } from '@directus/sdk';
-const directus = createDirectus('https://directus.example.com').with(rest());
+import { createDirectus, rest, aggregate } from "@directus/sdk";
+const directus = createDirectus("https://directus.example.com").with(rest());
const result = await directus.request(
- aggregate('posts', {
+ aggregate("posts", {
aggregate: {
- count: ['views', 'comments']
+ count: ["views", "comments"],
},
- groupBy: ['author', 'year(publish_date)'],
- })
+ groupBy: ["author", "year(publish_date)"],
+ }),
);
```
+
::
## Deep
@@ -405,6 +423,7 @@ Deep allows you to set any of the other query parameters (except for [Fields](#f
The nested query parameters are to be prefixed with an underscore.
::code-group
+
```http [REST]
// There are two available syntax:
@@ -418,50 +437,53 @@ GET /items/posts
```graphql [GraphQL]
# Natively supported by GraphQL.
query {
- posts {
- translations(filter: { languages_code: { code: { _eq: "en-US" } } }) {
- id
- }
- }
+ posts {
+ translations(filter: { languages_code: { code: { _eq: "en-US" } } }) {
+ id
+ }
+ }
}
```
```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('posts', {
- deep: {
- translations: {
- _filter: {
- languages_code: {
- _eq: 'en-US',
- },
- }
- },
- },
- })
+ readItems("posts", {
+ deep: {
+ translations: {
+ _filter: {
+ languages_code: {
+ _eq: "en-US",
+ },
+ },
+ },
+ },
+ }),
);
```
+
::
::callout{icon="material-symbols:info-outline"}
**Example**
Only get 3 related posts, with only the top rated comment nested:
+
```json
{
- "deep": {
- "related_posts": {
- "_limit": 3,
- "comments": {
- "_sort": "rating",
- "_limit": 1
- }
- }
+ "deep": {
+ "related_posts": {
+ "_limit": 3,
+ "comments": {
+ "_sort": "rating",
+ "_limit": 1
+ }
+ }
}
}
```
+
::
## Alias
@@ -480,40 +502,43 @@ GET /items/posts
```graphql [GraphQL]
# Natively supported by GraphQL.
query {
- posts {
- dutch_translations: translations(filter: { code: { _eq: "nl-NL" } }) {
- id
- }
+ posts {
+ dutch_translations: translations(filter: { code: { _eq: "nl-NL" } }) {
+ id
+ }
- all_translations: translations {
- id
- }
- }
+ all_translations: translations {
+ id
+ }
+ }
}
```
```js [SDK]
-import { createDirectus, rest, readItems } from '@directus/sdk';
-const directus = createDirectus('https://directus.example.com').with(staticToken()).with(rest());
+import { createDirectus, rest, readItems } from "@directus/sdk";
+const directus = createDirectus("https://directus.example.com")
+ .with(staticToken())
+ .with(rest());
const result = await directus.request(
- readItems('posts', {
- alias: {
- all_translations: 'translations',
- dutch_translations: 'translations',
- },
- deep: {
- dutch_translations: {
- _filter: {
- code: {
- _eq: 'nl-NL',
- },
- },
- },
- },
- })
+ readItems("posts", {
+ alias: {
+ all_translations: "translations",
+ dutch_translations: "translations",
+ },
+ deep: {
+ dutch_translations: {
+ _filter: {
+ code: {
+ _eq: "nl-NL",
+ },
+ },
+ },
+ },
+ }),
);
```
+
::
::callout{icon="material-symbols:info-outline"}
@@ -543,6 +568,8 @@ Queries a version of a record by version key when [content versioning](/guides/c
The keys `published` and `draft` are reserved. Use `published` (or `main` for backward compatibility) to explicitly fetch the published base item. Use `draft` to fetch the global draft version.
::
+::code-group
+
```http [GET /items/posts/1]
?version=v1
```
@@ -562,16 +589,21 @@ const directus = createDirectus("https://directus.example.com").with(rest());
const result = await directus.request(
readItem("posts", {
version: "v1",
- })
+ }),
);
```
+::
+
## 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]
@@ -590,15 +622,18 @@ const result = await directus.request(
readItem("posts", {
version: "v1",
versionRaw: true,
- })
+ }),
);
```
+::
+
## Functions
:partial{content="query-functions"}
::code-group
+
```http [REST]
GET /items/posts
?filter[year(date_published)][_eq]=1968
@@ -606,35 +641,40 @@ GET /items/posts
```graphql [GraphQL]
query {
- posts(filter: { date_published_func: { year: { _eq: 1968 } } }) {
- id
- }
+ posts(filter: { date_published_func: { year: { _eq: 1968 } } }) {
+ id
+ }
}
# Due to GraphQL name limitations, append `_func` at the end of the field name and use the function name as the nested field.
```
```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('posts', {
- filter: {
- "year(date_published)": {
- _eq: 1968
- }
- },
- })
+ readItems("posts", {
+ filter: {
+ "year(date_published)": {
+ _eq: 1968,
+ },
+ },
+ }),
);
```
+
::
-: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/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/quickstart#filtering-with-_json) instead.
## Backlink
-When backlink is set to `false`, the API will exclude reverse relations during `*.*` wildcard field expansion to prevent circular references and reduce duplicate data in responses.
+When backlink is set to `false`, the API will exclude reverse relations during `*.*` wildcard field expansion to prevent circular references and reduce duplicate data in responses.
The backlink parameter defaults to `true`, so you need to explicitly set it to `false` to enable the filtering behavior.
@@ -659,7 +699,7 @@ const directus = createDirectus("https://directus.example.com").with(rest());
const result = await directus.request(
readItems("posts", {
backlink: false,
- })
+ }),
);
```
@@ -744,4 +784,4 @@ The articles collection consists of a many-to-one relation to Users called `auth
}
```
-::
\ No newline at end of file
+::
diff --git a/content/guides/04.connect/7.json/1.quickstart.md b/content/guides/04.connect/7.json/1.quickstart.md
new file mode 100644
index 000000000..72c03bd39
--- /dev/null
+++ b/content/guides/04.connect/7.json/1.quickstart.md
@@ -0,0 +1,172 @@
+---
+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)`](/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.
+
+## Using the `json()` function
+
+**Function Syntax:**
+
+```
+json(field, path)
+```
+
+**Example:**
+
+Extract the `color` key from a `metadata` JSON field:
+
+::code-group
+
+```http [REST]
+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());
+
+const result = await directus.request(
+ readItems("articles", {
+ fields: ["id", "title", "json(metadata, color)"],
+ }),
+);
+```
+
+```graphql [GraphQL]
+query {
+ articles {
+ id
+ title
+ metadata_func {
+ json(path: "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" }
+ }
+ ]
+ }
+}
+```
+
+::
+
+For REST and SDK, the extracted value is returned under the alias `{field}_{path}_json` with `.`, `[`, and `]` replaced by underscores.
+
+## Filtering with `_json`
+
+**Operator Syntax:**
+
+```
+{
+ "field": {
+ "_json": {
+ "path": {
+ "_operator": value
+ }
+ }
+ }
+}
+```
+
+**Example:**
+
+Find articles where the `color` key inside `metadata` equals `"blue"`:
+
+::code-group
+
+```http [REST]
+GET /items/articles
+ ?filter={"metadata":{"_json":{"color":{"_eq":"blue"}}}}
+```
+
+```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" } },
+ },
+ },
+ }),
+);
+```
+
+```graphql [GraphQL]
+query {
+ articles(filter: { metadata: { _json: { color: { _eq: "blue" } } } }) {
+ id
+ title
+ }
+}
+```
+
+::
+
+Response:
+
+```json
+{
+ "data": [
+ { "id": 1, "title": "An Article" },
+ { "id": 4, "title": "Another Article" }
+ ]
+}
+```
+
+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
+
+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 key |
+| `[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 [Unsupported Path Expressions](/guides/connect/json/advanced-querying#unsupported-path-expressions) section for a complete list of unsupported characters.
+
+## More information
+
+For advanced usage details and additional examples, see [Advanced JSON Querying](/guides/connect/json/advanced-querying).
diff --git a/content/guides/04.connect/7.json/2.advanced-querying.md b/content/guides/04.connect/7.json/2.advanced-querying.md
new file mode 100644
index 000000000..d50c01de0
--- /dev/null
+++ b/content/guides/04.connect/7.json/2.advanced-querying.md
@@ -0,0 +1,937 @@
+---
+stableId: 26102b15-bee8-4e86-8f1f-4d74f1ea9cb0
+title: Advanced Querying
+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 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
+
+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 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)
+```
+
+```[Filtering]
+{
+ "metadata": {
+ "_json": {
+ "settings.theme": {
+ "_eq":"blue"
+ }
+ }
+ }
+}
+```
+
+::
+
+### Unsupported Path Expressions
+
+The following path syntaxes are **not supported** and and will result in an error if used
+
+| Expression | Example |
+| ------------------------- | ------------------------ |
+| Empty brackets (wildcard) | `items[]` |
+| `[*]` wildcard | `items[*].name` |
+| `*` glob | `items.*` |
+| JSONPath predicates | `items[?(@.price > 10)]` |
+| `@` current node | `@.name` |
+| `$` root | `$.name` |
+
+### Non-Alphanumeric Characters in Object Keys
+
+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 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).
+::
+
+### Syntax
+
+```
+json(field, path)
+```
+
+- `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.
+
+::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.
+::
+
+::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:
+
+```
+{field}_{path}_json
+```
+
+Path segments are normalized by replacing special characters (e.g. `[`, `]`, `.`) 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` |
+
+::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
+
+::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 a single JSON field in one request. In GraphQL, use field aliases on the `json` sub-field to differentiate each extracted value.
+
+::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 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 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
+
+```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)
+
+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
+
+```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)
+
+Syntax: `json(relation.item:collection_name.json_field, path)`
+
+M2A relations, use the standard Directus collection scope syntax inside the first argument.
+
+::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 independent depth limits:
+
+- **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.
+
+::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 `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";
+
+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 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.
+::
+
+## 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.
+
+::callout{icon="material-symbols:warning-rounded" color="warning"}
+`_json` is only valid on `json` typed fields.
+::
+
+### Syntax
+
+```
+{ "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
+
+The `_json` operator supports all standard filter operators **except** the following:
+
+| Category | Operators |
+| ---------- | --------------------------------- |
+| JSON | `_json` |
+| Geometric | `_intersects`, `_intersects_bbox` |
+| Regex | `_regex` |
+| Relational | `_some`, `_none` |
+
+### 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: { _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 },
+ },
+ },
+ },
+ }),
+);
+```
+
+::
+
+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` 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
+
+```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 using `_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: { 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 } } } },
+ ],
+ },
+ }),
+);
+```
+
+::
+
+Response:
+
+```json
+{
+ "data": [{ "id": 3, "title": "Large Blue Article" }]
+}
+```
+
+Conditions can also be grouped within the `_json` operator using `_and` or `_or`:
+
+```json
+{
+ "metadata": {
+ "_json": {
+ "_and": [{ "color": { "_eq": "blue" } }, { "size": { "_gt": 10 } }]
+ }
+ }
+}
+```
+
+### Dynamic Variables
+
+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 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 will return `0` / `1` instead of boolean values when the resolved path is a boolean.
+
+### MSSQL
+
+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
+
+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"`.