Product predicate language
A product discount’s predicate selects individual product-variant price records. Thor combines each price with its variant, product, collections, and optional channel, country, and customer-group dimensions, then evaluates the expression.
This is different from filtering a list of products and different from a cart predicate. A single variant can have several price records; some can match while others do not.
See Predicate language for common operators and Discounts for priority and discount application behavior.
Evaluation model
Thor builds one predicate context for every product-variant price whose validity window includes the current time:
validFromis absent or less than or equal to nowvalidUntilis absent or greater than or equal to now
The product discount’s own validFrom and validUntil determine whether the discount is active. The price record’s validity determines whether that price is considered for predicate matching. These are separate time windows.
Complete field reference
| Field | Type | Notes |
|---|---|---|
sku | string | Variant SKU. |
country_code | string or null | Price country dimension. |
channel.id | ID or null | Price channel dimension (ch_...). |
customer_group.id | ID or null | Price customer-group dimension (cusgrp_...). |
price.id | ID | Price record (price_...). |
price.amount | integer | Price value in minor currency units. |
price.currency | string | ISO currency code. |
product.id | ID | Product (product_...). |
product.name | string | Product name. |
product.slug | string | Product slug. |
product.collections | collection | Assigned collections. |
variant.id | ID | Product variant (vid_...). |
variant.name | string | Variant name. |
Each item in product.collections exposes id, name, and slug.
Product tags and categories are not present in this predicate context. Use a collection, product ID, slug, SKU, or another available field when defining a product-discount audience.
Context dimensions and nulls
Channel, customer group, and country belong to the price record. A broadly available base price can have null for these dimensions. Guard a nullable dimension when the rest of the expression reads or combines it.
A predicate that explicitly compares channel.id to ch_... only matches channel-specific price records. It does not also match a contextless price that may be eligible as a storefront fallback.
Collections
Use .any(...) to match a collection by stable ID or slug. IDs are safest across renames; slugs are useful when they are deliberately managed as stable business identifiers.
Amounts and currencies
price.amount is an integer minor-unit amount in price.currency. For example, 9000 represents EUR 90.00 for an EUR price. Always include a currency condition when comparing amounts across a catalog with more than one currency.
Practical recipes
Validation checklist
- Use snake_case property names and a boolean expression.
- Compare typed IDs with their real prefixes:
ch_,cusgrp_,product_,vid_,price_, andcol_. - Treat strings as case-sensitive and copy values from the API.
- Pair
price.amountwithprice.currencyand use minor units. - Remember that the expression evaluates price records, so channel, country, and customer group describe the price—not the shopper currently browsing.
- Test both contextual and contextless prices, and prices at the boundaries of their validity windows.
- An empty product predicate is normalized to an always-true expression, but writing
truemakes that intent explicit.