Where each rule works — storefront vs checkout
The two places your rules are evaluated, exactly which selectors each supports, and why a restriction or discount can silently ignore a rule.
The single most common "my rule isn't working" surprise comes from one fact: your rules are evaluated in two different places, and the two places do not support the same set of rule types. A rule that works perfectly in a storefront widget can be silently ignored by a cart restriction or an automatic checkout discount. This page is the full reference for which selectors work where, why the difference exists, and what to use instead at checkout.
If you only remember one thing: storefront sees every rule; checkout sees a subset. When a restriction or a checkout discount looks like it's ignoring one of your rules, the rule type is almost certainly unsupported at checkout.
Two places rules are evaluated
Your rule groups are run by two completely separate engines depending on where the work happens.
1. The storefront engine (full power)
Storefront widgets, the Llama Cart, and the app proxy all run the full JavaScript evaluation engine. This engine:
- Supports all 22+ selectors, including URL, UTM, referrer, cart attribute, and promo code.
- Can filter line items and carry the filtered set forward between rule groups — this is the "filter then measure" behavior described in Complex conditions & recipes.
- Has live access to cart data, the page URL, applied discount codes, and customer information at the moment the widget renders.
- Honors configurable
AND/ORconnectors at both levels (within a group and between groups).
This is the engine behind your upsell offers, auto-add campaigns, gift-purchase progress bars, and everything else a shopper sees on the storefront.
2. The checkout engine (Shopify Function / WASM)
Cart restrictions and automatic product discounts do not run your storefront code. They run as Shopify Function extensions compiled to WebAssembly (WASM) on Shopify's own infrastructure. That gives them speed and reliability — they enforce even when JavaScript is disabled, on headless storefronts, and through express buttons like Shop Pay and Apple Pay — but it comes with hard constraints:
- At checkout there is no backend access at runtime. The function cannot call the app's server, hit an API, or load the page URL.
- Instead, the function reads a compressed configuration from a Shopify metafield that the app syncs ahead of time. Anything that isn't in that metafield, or that needs live lookups, simply cannot be evaluated.
- Because of this, the checkout engine supports only a subset of selectors.
A cart restriction or a checkout discount that appears to ignore one of your rules is almost always using a rule type the checkout engine doesn't support. The rule is still saved — it just has no effect at checkout. Check the support matrix below and swap in a supported selector.
Selector support matrix
Every selector and where it works. Use the storefront column for campaigns, widgets, and Llama Cart; use the checkout column for cart restrictions and automatic product discounts.
| Selector (rule name in the editor) | Storefront / campaigns | Checkout (restrictions & discounts) |
|---|---|---|
| Specific variants | Yes | Yes |
| Product tag | Yes | Yes |
| Product SKU | Yes | Yes |
| Product type | Yes | Yes |
| Line attribute | Yes | Yes |
| Cart subtotal | Yes | Yes |
| Cart total (original price) | Yes | Yes |
| Specific country | Yes | Yes |
| Per-SKU quantity validation | Yes | Yes |
| Filtered products subtotal | Yes | Yes |
| Filtered products total (original price) | Yes | Yes |
| Filtered products quantity | Yes | Yes |
| Customer tag | Yes | Yes |
| Collection | Yes | No |
| Vendor | Yes | No |
| Product title | Yes | No |
| URL | Yes | No |
| UTM parameter | Yes | No |
| Referrer (ref) | Yes | No |
| Cart attribute | Yes | No |
| Promo code | Yes | No |
The supported-at-checkout set covers variants, product tag, SKU, type, line attribute, cart subtotal, cart total, country, per-SKU quantity, the three filtered-product totals, and customer tag. Everything else — collection, vendor, title, URL, UTM, referrer, cart attribute, and promo code — is storefront only.
Behavior differences in the checkout engine
Even for the selectors that are supported, the checkout engine behaves differently from the storefront. These differences explain a few "but I set AND, why did it act like OR?" surprises.
Connectors are handled differently
On the storefront you choose AND or OR at both levels. In the checkout (WASM) engine the logic is fixed:
- Within a group, rules are always combined with AND — every rule in the group must match.
- Between groups, groups are combined with OR — any group matching is enough.
If you build a multi-group restriction expecting the groups to be joined with AND, the checkout engine joins them with OR instead. To require several independent conditions at checkout, put them as separate rules inside a single group (where they're AND-ed), rather than across multiple groups.
The product filter pre-pass
The checkout engine doesn't carry filtered items forward group-by-group the way the storefront does. Instead it runs a single pre-pass that scans the cart for items matching your product filter rules — Specific variants, Product tag, Product SKU, and Product type — and collects the set of matching variant IDs. Your filtered-product aggregate rules (filtered subtotal, filtered total, filtered quantity) are then measured against that collected set.
The practical takeaway is the same as on the storefront: a product filter narrows the items, and the filtered-product rules measure only those items. But note the pre-pass only recognizes those four product filters. A Collection or Vendor filter contributes nothing at checkout, because those selectors aren't supported there.
The auto-injected "fake quantity rule"
There's one piece of automatic behavior worth understanding. If a restriction has product filter rules (variants, tag, SKU, or type) but no filtered-product aggregate rule (no filtered quantity, filtered subtotal, or filtered total), the engine quietly adds a synthetic rule for you:
- If your product filter uses an equality-style match, it injects filtered products quantity is greater than or equal to 1.
- Otherwise it injects filtered products quantity is less than or equal to 0.
This makes the restriction fire whenever matching products exist in the cart — which is almost always what you want from a bare "block carts that contain this product" rule. You don't configure this; it just means a product-only restriction still triggers correctly without you having to add an explicit quantity threshold.
Multi-currency is resolved inline
Price rules (cart subtotal, cart total, and the filtered-product totals) can hold a different threshold per currency. On the storefront the value is resolved before evaluation. At checkout the WASM engine resolves it inline, picking the shopper's active market currency — using your explicit per-currency amount if you set one, otherwise converting your base amount by the market's exchange rate. The behavior matches the storefront; it just happens at a different moment. For round-number thresholds across markets, set the amount explicitly per currency (for example, set £45 rather than relying on an auto-converted figure).
Existing-offer items are excluded the same way
When the engine measures filtered-product totals, it can exclude items that were already added by an offer (the ignore existing offers behavior) so an auto-added product doesn't satisfy its own trigger. At checkout this is done by passing the collected restricted-variant IDs into the same filtering logic, so the outcome is consistent with the storefront.
Substitutions to use at checkout
When a rule you need is storefront-only, swap it for a supported equivalent before relying on it in a restriction or a checkout discount.
| If you wanted to target by… | Use this at checkout instead |
|---|---|
| Collection | Product tag or Product SKU — tag the collection's products and filter on the tag. |
| Vendor | Product tag or Product type — tag products by brand, or match on type. |
| Product title | Product tag or Product type — match a tag/type instead of the title text. |
Some rules have no checkout equivalent at all, because the data simply isn't available to a Shopify Function at runtime:
- UTM parameter, Referrer (ref), and URL — these read the page the shopper arrived from, which the checkout function never sees.
- Cart attribute — only line attribute is supported at checkout, not cart-level attributes.
- Promo code — the applied code isn't resolvable inside the checkout function.
You cannot gate a cart restriction or an automatic checkout discount on UTM, referrer, URL, a cart attribute, or a promo code. If your campaign depends on one of these, keep that logic on the storefront (a widget or auto-add campaign) and don't expect a restriction or checkout discount to enforce it.
Why discounts never re-evaluate conditions
Automatic product discounts deserve a special note, because their behavior surprises people who assume the discount re-checks the rules at checkout. It doesn't.
When a storefront widget adds an offer product to the cart, it stamps that cart line with a _lcu_condition_id line attribute identifying which condition produced it. The product-discount extension at checkout then:
- Reads the
_lcu_condition_idattribute from each cart line. - Looks up that condition and variant in the discount maps the app synced to the metafield (the condition-settings map and discount-settings map).
- Applies the matching discount (percentage or fixed amount, adjusted for the shopper's currency).
At no point does it re-run your trigger rules. The discount follows the line attribute stamp, not a fresh rule evaluation.
This is why a discount can look like it's "ignoring" a condition rule: the discount isn't checking that rule at all. It only applies to lines that were added with the matching _lcu_condition_id stamp. If a shopper removes and re-adds a product through a path that doesn't stamp the attribute, the discount won't attach — and changing a storefront-only rule on the condition has no effect on the checkout discount.
Troubleshooting
Key takeaways
- Rules run in two engines: the storefront JavaScript engine (all 22+ selectors) and the checkout Shopify Function engine (a supported subset, reading a synced metafield with no backend access).
- Supported at checkout: specific variants, product tag, SKU, product type, line attribute, cart subtotal, cart total, country, per-SKU quantity, the three filtered-product totals, and customer tag.
- Storefront only: collection, vendor, product title, URL, UTM, referrer, cart attribute, and promo code.
- At checkout, rules within a group are AND-ed and groups are OR-ed — different from the configurable storefront connectors.
- Automatic discounts apply by the
_lcu_condition_idline-attribute stamp and the synced discount maps; they never re-evaluate your trigger rules.
Related
Cart restrictions
Block or warn shoppers at the cart and checkout based on cart conditions.
Discounts overview
The discount types in Llama Upsells and how they apply automatically at checkout.
Condition selectors reference
Every rule selector and matcher available in restrictions and campaigns.
Complex conditions & recipes
Rule groups, filter-then-measure logic, and the edge cases that decide whether a campaign shows.
Matchers reference & comparison behavior
Exactly how each of the 12 matchers compares values — case-sensitivity, range bounds, empty-value rules, and the edge cases that explain "why didn't my rule match?".
Targeting by schedule, market & customer tags
Overview of the three campaign-level targeting filters — schedule, markets, and customer tags — with links to the in-depth guides.
