Skip to content

Every collection endpoint (/api/{entity_type}/{bundle}, see the Content API reference) accepts the JSON:API query parameters below. Parameters combine freely with &. Field paths always use field machine names, exactly as they appear as keys in responses, not admin-UI labels. A self-managed Drupal site on Cloud Platform serves its own JSON:API rather than the Content API; for its parameters, see Drupal’s JSON:API documentation.

For learning-oriented walkthroughs, see the query quickstart and the querying guide.

All fifteen operators below work on Source CMS. Two caveats: date fields compare as UNIX timestamps (see the operators section), and filtering the computed moderation_state field returns a server error (503) with any operator, including plain equality; filter on status instead.

?filter[field_name]=value

Example: published entries only (fields trims the response for display, see sparse fieldsets):

GET /api/node/article?filter[status]=1&fields[node--article]=title
{
"jsonapi": { "version": "1.1", "meta": { … } },
"data": [
{ "type": "node--article", "id": "7f7dd525-…", "attributes": { "title": "Spring launch recap" } },
{ "type": "node--article", "id": "08d88ef2-…", "attributes": { "title": "Designing for headless from day one" } },
{ "type": "node--article", "id": "36579530-…", "attributes": { "title": "Q3 platform roadmap highlights" } },
{ "type": "node--article", "id": "ba1f4fb1-…", "attributes": { "title": "How editorial workflows changed our release cadence" } }
],
"meta": { "count": 4 },
"links": { "self": { … } }
}
?filter[LABEL][condition][path]=FIELD_PATH
&filter[LABEL][condition][operator]=OPERATOR
&filter[LABEL][condition][value]=VALUE

LABEL is any name you choose for the condition. The condition keys:

Parameter Type Required Default Description
[condition][path] string yes none Field path, machine names only; dot notation follows relationships (tags.name)
[condition][operator] string no = One of the operators below, exactly as written
[condition][value] string or array of string all operators except IS NULL/IS NOT NULL none The comparison value; multi-value operators take repeated value[] entries
[condition][memberOf] string no none Attaches the condition to a named filter group

Example: titles containing “launch”:

GET /api/node/article?filter[t][condition][path]=title
&filter[t][condition][operator]=CONTAINS
&filter[t][condition][value]=launch
&fields[node--article]=title
{
"jsonapi": { "version": "1.1", "meta": { … } },
"data": [
{ "type": "node--article", "id": "7f7dd525-…", "attributes": { "title": "Spring launch recap" } }
],
"meta": { "count": 1 },
"links": { "self": { … } }
}
Operator Matches Example (condition parts)
= Exact value (the default; the short form uses it) path=status, operator==, value=1
<> Anything except the value path=status, operator=<>, value=0
> Greater than path=created, operator=>, value=1893456000 (a timestamp; see the date caveat below)
>= Greater than or equal path=field_price, operator=>=, value=10
< Less than path=created, operator=<, value=1893456000 (a timestamp; see the date caveat below)
<= Less than or equal path=field_price, operator=<=, value=100
STARTS_WITH String prefix path=title, operator=STARTS_WITH, value=How
CONTAINS Substring path=title, operator=CONTAINS, value=release
ENDS_WITH String suffix path=title, operator=ENDS_WITH, value=2026
IN Any of several values, pass value[] multiple times path=tags.name, operator=IN, value[]=news&value[]=events (each entry repeats the same filter[...] prefix; full example below)
NOT IN None of several values path=tags.name, operator=NOT IN, value[]=archive
BETWEEN Inside a range, exactly two value[] entries path=field_price, operator=BETWEEN, value[]=10&value[]=100
NOT BETWEEN Outside a range path=field_price, operator=NOT BETWEEN, value[]=10&value[]=100
IS NULL Field has no value (omit value) path=field_subtitle, operator=IS NULL
IS NOT NULL Field has any value (omit value) path=field_subtitle, operator=IS NOT NULL

Date comparisons use UNIX timestamps (verified). created and changed are stored as timestamps, and comparison operators compare against the raw stored value, even though responses display ISO 8601. filter[t][condition][value]=2030-01-01 silently compares against the integer 2030 (matching almost nothing, or everything, depending on direction); filter[t][condition][value]=1893456000 compares against the date you meant. Convert dates to timestamps before filtering: date -d 2030-01-01 +%s (GNU) or date -j -f %Y-%m-%d 2030-01-01 +%s (macOS).

Full IN example:

