Connect

JSON Queries

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.

PatternExampleMeaning
keycolorTop-level key
a.b.csettings.theme.colorNested keys
[n]tags[0]Array element at index n
a[n].bitems[0].nameMixed 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:

ExpressionExample
Empty brackets (wildcard)items[]
[*] wildcarditems[*].name
* globitems.*
JSONPath predicatesitems[?(@.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.

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

GET /items/articles?fields=id,title,json(metadata, color)

Response:

{
    "data": [
        {
            "id": 1,
            "title": "An Article",
            "metadata_color_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.

GET /items/articles?fields=id,json(metadata, color),json(metadata, settings.theme),json(metadata, tags[0])

Response:

{
    "data": [
        {
            "id": 1,
            "metadata_color_json": "blue",
            "metadata_settings_theme_json": "dark",
            "metadata_tags_0_json": "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.

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.
GET /items/articles?fields=id,json(metadata, dimensions),json(metadata, tags)

Response:

{
    "data": [
        {
            "id": 1,
            "metadata_dimensions_json": { "width": 100, "height": 50 },
            "metadata_tags_json": ["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.

GET /items/articles?fields=id,title,category_id.name,json(category_id.metadata, color)

Response:

{
    "data": [
        {
            "id": 1,
            "title": "An Article",
            "category_id": {
                "name": "News",
                "metadata_color_json": "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.

GET /items/articles/1?fields=id,json(comments.data, type)

Response:

{
    "data": {
        "id": 1,
        "comments": [
            { "data_type_json": "comment" },
            { "data_type_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)

GET /items/shapes/1?fields=id,json(children.item:circles.metadata, color)

Response:

{
    "data": {
        "id": 1,
        "children": [
            {
                "item": {
                    "metadata_color_json": "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.

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

Alias Typing Requires Literal Field ArraysAlias 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).

Supported Inner Operators

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

GET /items/articles
    ?filter={"metadata":{"_json":{"color":{"_eq":"blue"}}}}

Response:

{
    "data": [
        { "id": 1, "title": "An Article" },
        { "id": 4, "title": "Another Article" }
    ]
}

Multiple Path Conditions

Combine several path conditions inside a single _json object.

GET /items/articles
    ?filter={"metadata":{"_json":{"color":{"_eq":"red"},"brand":{"_in":["BrandX","BrandY"]},"level":{"_gte":3}}}}

Response:

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

GET /items/articles
    ?filter={"metadata":{"_json":{"settings.theme":{"_eq":"dark"},"tags[0]":{"_eq":"electronics"}}}}

Response:

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

GET /items/articles
    ?filter={"category_id":{"metadata":{"_json":{"color":{"_eq":"blue"}}}}}

Response:

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

GET /items/articles
    ?filter={"_and":[{"metadata":{"_json":{"color":{"_eq":"blue"}}}},{"metadata":{"_json":{"size":{"_gt":10}}}}]}

Response:

{
    "data": [
        { "id": 3, "title": "Large Blue Article" }
    ]
}

You can also group conditions inside a single _json value using _and or _or:

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

Get once-a-month release notes & real‑world code tips...no fluff. 🐰