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:

  • validFrom is absent or less than or equal to now
  • validUntil is 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.

Code in text

Complete field reference

FieldTypeNotes
skustringVariant SKU.
country_codestring or nullPrice country dimension.
channel.idID or nullPrice channel dimension (ch_...).
customer_group.idID or nullPrice customer-group dimension (cusgrp_...).
price.idIDPrice record (price_...).
price.amountintegerPrice value in minor currency units.
price.currencystringISO currency code.
product.idIDProduct (product_...).
product.namestringProduct name.
product.slugstringProduct slug.
product.collectionscollectionAssigned collections.
variant.idIDProduct variant (vid_...).
variant.namestringVariant 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.

Code in typescript

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.

Code in typescript

Practical recipes

Code in typescript

Validation checklist

  • Use snake_case property names and a boolean expression.
  • Compare typed IDs with their real prefixes: ch_, cusgrp_, product_, vid_, price_, and col_.
  • Treat strings as case-sensitive and copy values from the API.
  • Pair price.amount with price.currency and 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 true makes that intent explicit.