# Advanced Querying

> 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.

<table>
<thead>
  <tr>
    <th>
      Pattern
    </th>
    
    <th>
      Example
    </th>
    
    <th>
      Meaning
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        key
      </code>
    </td>
    
    <td>
      <code>
        color
      </code>
    </td>
    
    <td>
      Top-level object key
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        a.b.c
      </code>
    </td>
    
    <td>
      <code>
        settings.theme.color
      </code>
    </td>
    
    <td>
      Nested object key
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        [n]
      </code>
    </td>
    
    <td>
      <code>
        tags[0]
      </code>
    </td>
    
    <td>
      Array element at index <code>
        n
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        a[n].b
      </code>
    </td>
    
    <td>
      <code>
        items[0].name
      </code>
    </td>
    
    <td>
      Mixed object/array access
    </td>
  </tr>
</tbody>
</table>

**Examples:**

<code-group>

```text [Field Selection]
json(metadata, settings.theme)
```

```text [Filtering]
{
  "metadata": {
    "_json": {
      "settings.theme": {
        "_eq":"blue"
      }
    }
  }
}
```

</code-group>

### Unsupported Path Expressions

The following path syntaxes are **not supported** and and will result in an error if used

<table>
<thead>
  <tr>
    <th>
      Expression
    </th>
    
    <th>
      Example
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      Empty brackets (wildcard)
    </td>
    
    <td>
      <code>
        items[]
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        [*]
      </code>
      
       wildcard
    </td>
    
    <td>
      <code>
        items[*].name
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        *
      </code>
      
       glob
    </td>
    
    <td>
      <code>
        items.*
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      JSONPath predicates
    </td>
    
    <td>
      <code>
        items[?(@.price > 10)]
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        @
      </code>
      
       current node
    </td>
    
    <td>
      <code>
        @.name
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        $
      </code>
      
       root
    </td>
    
    <td>
      <code>
        $.name
      </code>
    </td>
  </tr>
</tbody>
</table>

### 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="i-lucide-triangle-alert" 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).

</callout>

### Syntax

```text
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="i-lucide-info" 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>

<callout icon="i-lucide-info" 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 details.

</callout>

### 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:

```text
{field}_{path}_json
```

Path segments are normalized by replacing special characters (e.g. `[`, `]`, `.`) with underscores.

<table>
<thead>
  <tr>
    <th>
      Request field
    </th>
    
    <th>
      Response key
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        json(metadata, color)
      </code>
    </td>
    
    <td>
      <code>
        metadata_color_json
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        json(metadata, settings.priority)
      </code>
    </td>
    
    <td>
      <code>
        metadata_settings_priority_json
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        json(data, items[0].name)
      </code>
    </td>
    
    <td>
      <code>
        data_items_0_name_json
      </code>
    </td>
  </tr>
</tbody>
</table>

<callout icon="i-lucide-triangle-alert" 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.

</callout>

### 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)"],
  }),
);
```

</code-group>

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" }
      }
    ]
  }
}
```

</code-group>

### 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])",
    ],
  }),
);
```

</code-group>

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"
        }
      }
    ]
  }
}
```

</code-group>

### 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="i-lucide-triangle-alert" 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.

</callout>

<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)"],
  }),
);
```

</code-group>

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"]
        }
      }
    ]
  }
}
```

</code-group>

### 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)"] }],
  }),
);
```

</code-group>

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" }
        }
      }
    ]
  }
}
```

</code-group>

#### 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)"] }],
  }),
);
```

</code-group>

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" } }
      ]
    }
  }
}
```

</code-group>

#### 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)"],
            },
          },
        ],
      },
    ],
  }),
);
```

</code-group>

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" }
          }
        }
      ]
    }
  }
}
```

</code-group>

### 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="i-lucide-triangle-alert" color="warning">

Exceeding either of these limits will result in an error.

</callout>

### 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<Schema>("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="i-lucide-info">

**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.

</callout>

## 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="i-lucide-info" color="info">

JSON filtering is also available visually in the Studio filter UI. See [Filtering JSON Fields](/guides/content/explore#filtering-json-fields).

</callout>

<callout icon="i-lucide-triangle-alert" color="warning">

`_json` is only valid on `json` typed fields.

</callout>

### Syntax

```text
{ "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:

<table>
<thead>
  <tr>
    <th>
      Category
    </th>
    
    <th>
      Operators
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      JSON
    </td>
    
    <td>
      <code>
        _json
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      Geometric
    </td>
    
    <td>
      <code>
        _intersects
      </code>
      
      , <code>
        _intersects_bbox
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      Regex
    </td>
    
    <td>
      <code>
        _regex
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      Relational
    </td>
    
    <td>
      <code>
        _some
      </code>
      
      , <code>
        _none
      </code>
    </td>
  </tr>
</tbody>
</table>

### 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" } },
      },
    },
  }),
);
```

</code-group>

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 },
        },
      },
    },
  }),
);
```

</code-group>

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" },
        },
      },
    },
  }),
);
```

</code-group>

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" } },
        },
      },
    },
  }),
);
```

</code-group>

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 } } } },
      ],
    },
  }),
);
```

</code-group>

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"`.
