Filter expression syntax (KQL)
Some Query APIs accept an optional filter.expression argument for advanced filtering, using Kibana Query Language (KQL). This page documents the syntax and behavior that's common across every API that supports it. For the fields you can actually reference in expression — which differ per API — see that API's own reference page:
(More APIs will be added to this list as expression support rolls out to them.)
expression is not the same language as searchTerm. searchTerm uses Lucene query_string syntax and contributes to relevance scoring (it affects result order). filter.expression uses KQL and only decides which items match — it never affects their order. Use the separate sort argument to control ordering.
expressionis not validated ahead of time — an invalid KQL string is only caught when the query runs, and comes back as an error in the GraphQL response rather than an upfront validation message. For the full KQL grammar, treat the official KQL reference as authoritative.
Basic syntax
NOTE:
Reserved characters —
\ ( ) : < > " *— must be escaped with a backslash when used literally (e.g. inside an unquoted value), unless the whole value is wrapped in double quotes.Leading wildcards (e.g.
*ing) are disabled by default, same as insearchTerm.
| Construct | Example | Meaning |
|---|---|---|
| Equality / contains | status: ACTIVE | Field contains the value |
| Exact match | brand.keyword: "PnP Foods" | Field equals the value exactly (see .keyword fields) |
| AND / OR / NOT | status: ACTIVE AND NOT brand: PnP | Boolean combinators (case-insensitive keywords) |
| Grouping | (status: ACTIVE OR status: DISCONTINUED) AND brand: PnP | Parentheses control precedence |
| Multi-value shorthand | type: (GTIN13 GTIN14) | Same as type: GTIN13 OR type: GTIN14. The values inside ( ) must be bare and space-separated — type: (GTIN13 OR GTIN14) (a literal OR inside the list) is a parse error. To use OR explicitly, repeat the field name on both sides: type: GTIN13 OR type: GTIN14. |
| Range | netContent >= 1 AND netContent <= 5 | Also >, < |
| Existence | conditionIds: * | Field has any value set |
| Wildcard | itemCategoryId.keyword: default-dataset-cat* | * matches zero or more characters; ? is not supported (unlike searchTerm) |
| Grouped match | additionalProperties: { id: Color AND value: Blue } | Match against a single entry of a list field — see Grouped matching on list fields |
.keyword vs regular fields
Every text field has a paired .keyword variant, exactly like searchTerm's .keyword fields:
name: "paper towel"— matches if that phrase appears anywhere inname.name.keyword: "paper towel"— matches only ifnameis exactly that string (case-sensitive).- Wildcards should generally target
.keywordfields (e.g.itemCategoryId.keyword: cat-*) to get predictable prefix/substring behavior.
Some .keyword fields have a maximum length they'll match against — check the field reference of the specific API you're using.
Grouped matching on list fields
Many entities have fields that are lists of objects — for example, additionalProperties, a set of customer-specific key/value pairs found on several entities. For these, group the conditions that must apply to the same list entry inside <field>: { ... }:
additionalProperties: { id: Color AND value: Blue } AND additionalProperties: { id: Size AND (value: S OR value: M) }
This matches items that have one additional property with id: Color, value: Blue and (possibly a different) additional property with id: Size and value of S or M.
NOTE:
The query is repeated per entry — writing
additionalProperties: { id: Color AND value: Blue AND id: Size AND value: S }would incorrectly require a single entry to have two differentids at once.
Not every list-of-objects field supports this grouping syntax — check the specific API's field reference for which ones do.
Escaping
expression accepts plain KQL text. KQL uses double quotes for phrase matches (e.g. description: "heavy duty fabric") and backslashes to escape reserved characters (e.g. a literal \() — send those characters as-is, with whatever escaping your GraphQL client normally applies for a string value (e.g. \" and \\ in a JSON variables payload), and nothing more.
Concretely, to filter on the exact phrase description: "heavy duty fabric", send this in your GraphQL variables:
{
"expression": "description: \"heavy duty fabric\""
}
Some APIs may still accept an older, no-longer-required extra level of escaping for backward compatibility — check that API's own reference page if you're unsure which format to use for a new integration (plain escaping, as shown above, is always the right choice for new code).
Case sensitivity
- Matches against
.keywordfields, and fields holding fixed values (e.g. status or type enums), are case-sensitive —status: activewill not match an item whose status isACTIVE. - Matches against regular (non-
.keyword) fields are effectively case-insensitive.
Dates and time zones
Date comparisons and date math (e.g. created > now-2d) are evaluated in UTC. Prefer explicit dates over relative date math to avoid ambiguity.
A bare date like
validFrom <= 2026-07-17needs no quoting (it contains no reserved characters). A full timestamp like2026-07-17T00:00:00.000Zcontains:, which is a reserved KQL character, so it must be quoted:validFrom <= "2026-07-17T00:00:00.000Z"— see Escaping for how to send the quotes.
Bare terms
A term with no field: prefix (e.g. expression: "paper") is matched against a default set of fields rather than one you chose. This is both imprecise and comparatively expensive. Always qualify expression terms with an explicit field name.
Limitations that apply to every API
- No upfront validation. An invalid
expressionisn't caught until the query runs — it comes back as an error in the GraphQL response, not a schema-level validation message. - Match-only, no relevance scoring.
expressioncannot influence result order or apply a boost; it only decides matches. Usesortto control ordering. - Different language from
searchTerm. KQL has no fuzzy matching (~), proximity search ("..."~N), boosting (^), or regex (/…/) — those aresearchTerm-only features. Don't mix the two syntaxes. - Leading wildcards disabled (e.g.
*ing), same restriction assearchTerm. field: (A OR B)is invalid. A literalORinside the multi-value shorthand list is a parse error — see the multi-value shorthand row above.- Older filter arguments may combine additively with
expression. Where an API'sfilterinput still has older, single-purpose fields alongsideexpression(typically marked deprecated), supplying both means they're AND-ed together, which can produce confusing double-filtering. Prefer expressing everything throughexpressionalone.
Each API may have additional limitations of its own — check that API's field reference page.