Query Parameters
Most Directus API endpoints can use global query parameters to alter the data that is returned.
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.
GET /items/posts
?fields=first_name,last_name,avatar.description
Use native GraphQL queries.
{
"fields": ["first_name", "last_name", { "avatar": ["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. |
Many to Any Fields
As Many to Any (M2A) fields have nested data from multiple collections, you are not always able to fetch the same field from every related collection. In M2A fields, you can use the following syntax to specify what fields to fetch from which related nested collection type: ?fields=m2a-field:collection-scope.field
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.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
# Use can use native GraphQL Union types.
query {
posts {
sections {
item {
... on headings {
title
level
}
... on paragraphs {
body
}
... on videos {
source
}
}
}
}
}
import { createDirectus, rest, readItems } from '@directus/sdk';
const directus = createDirectus('https://directus.example.com').with(rest());
const result = await directus.request(
readItems('posts', {
fields: [
'title',
{
sections: [
{
item: {
headings: ['title', 'level'],
paragraphs: ['body'],
videos: ['source'],
}
}
]
}
],
})
);
Filter
Specify which items are returned based on the result of a filter rule.
// There are two available syntax:
GET /items/posts
?filter[title][_eq]=Hello
GET /items/posts
?filter={ "title": { "_eq": "Hello" }}
query {
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.
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',
},
},
})
);
Search
Search on all string and text type fields within a collection. It's an easy way to search for an item without creating complex field filters – though it is far less optimized. Related item fields are not included.
GET /items/posts
?search=Directus
query {
posts(search: "Directus") {
id
}
}
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',
})
);
Sort
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.
GET /items/posts
?sort=sort,-date_created,author.name
query {
posts(sort: ["sort", "-date_created", "author.name"]) {
id
}
}
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'],
})
);
Limit
Set the maximum number of items that will be returned. The default limit is set to 100. -1 will return all items.
GET /items/posts
?limit=50
query {
posts(limit: 50) {
id
}
}
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,
})
);
Depending on the size of your collection, fetching the maximum amount of items may result in degraded performance or timeouts.The maximum number of items that can be requested on the API can be configured using the
QUERY_LIMIT_MAX environment variable. This cannot be overridden by changing the value of limit.Offset
Skip the specified number of items in the response. This parameter can be used for pagination.
GET /items/posts
?offset=100
query {
posts(offset: 100) {
id
}
}
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,
})
);
Page
An alternative to offset. Returned values are the value of limit multiplied by page. The first page is 1.
GET /items/posts
?page=2
query {
posts(page: 2) {
id
}
}
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,
})
);
Aggregate
Aggregate functions allow you to perform calculations on a set of values, returning a single result.
| Function | Description |
|---|---|
count | Counts how many items there are |
countDistinct | Counts how many unique items there are |
sum | Adds together the values in the given field |
sumDistinct | Adds together the unique values in the given field |
avg | Get the average value of the given field |
avgDistinct | Get the average value of the unique values in the given field |
min | Return the lowest value in the field |
max | Return the highest value in the field |
countAll | Equivalent to ?aggregate[count]=* (GraphQL only) |
GET /items/posts
?aggregate[count]=*
query {
posts_aggregated {
countAll
}
}
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: '*' },
})
);
GroupBy
Grouping allows for running aggregate functions based on a shared value, rather than the entire dataset.
You can group by multiple fields simultaneously. Combined with the functions, this allows for aggregate reporting per year-month-date.
GET /items/posts
?aggregate[count]=views,comments
&groupBy[]=author
&groupBy[]=year(publish_date)
query {
posts_aggregated(groupBy: ["author", "year(publish_date)"]) {
group
count {
views
comments
}
}
}
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: ['views', 'comments']
},
groupBy: ['author', 'year(publish_date)'],
})
);
Deep
Deep allows you to set any of the other query parameters (except for Fields and Deep itself) on a nested relational dataset.
The nested query parameters are to be prefixed with an underscore.
// There are two available syntax:
GET /items/posts
?deep[translations][_filter][languages_code][_eq]=en-US
GET /items/posts
?deep={ "translations": { "_filter": { "languages_code": { "_eq": "en-US" }}}}
# Natively supported by GraphQL.
query {
posts {
translations(filter: { languages_code: { code: { _eq: "en-US" } } }) {
id
}
}
}
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',
},
}
},
},
})
);
Only get 3 related posts, with only the top rated comment nested:
{
"deep": {
"related_posts": {
"_limit": 3,
"comments": {
"_sort": "rating",
"_limit": 1
}
}
}
}
Alias
Rename fields for this request, and fetch the same nested data set multiple times using different filters.
GET /items/posts
?alias[all_translations]=translations
&alias[dutch_translations]=translations
&deep[dutch_translations][_filter][code][_eq]=nl-NL
# Natively supported by GraphQL.
query {
posts {
dutch_translations: translations(filter: { code: { _eq: "nl-NL" } }) {
id
}
all_translations: translations {
id
}
}
}
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',
},
},
},
},
})
);
Aliases support being used in combination with:
- functions, e.g.
alias[release_year]=year(released) - in the deep query parameter, e.g.
deep[author][_alias][birthyear]=year(birthday)
alias[author_name]=author.name and not possible to have . in the alias name itself e.g. alias[not.possible]=field.Export
Saves the API response to a file. Valid values are csv, json, xml, yaml.
?export=type
Version
Queries a version of a record by version key when content versioning is enabled on a collection. Applies only to single item retrieval.
?version=v1
query {
posts_by_id(id: 1, version: "v1") {
id
}
}
import { createDirectus, rest, readItem } from "@directus/sdk";
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 on a version record.
?version=v1&versionRaw=true
query {
posts_by_version(id: 1, version: "v1") {
id
}
}
import { createDirectus, rest, readItem } from "@directus/sdk";
const directus = createDirectus("https://directus.example.com").with(rest());
const result = await directus.request(
readItem("posts", {
version: "v1",
versionRaw: true,
})
);
Functions
Functions accept a field and return a modified value. Functions can be used in any query parameter you'd normally supply a field key, including fields, aggregation, and filters.
The syntax for using a function is function(field).
| Function | Description |
|---|---|
year | Extract the year from a datetime/date/timestamp field |
month | Extract the month from a datetime/date/timestamp field |
week | Extract the week from a datetime/date/timestamp field |
day | Extract the day from a datetime/date/timestamp field |
weekday | Extract the weekday from a datetime/date/timestamp field |
hour | Extract the hour from a datetime/date/timestamp 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 |
GET /items/posts
?filter[year(date_published)][_eq]=1968
query {
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.
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
}
},
})
);
The json() Function
The json() function extracts a specific value from 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.
Note: the
json()function is currently only supported for use in thefieldsquery parameter.
Syntax
json(field, path)
field— the name of a JSON column in the collection (or a relational path to one, see 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
GET /items/articles?fields=id,title,json(metadata, color)&sort=title
{
"data": [
{
"id": 1,
"title": "An Article",
"metadata_color_json": "blue"
}
]
}
Relational Queries
json() 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.
GET /items/articles?fields=id,title,category_id.name,json(category_id.metadata, color)
{
"data": [
{
"id": 1,
"title": "An Article",
"category_id": {
"name": "Tech",
"metadata_color_json": "blue"
}
}
]
}
Multiple json() extractions from the same relation are grouped under the same relational key:
GET /items/articles?fields=id,json(category_id.metadata, color),json(category_id.metadata, icon)
{
"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.
GET /items/articles/1?fields=id,json(comments.data, type)
{
"data": {
"id": 1,
"comments": [
{ "data_type_json": "review" },
{ "data_type_json": "feedback" },
{ "data_type_json": "question" }
]
}
}
Any-to-One (A2O / Many-to-Any)
For A2O 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)
Relational Depth Limit
Directus enforces a maximum relational depth (MAX_RELATIONAL_DEPTH, default 10). For json(), only the first argument (the field path) counts toward this limit — the JSON path in the second argument is excluded.
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 returns a 400 Invalid query. Max relational depth exceeded. error.
JSON Path Depth Limit
The JSON path (second argument) also has its own configurable limit:
MAX_JSON_QUERY_DEPTH=10 # default
Depth is counted by the number of . and [ characters in the normalized path. A path like a.b.c.d.e.f.g.h.i.j has depth 10 and is allowed by default; adding one more segment exceeds the limit.
Exceeding this limit returns: Invalid query. JSON path depth (N) exceeds allowed maximum of M.
Error Handling
| Condition | Behavior |
|---|---|
Malformed json() syntax | 400 Invalid json() syntax |
| Missing field or path argument | 400 Invalid json() syntax: missing field name / path |
| Field is not a JSON type | 400 error with message {collection}.{field} is not a JSON field |
| Nonexistent relational field | Silently ignored (Directus standard behavior for unknown relational fields) |
| Path does not exist in the JSON document | Returns null for that field |
| Relational depth exceeded | 400 Invalid query. Max relational depth exceeded. |
| JSON path depth exceeded | 400 Invalid query. JSON path depth (N) exceeds allowed maximum of M. |
Nested functions in fields | 400 Nested functions are not supported in "fields" |
Unsupported Path Expressions
The following path syntaxes are not supported and will return a 400 error:
| Expression | Example | Reason |
|---|---|---|
| Empty brackets (wildcard) | items[] | Not supported |
[*] wildcard | items[*].name | Not supported |
* glob | items.* | Not supported |
| JSONPath predicates | items[?(@.price > 10)] | Not supported |
@ current node | @.name | Not supported |
$ root | $.name | Not supported |
Object Keys with Special Characters
The json() 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 Behavior
PostgreSQL / CockroachDB
Uses the native -> operator chain with parameterized bindings to prevent SQL injection. The PostgreSQL pg driver automatically deserializes JSON/JSONB values, so objects and arrays are returned as native JavaScript values.
-- json(metadata, items[0].name)
"table"."metadata"::json->?->?->? -- bindings: ['items', 0, 'name']
MySQL / MariaDB
Uses JSON_UNQUOTE(JSON_EXTRACT()) with $ path notation.
-- json(metadata, color)
JSON_UNQUOTE(JSON_EXTRACT(`table`.`metadata`, '$.color'))
Objects and arrays extracted by MySQL's JSON_EXTRACT are returned as strings containing JSON text. Directus automatically parses these back to JavaScript objects/arrays. Scalar values (strings, numbers, booleans) are returned as their native types after JSON_UNQUOTE.
SQLite
Uses json_extract() with $ path notation.
-- json(data, items[0].name)
json_extract(`table`.`data`, '$.items[0].name')
Objects and arrays are returned as JSON strings by the SQLite driver and are automatically parsed by Directus.
Note: SQLite can return
0/1isntead ofbooleanvalues.
MSSQL
Uses COALESCE(JSON_QUERY(), JSON_VALUE()) to handle both object/array paths and scalar paths in a single query:
-- json(metadata, color)
COALESCE(JSON_QUERY([table].[metadata], '$.color'), JSON_VALUE([table].[metadata], '$.color'))
JSON_QUERY returns objects/arrays (or NULL for scalars), and JSON_VALUE returns scalars (or NULL for objects/arrays). COALESCE picks whichever is non-NULL.
Note:
JSON_VALUEin MSSQL always returns scalar values as strings (NVARCHAR), even when the original JSON value is a number or boolean. For example, a JSON integer42will be returned as the string"42". Your application should perform type coercion as needed.
Oracle
Uses the same COALESCE(JSON_QUERY(), JSON_VALUE()) pattern as MSSQL:
-- json(metadata, color)
COALESCE(JSON_QUERY("table"."metadata", '$.color'), JSON_VALUE("table"."metadata", '$.color'))
Note: As with MSSQL,
JSON_VALUEin Oracle returns scalar values as strings, regardless of the original JSON type (number, boolean, etc.). A JSON number3.14will be returned as"3.14".
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.
The backlink parameter defaults to true, so you need to explicitly set it to false to enable the filtering behavior.
The backlink parameter only affects
*.* wildcard field expansion. Explicitly specified field names are not filtered.
For example: fields=author.articles will still include the reverse relation even when backlink=false.GET /items/posts
?fields=*.*.*
&backlink=false
import { createDirectus, rest, readItems } from "@directus/sdk";
const directus = createDirectus("https://directus.example.com").with(rest());
const result = await directus.request(
readItems("posts", {
backlink: false,
})
);
Red lines mark the response when backlink is set to
true while green marks when backlinks is set to false.
The articles collection consists of a many-to-one relation to Users called author and a many-to-many relation to Tags called tags.GET /items/articles?fields=*.*.*&backlink=true /
false{
"data": [
{
"id": 1,
"title": "My first Article",
"author": {
"id": 2,
"name": "Nils",
"articles": [
{ "id": 1, "title": "My first Article", "author": { "id": 2, "name": "Nils", "articles": [1] }, "tags": [ { "id": 3, "articles_id": 1, "tags_id": 4 } ] } 1 ]
},
"tags": [
{
"id": 3,
"articles_id": { "id": 1, "title": "My first Article", "author": { "id": 2, "name": "Nils", "articles": [1] }, "tags": [ { "id": 3, "articles_id": 1, "tags_id": 4 } ] }, "articles_id": 1, "tags_id": {
"id": 4,
"tag": "Tag1",
"articles": [
{ "id": 3, "articles_id": 1, "tags_id": 4 } 3 ]
}
}
]
}
]
}
Get once-a-month release notes & real‑world code tips...no fluff. 🐰
Filter Rules
Learn about filter rules in Directus - available operators, filter syntax, relational fields, dynamic variables, logical operators, and functions parameters. Understand how to build complex filters for permissions, validations, and automations.
Relational Data
Directus enables you to manage and interact with relational data. This section will guide you through the different types of relationships and how to work with them.