GET /api/node/article?filter[tags][condition][path]=tags.name
&filter[tags][condition][operator]=IN
&filter[tags][condition][value][]=news
&filter[tags][condition][value][]=events

Multiple conditions are ANDed by default. To OR them, declare a group with a conjunction and attach conditions to it with memberOf:

GET /api/node/article?filter[or-group][group][conjunction]=OR
&filter[a][condition][path]=title
&filter[a][condition][operator]=CONTAINS
&filter[a][condition][value]=beta
&filter[a][condition][memberOf]=or-group
&filter[b][condition][path]=title
&filter[b][condition][operator]=CONTAINS
&filter[b][condition][value]=preview
&filter[b][condition][memberOf]=or-group

This matches articles whose title contains “beta” OR “preview”. Groups can nest: a group itself takes memberOf to join a parent group. The JSON:API Query Builder (the interactive query workbench in your site’s admin UI at API > JSON:API Query Builder) generates these in its Filter Groups (AND/OR logic) section.

Used in: querying guide → filtering.

Return only the named fields for a resource type. The type key is the full resource type, {entity_type}--{bundle}:

GET /api/node/article?
fields[node--article]=title,created,image
  • Fields not listed are omitted from both attributes and relationships.
  • Repeat the parameter per type to trim included resources too: &fields[media--image]=media_image.
  • Smaller responses are faster; the Query Builder’s Fields tab builds this parameter.

Each entry’s attributes carries only the named field, and relationships is gone entirely:

{
"jsonapi": { "version": "1.1", "meta": { … } },
"data": [
{ "type": "node--article", "id": "7f7dd525-…", "links": { … },
"attributes": { "title": "Spring launch recap" } },
],
"meta": { "count": 4 },
"links": { "self": { … } }
}

Used in: querying guide → sparse fieldsets.

Embed related entries in the response’s top-level included array instead of fetching them one by one:

GET /api/node/article?include=image
  • Comma-separate multiple relationships: include=image,tags.
  • Dot notation follows relationships through intermediate entities, e.g. through a media entity to its file: include=image.media_image.
  • No nesting-depth cap is documented, and none was encountered in verification (two-level chains like image.media_image work; each segment must be a relationship field, or the request 400s naming the failing segment, see 400 responses).

For the two-level chain GET /api/node/article?include=image.media_image&page[limit]=1, every entity on the chain lands in included, once each:

{
"jsonapi": { "version": "1.1", "meta": { … } },
"data": [
{ "type": "node--article", "id": "7f7dd525-…",
"attributes": { … },
"relationships": { "image": { "data": { "type": "media--image", "id": "5f2c5cde-…" } }, … } }
],
"included": [
{ "type": "media--image", "id": "5f2c5cde-…", "attributes": { "name": "Portal verification hero", … } },
{ "type": "file--file", "id": "0d28b1dd-…",
"attributes": { "filename": "portal-verification-hero.png",
"uri": { "url": "/sites/default/files/2026-07/portal-verification-hero.png", … }, … } }
],
"meta": { … }, "links": { … }
}

Used in: querying guide → relationships and the N+1 pitfall.

GET /api/node/article?page[limit]=10&page[offset]=20
Parameter Type Required Default Description
page[limit] integer no 50 Maximum number of entries in one response; values above 50 are clamped to 50 (verified). Lower limits make the server respond faster
page[offset] integer no 0 Number of entries to skip before the first returned entry

Responses include a links.next URL while more entries remain. Follow it rather than computing offsets by hand. It adds the page[offset] and preserves every other parameter, with square brackets percent-encoded. The shape for GET /api/node/article?page[limit]=2&sort=-created on a five-entry collection:

{
"meta": { "count": 5 },
"links": {
"next": {
"href": "https://your-site.example.com/api/node/article?page%5Boffset%5D=2&page%5Blimit%5D=2&sort=-created"
}
}
}

meta.count is the collection total. Caveat: pagination links are computed before access filtering, so a page can return "data": [] and still carry links.next/links.last (for example when every entry on it is unpublished). Don’t treat an empty page as the end of the collection; follow links.next, or check meta.count and meta.omitted (see omitted resources).

Used in: query quickstart (page[limit] is its one variation), querying guide → pagination.

?sort=FIELD_PATH
Syntax Meaning
sort=created Ascending by created
sort=-created Descending: prefix the path with -
sort=-status,created Multiple keys, comma-separated, applied left to right
sort=tags.name Dot notation sorts by a related entry’s field

Example: alphabetical by title:

