Predicates provide a way to define complex expressions for querying resources or specifying conditional triggers for API Extensions.
where
query parameter that accepts a predicate expression to determine whether a specific resource representation should be included in the result.condition
field in the ExtensionTrigger.The structure of predicates and the names of the fields follow the structure and naming of the fields in the documented response representation of the respective query results.
UTF-8
and the predicate must be URL-encoded in HTTP requests.Example predicate:
# decoded predicate:
masterData(current(slug(en="super-product") and name(en="Super Product")))
# URL-encoded predicate:
masterData%28current%28slug%28en%3D%22super-product%22%29+and+name%28en%3D%22Super+Product%22%29%29%29
Query Predicates by example
on standard and Custom Fields
// Compare a field's value to a given value
name = "Peter" // For exact match to "Peter". This does not perform substring match.
name != "Peter"
age < 42
age > 42
age <= 42
age >= 42
age <> 42
// Combine any two conditional expressions in a logical conjunction / disjunction
name = "Peter" and age < 42
name = "Peter" or age < 42
// Negate any other conditional expression
not (name = "Peter" and age < 42)
// Check whether a field's value is or is not contained in
// a specified set of values.
age in (42, 43, 44)
age not in (42, 43, 44)
// to be noted: 'in' is much more efficient than several '='
// prefer:
name in ("Peter", "Barbara")
// to:
name = "Peter" or name = "Barbara"
// Check whether an array contains all or any of a set of values
tags contains all ("a", "b", "c")
tags contains any ("a", "b", "c")
// Check whether an array is empty
tags is empty
// Check whether a field exists & has a non-null value
name is defined
name is not defined
// Descend into nested objects
dog(age < 7 and name = "Beethoven")
// Descend into nested arrays of objects
cities(zip > 10000 and zip < 20000)
// Query GeoJSON field within a circle
// The two first parameters are the longitude and latitude of the circle's center.
// The third parameter is the radius of the circle in meter.
geoLocation within circle(13.37770, 52.51627, 1000)
// To query for resources with Custom Fields
// enclose the Custom Field with 'custom(fields(<name>:<value>))'
// where <name> is as defined in the FieldDefinition and <value> is compliant to the FieldType of the Custom Field
// name: "description", FieldType: CustomFieldStringType
custom(fields(description="example description"))
on Product Attributes
// for missing attribute
variants(not(attributes(name="attribute-name")))
// for single attribute value of TextType
variants(attributes(name="attribute-name" and value="attribute-value"))
// for multiple attribute values of TextType with same name
variants(attributes(name="attribute-name" and value in ("attribute-value-1", "attribute-value-2")))
// for single attribute value of LTextType
variants(attributes(name="attribute-name" and value(en="attribute-value")))
// for multiple attribute values of LTextType with same name
variants(attributes(name="attribute-name" and value(en="english-value" or de="german-value")))
// for EnumType or LocalizableEnumType
variants(attributes(name="attribute-name" and value(key="enum-key")))
// for MoneyType (currencyCode is required)
variants(attributes(name="attribute-name" and value(centAmount=999 and currencyCode="EUR")))
// for MoneyType with centAmount within a specific range (currencyCode is required)
variants(attributes(name="attribute-name" and value(centAmount > 999 and centAmount < 1001 and currencyCode="EUR")))
// for NumberType
variants(attributes(name="attribute-name" and value=999))
// for NumberType with value within a specific range
variants(attributes(name="attribute-name" and value > 999 and value < 1001 ))
// for DateType, TimeType, or DateTimeType
variants(attributes(name="attribute-name" and value="attribute-value"))
// for DateType, TimeType, or DateTimeType with a value within a specific range
variants(attributes(name="attribute-name" and value > "value-start" and value < "value-end"))
// for ReferenceType
variants(attributes(name="attribute-name" and value(typeId="reference-type-id" and id="reference-id")))
masterVariant
instead of variants
:// for single attribute value of TextType
masterVariant(attributes(name="attribute-name" and value="attribute-value"))
masterVariant
and variants
predicates:// for single attribute value of TextType
masterVariant(attributes(name="attribute-name" and value="attribute-value")) or variants(attributes(name="attribute-name" and value="attribute-value"))
masterData(current({example}))
or masterData(staged({example}))
.// for current data
masterData(current(variants(attributes(name="attribute-name" and value=999))))
// for staged data
masterData(current(masterVariant(attributes(name="attribute-name" and value="attribute-value"))))
A query endpoint usually restricts predicates to only be allowed on a specified subset of a resource representation's fields. The documentation of the endpoint lists fields that can be used for constructing predicates.
where
query parameters, the individual predicates are combined in a logical conjunction, just as if
they had been specified in a single where
query parameter and combined with and
.# decoded predicate
masterData(current(slug(en="peter-42") and name(en="Peter")))
# URL-encoded predicate
masterData%28current%28slug%28en%3D%22peter-42%22%29%20and%20name%28en%3D%22Peter%22%29%29%29
on Shipping Methods
active
, createdAt
, createdBy
, custom
, description
, id
, isDefault
, key
, lastModifiedAt
, lastModifiedBy
, name
, predicate
, taxCategory
, version
, zoneRates
.on Carts
anonymousId
, billingAddress
, businessUnit
, cartState
, country
, createdAt
, createdBy
, custom
, customLineItems
, customerEmail
, customerGroup
, customerId
, deleteDaysAfterLastModification
, discountCodes
, id
, inventoryMode
, itemShippingAddresses
, key
, lastModifiedAt
, lastModifiedBy
, lineItems
, locale
, origin
, paymentInfo
, shipping
, shippingAddress
, shippingCustomFields
, shippingInfo
, shippingRateInput
, store
, taxCalculationMode
, taxMode
, taxRoundingMode
, taxedPrice
, totalPrice
, version
.custom
, discountedPrice
, discountedPricePerQuantity
, distributionChannel
, id
, name
, price
, productId
, productKey
, productType
, quantity
, state
, supplyChannel
, taxRate
, variant
.custom
, discountedPrice
, discountedPricePerQuantity
, money
, name
, quantity
, slug
, state
.on Cart Discounts
createdAt
, createdBy
, custom
, description
, id
, isActive
, key
, lastModifiedAt
, lastModifiedBy
, name
, references
, requiresDiscountCode
, sortOrder
, stackingMode
, stores
, target
, validFrom
, validUntil
, value
, version
.// query for Cart Discounts with total price discount as target
"target(type="totalPrice")"
on Customers
id
, createdAt
, lastModifiedAt
, customerNumber
, email
, lowercaseEmail
, stores
, firstName
, lastName
, middleName
, title
, addresses
, defaultShippingAddressId
, defaultBillingAddressId
, isEmailVerified
, externalId
, customerGroup
, customerGroupAssignments
BETA, locale
, salutation
, key
.# decoded predicate
lowercaseEmail="peter@example.com"
# URL-encoded predicate
lowercaseEmail%3D%22peter%40example.com%22
on Orders
createdAt
, lastModifiedAt
, completedAt
, orderNumber
, customerId
, customerEmail
, anonymousId
, country
, totalPrice
, taxedPrice
, shippingAddress
, billingAddress
, customerGroup
, orderState
, shipmentState
, paymentState
, syncInfo
, returnInfo
, lineItems
, customLineItems
, cart
, paymentInfo
, state
, locale
, inventoryMode
, shippingRateInput
, shippingInfo
.custom
, discountedPrice
, discountedPricePerQuantity
, distributionChannel
, id
, name
, price
, productId
, productKey
, productType
, quantity
, state
, supplyChannel
, taxRate
, variant
.custom
, discountedPrice
, discountedPricePerQuantity
, money
, name
, quantity
, slug
, state
.// query for Orders that are awaiting stock
shipmentState="Backorder"
// query for Orders created in August 2022
createdAt > "2022-08-01T00:00:00.000Z" and createdAt < "2022-09-01T00:00:00.000Z"
on Shopping Lists
key
, name
, customer
, slug
, description
, lineItems
, textLineItems
, deleteDaysAfterLastModification
, anonymousId
, store
, custom
.id
, key
, name
, quantity
, addedAt
, description
, custom
.// query Shopping Lists of Customer with ID "657337d1-b0f3-4582-a1c1-c096c165f029"
customer(id = "657337d1-b0f3-4582-a1c1-c096c165f029")
// query Shopping Lists that have a TextLineItem with key "recommended-123"
textLineItems(key = "recommended-123")
on Custom Objects
value
on Custom Objects can be used as a predicate but is not checked for validity. value
can contain any number, string, boolean, array, object, or common API data type.The following examples are based on having a Custom Object with the following values:
{
"container": "example-container",
"key": "example-key",
"value": {
"exampleText": "This is example text",
"exampleNumber": 1234,
"exampleObject": {
"exampleBoolean": true,
"exampleArray": ["first", "second", "third"]
}
}
}
// Query Custom Objects for a field within value
value(exampleNumber > 1233)
// Query Custom Objects for a field within an object
value(exampleObject(exampleBoolean = true))
// Query Custom Objects for an array within an object
value(exampleObject(exampleArray is not empty))
Input variables
where
query parameter values.:
.var.
. The same input parameter can be passed multiple times to be used as an array of values.The actual names of the input variables must consist of alphanumeric characters only.
Input variable examples
HTTP query using one input variable:
# decoded:
?where=firstName = :name&var.name=Peter
# URL-encoded:
?where=firstName%20%3D%20%3Aname&var.name=Peter
HTTP query using an array input variable:
# decoded:
?where=masterVariant(sku in :skus) or variants(sku in :skus)&var.skus=sku1&var.skus=sku2&var.skus=sku3
# URL-encoded:
?where=masterVariant%28sku%20in%20%3Askus%29%20or%20variants%28sku%20in%20%3Askus%29&var.skus=sku1&var.skus=sku2&var.skus=sku3
Referencing input variables in Query Predicates:
// Compare a field's value to a given input variable value
name = :name
// Check whether a field's value is or is not contained in
// a specified set of input variable values.
age in :ages
age in (:age1, :age2, :age3)
age not in :ages
age not in (:age1, :age2, :age3)
// Check whether an array contains all or any of a set of input variable values
tags contains all :tags
tags contains all (:tag1, :tag2, :tag3)
tags contains any :tags
tags contains any (:tag1, :tag2, :tag3)
// Referencing an input variable multiple times
masterVariant(sku in :skus) or variants(sku in :skus)
Performance considerations
Query predicates are translated to database queries whose efficiency depends on how well the database can use indexes.
The automatic index creation needs to collect a significant amount of data to not optimize for outlier scenarios. That's why it can take up to two weeks before a new index is added.
Efficient queries can be fast on extremely large datasets and inefficient queries can be fast on small datasets, too. But inefficient query patterns on large datasets cause long-running and resource-intensive queries. Such queries can affect the overall performance of a Project.
Inefficient patterns
- Query Predicates on Attributes of Products and Product Projections: Since Attributes are generally not indexed for these query endpoints, such predicates are inefficient for Projects with a large number of Product Variants. Use Product Search or Product Projection Search instead.
- Fields nested inside arrays: The query becomes inefficient for predicates on arrays that contain many entries. For example,
variants(attributes(name = "attribute-name" and value = "attribute-value"))
. - Querying for a condition that is true for the majority of resources, for example
custom(state = "Done")
. - Negations, such as
state != "Open"
orstate is not defined
. - The
empty
operator on arrays, such aslineItems is empty
orlineItems is not empty
. - The
in
operator on arrays: The number of values provided in the predicate should not exceed 50 entries, for example:sku in (value1, value2 .. value50)
.
Efficient patterns
The following patterns are supporting efficient query execution:
- Non-nested fields that heavily reduce the subset of resources to filter, for example
custom(state = "WaitingForExternalApproval")
(assuming there are few resources waiting for external approval) - If possible, prefer equality over other operators. For example,
(state = "Done" or state = "Canceled)"
can be faster than(state != "Open")
in a query that contains further expressions. - Queries on Orders, Carts, Customers, etc. may be fast in the beginning, but slow down over time as your Project grows. Include a time range, for example
lastModifiedAt > $1-week-ago and ...
(replace$1-week-ago
with an actual date). Try defaulting the time range to the smallest value that is acceptable for the use case. Alternatively, try filtering by a field value that naturally only occurs in recently created resources.
Sorting and query performance
Sorting can also be supported by indexes. For best performance, the same index can be used for filtering and sorting. If possible, re-use a field from the Query Predicate for sorting.
lastModifiedAt > $1-week-ago
, sorting on lastModifiedAt
is advised since it is more performant than sorting on a different field, like id
.Deactivate calculating the total
total
field in the PagedQueryResult will improve the performance of the query. Whenever the total
is not needed, deactivate its calculation by using the query parameter withTotal=false
.Use predicates in conditional API Extensions
Unsupported operators
A few minor differences aside, the behavior of the language and the operators used are the same for both querying APIs and defining API Extension conditions. The features not supported in conditional API Extensions are:
- Input variables
- The
within-circle
operator
React to data changes with predicates
has changed
operator, you can ensure that the Extension is called only when certain properties are updated in the update action triggering the Extension. Given a valid predicate, the has changed
operator evaluates to true for all create actions.has changed
is also supported. Using has not changed
ensures that the Extension only triggers when certain properties do not change during the update action. Given a valid predicate, the has not changed
operator evaluates to false for all create actions.has changed
and has not changed
operators behave during create and update actions:// Evaluates to true if the name field is updated during the API call
name has changed
// Evaluates to true if the name field is not updated during the API call
name has not changed
// Evaluates to true if the resource is created with the name attribute
name has changed
// Evaluates to true if the quantities of existing Line Items change, or if new Line Items are added
lineItems(quantity has changed)
// Evaluates to true if a Line Item is added, removed, or updated
lineItems has changed
// Evaluates to false if the field shoeSize does not exist
shoeSize has changed
has changed
and has not changed
operators are only supported when defining API Extension conditions.