Targeting & conditions

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/OR connectors 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 / campaignsCheckout (restrictions & discounts)
Specific variantsYesYes
Product tagYesYes
Product SKUYesYes
Product typeYesYes
Line attributeYesYes
Cart subtotalYesYes
Cart total (original price)YesYes
Specific countryYesYes
Per-SKU quantity validationYesYes
Filtered products subtotalYesYes
Filtered products total (original price)YesYes
Filtered products quantityYesYes
Customer tagYesYes
CollectionYesNo
VendorYesNo
Product titleYesNo
URLYesNo
UTM parameterYesNo
Referrer (ref)YesNo
Cart attributeYesNo
Promo codeYesNo

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
CollectionProduct tag or Product SKU — tag the collection's products and filter on the tag.
VendorProduct tag or Product type — tag products by brand, or match on type.
Product titleProduct 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:

  1. Reads the _lcu_condition_id attribute from each cart line.
  2. Looks up that condition and variant in the discount maps the app synced to the metafield (the condition-settings map and discount-settings map).
  3. 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_id line-attribute stamp and the synced discount maps; they never re-evaluate your trigger rules.

On this page