GET /api/node/article?sort=title&fields[node--article]=title
{
"jsonapi": { "version": "1.1", "meta": { … } },
"data": [
{ "type": "node--article", "id": "08d88ef2-…", "attributes": { "title": "Designing for headless from day one" } },
{ "type": "node--article", "id": "ba1f4fb1-…", "attributes": { "title": "How editorial workflows changed our release cadence" } },
{ "type": "node--article", "id": "36579530-…", "attributes": { "title": "Q3 platform roadmap highlights" } },
{ "type": "node--article", "id": "7f7dd525-…", "attributes": { "title": "Spring launch recap" } }
],
"meta": { "count": 4 },
"links": { "self": { … } }
}

Used in: querying guide → sorting.

The content language is selected per request with the langCode query parameter, provided by the JSON:API Multilingual module, which every Source CMS site ships active (confirmed 2026-08-19; behavior verified live the same day). Selection is strict: a missing translation is a 404 whose error object names the entry’s viewable translations in errors[0].detail and, machine-readably, in errors[0].meta.availableTranslations (captured: The "fi" translation of the specified resource does not exist. Available translations: en.). A language not enabled on the site is a 422 (captured: The specified language ("de") is invalid or has not been configured.).

Adding includeFallback=1 resolves the read through the site’s language fallback chain instead, so an existing entry always returns 200. It is valid only together with langCode, only on reads, and only with the literal value 1; the rejections are 400s (captured: The "includeFallback" query parameter requires the "langCode" query parameter. and The "includeFallback" query parameter only accepts the value "1".).

Both parameters apply to individual, collection, and related reads and, for langCode, to writes (reads and writes verified 2026-08-19). The language actually served is reported by the Content-Language response header (individual responses) and each entry’s langcode attribute. See the multilingual guide for requesting translations and detecting fallback, and translating content for the per-translation writes. A site without the module (a local stand-in, say) accepts langCode without acting on it, the treatment JSON:API’s parameter naming rules prescribe for implementation-specific parameters a server does not implement.

A langcode URL path prefix (for example /es/api/node/article) resolves for enabled languages and 404s otherwise. Whether it selects the content language is site negotiation configuration, not a contract: a local product instance negotiated by prefix (2026-07-03), while the Source CMS site verified 2026-08-19 returned the default-language entry under a prefix even when the translation existed. Select with langCode, not the prefix. filter[langcode] filters by translation existence without changing the language served (verified 2026-08-19: filter[langcode]=fi matched an entry with a Finnish translation and returned it in the default language).

A lowercase ?langcode= is rejected with 400 and the literal detail The following query parameters violate the JSON:API spec: 'langcode'. (verified 2026-07-03 and 2026-08-19), like any parameter that violates JSON:API’s naming rules, lang included (see 400 responses). The Accept-Language request header does not negotiate content language (verified 2026-07-03 and 2026-08-19). Each entry’s language appears verbatim as the langcode attribute in responses.

A malformed query returns 400 Bad Request with a JSON:API error document:

{
"jsonapi": { "version": "1.1" },
"errors": [
{
"title": "Bad Request",
"status": "400",
"detail": "..."
}
]
}

The errors[].detail member names what was wrong. Causes mapped to the literal detail (JSON:API 1.1):

Cause Literal detail Fix
Unknown field path in filter or sort Invalid nested filtering. The field `nonexistent_field`, given in the path `nonexistent_field`, does not exist. Use the field’s machine name, not its label; the Query Builder’s Field dropdown lists valid paths
Invalid or misspelled operator The 'LIKE' operator is not allowed in a filter parameter. (the accepted set is not listed in the error) Use one from the operators table, exactly, uppercase where shown
include path that isn’t a relationship `uid.bad.deep` is not a valid include path. `bad` is not a valid relationship field name. Possible values: roles. (the error lists the valid relationship names at the failing segment) Only relationship fields can be included; attributes cannot

A non-numeric page[limit] or page[offset] does not return a 400. The invalid value is silently ignored and the default applies. If your limit doesn’t seem to take effect, check its spelling and value before suspecting the data.

An unknown all-lowercase query parameter violates JSON:API’s parameter naming rules and is rejected (verified): The following query parameters violate the JSON:API spec: 'cb'. A name carrying a non-lowercase character (langCode, say) is an implementation-specific parameter under those same rules: it passes validation and does nothing unless something on the site implements it (see Language). Either way there is no cache-busting via made-up parameters.

Used in: querying guide → troubleshooting.

Was this page helpful?