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?".
Every condition rule is a selector (the cart or customer data to check), a matcher (how to compare it), and a value. The selector decides what gets compared; the matcher decides how. This page is the deep reference for the matcher half — the subtle comparison behavior that explains most "but my rule should have matched!" surprises.
For the list of selectors and which data each one reads, see the Condition selectors reference. For combining rules into groups and the filter-then-measure pattern, see Complex conditions & recipes.
The display names below are exactly what you see in the rule builder dropdown. Two matchers, a strict "greater than" and a strict "less than", exist in the engine but are not exposed in the UI — the builder only offers the "or equal to" variants. See the edge cases section at the end.
The 12 matchers
Each matcher below lists its display name, what it compares, the value types it applies to, whether the comparison is case-sensitive, and how the value field behaves when you pick it.
is equal to
- What it compares: an exact match. Values are coerced to strings and compared with loose equality, so the number
50and the text50are treated as the same. - Applies to: strings (tag, vendor, title, SKU, type), numbers (quantities), prices (subtotal, total, filtered totals), collections, and resources (product).
- Case-sensitivity: case-sensitive for plain string comparisons.
Summerdoes not equalsummer. - Value input: a single text, number, currency, or resource field depending on the selector.
is equal to is the only string matcher that cares about letter case. Red will not match a Product vendor stored as red. If you are unsure how a value is capitalized in Shopify, prefer contains or starts with, which lowercase both sides before comparing. Note the Product tag exception: is equal to on a tag is case-insensitive (both sides are lowercased), so tag Red does match red.
equals any
- What it compares: nothing specific — it is a wildcard. On the Product selector it matches if there is any line item in the cart.
- Applies to: the Product (variants) selector only.
- Case-sensitivity: not applicable — no string is compared.
- Value input: the value is left empty for the Product wildcard. No specific variant needs to be selected.
On the Product selector, "any line item in the cart" is measured after the ignore-existing-offers filter runs. An item this same condition already auto-added doesn't count toward the wildcard.
is not equal to
- What it compares: the inverse of
is equal to— loose inequality on string-coerced values. - Applies to: strings (vendor, customer tag, attributes), numbers, prices, collections, and the country selector.
- Case-sensitivity: mirrors
is equal to— case-sensitive for plain string comparisons. - Value input: a single field matching the selector type.
is greater or equal to
- What it compares:
value ≥ your number, evaluated numerically. - Applies to: numbers (quantities, per-SKU quantity) and prices (subtotal, total, filtered totals).
- Case-sensitivity: not applicable.
- Value input: a number or currency field.
Both sides are parsed as numbers. If the value can't be parsed (it's blank or non-numeric), it falls back to 0 rather than failing loudly. A misconfigured "greater or equal to" rule with an empty box behaves like "greater or equal to 0", which almost always passes.
is less or equal to
- What it compares:
value ≤ your number, evaluated numerically. - Applies to: numbers and prices.
- Case-sensitivity: not applicable.
- Value input: a number or currency field. The same non-numeric →
0fallback applies.
is between
- What it compares: a numeric range. The rule passes when
min ≤ value < max. - Applies to: prices (subtotal, total, filtered totals) — wherever a numeric range makes sense.
- Case-sensitivity: not applicable.
- Value input: two fields — a minimum and a maximum.
The lower bound is inclusive and the upper bound is exclusive. A range of 50–100 matches a cart of exactly 50 but not a cart of exactly 100. To include 100, set the maximum a little higher, for example 50–101. This single behavior is the most common cause of "the rule didn't fire at my round-number threshold."
On a multi-currency store, each bound converts independently. A 50–100 range set in your base currency becomes (50 × rate) to (100 × rate) for a shopper checking out in another market's currency, unless you set explicit per-currency amounts.
starts with
- What it compares: whether the value begins with your text.
- Applies to: strings (tag, title, SKU, type, ref) and the promo code selector.
- Case-sensitivity: case-insensitive — both the cart value and your text are lowercased before comparing.
GIFT-matches a SKU ofgift-001. - Value input: a single text field.
ends with
- What it compares: whether the value ends with your text.
- Applies to: strings (tag, title, SKU, type).
- Case-sensitivity: case-insensitive — both sides are lowercased.
- Value input: a single text field.
contains
- What it compares: whether your text appears anywhere inside the value (a substring match).
- Applies to: strings (tag, title, SKU, type, ref), the UTM selector, and the URL selector — which only supports this matcher.
- Case-sensitivity: case-insensitive — both sides are lowercased.
- Value input: a single text field (or a multi-value tag input on UTM).
does not contain
- What it compares: whether your text does not appear inside the value.
- Applies to: strings (tag, title, SKU, type, ref) and the UTM selector.
- Case-sensitivity: case-insensitive.
- Value input: a single text field.
does not contain only matches when your search text has length greater than zero. An empty "does not contain" box does not match everything — it matches nothing. This guard prevents a blank rule from silently passing every cart.
On the Product tag selector, does not contain inverts at the item level: a line item is excluded if any of its tags contains your search text. So "tag does not contain clearance" drops any product carrying a tag like clearance-2024, not just an exact clearance tag.
is not empty
- What it compares: that the field has a value (a truthy check).
- Applies to: strings — tag, SKU, type, ref. (It is not offered on Product title.)
- Case-sensitivity: not applicable.
- Value input: disabled — no value is needed, so the field is greyed out.
- For tags: passes when the line item has at least one tag.
is empty
- What it compares: that the field has no value (a falsy check).
- Applies to: strings — tag, SKU, type, ref.
- Case-sensitivity: not applicable.
- Value input: disabled — no value is needed.
- For tags: passes when the line item's tag list is empty.
Which matchers each selector allows
Selectors don't expose all 12 matchers. Each selector is wired to a fixed matcher group. The table below maps the internal groups to the selectors that use them, so you know exactly which options appear in the dropdown.
| Matcher group | Matchers offered | Selectors that use it |
|---|---|---|
| Price | is equal to, is not equal to, is greater or equal to, is less or equal to, is between | Cart subtotal, Cart total (original price), Filtered products subtotal, Filtered products total |
| Numeric | is equal to, is not equal to, is greater or equal to, is less or equal to | Filtered products quantity, Per-SKU quantity (validation) |
| String | is equal to, ends with, starts with, contains, does not contain, is not empty, is empty | Product tag, Product SKU, Product type |
| Product title | is equal to, ends with, starts with, contains, does not contain | Product title (note: no is not empty / is empty) |
| Product vendor | is equal to, is not equal to | Product vendor, Collection |
| Attributes | is equal to, is not equal to | Cart attribute, Line item attribute |
| Customer tag | is equal to, is not equal to | Customer tag |
| Resources | is equal to, equals any | Product (variants) |
| URL | contains | Page URL |
| UTM | is equal to, is not equal to, contains, does not contain | UTM |
| Ref | is equal to, is not equal to, contains, does not contain, is not empty, is empty | Ref |
| Promo code | is equal to, starts with | Promo code |
| Specific countries | is equal to, is not equal to | Specific country |
| Per-SKU quantity | is greater or equal to, is less or equal to | Per-SKU quantity validation |
The Product title group is deliberately missing is not empty and is empty — every product has a title, so those checks would be meaningless. The full String group (tag, SKU, type) keeps them because those fields can legitimately be blank.
Edge cases that trip people up
These are the behaviors that most often produce "why didn't my rule match?" tickets. Each one is real engine behavior, not a UI quirk.
Worked examples
Cart between $50 and $100
Use one rule: Cart subtotal → is between → minimum 50, maximum 100.
Because the upper bound is exclusive, a cart of exactly $100 will not match. If you want $100 carts included, set the maximum to 101 instead. A cart of exactly $50 matches, because the lower bound is inclusive.
SKU starts with GIFT-
Use one rule: Product SKU → starts with → GIFT-.
This matcher is case-insensitive, so it matches SKUs stored as GIFT-001, gift-001, or Gift-Card. Compare with is equal to, which would only match an exact, same-case GIFT- string.
Tag does not contain clearance
Use one rule: Product tag → does not contain → clearance.
On the tag selector this excludes any line item whose tags include the substring clearance anywhere — so clearance, clearance-2024, and final-clearance are all dropped. Keep the value box filled; a blank "does not contain" matches nothing.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Rule with is between ignores the round top number | Upper bound is exclusive | Raise the maximum (e.g. 50–101 to include 100). |
| is equal to on a SKU/vendor/title never matches | Case mismatch — is equal to is case-sensitive (except on Product tag, which is case-insensitive) | Switch to contains or starts with, or match the exact stored case. |
| does not contain with a blank box matches nothing | Empty search value is rejected by design | Enter the text you want to exclude. |
| is greater or equal to passes when it shouldn't | Empty / non-numeric value falls back to 0 | Enter a real number in the value box. |
| is between never fires | One or both bounds failed to parse and defaulted to 0 | Re-enter clean numbers in both fields. |
Related
Selector reference (every rule, in depth)
The canonical, selector-by-selector reference for every condition rule — data point, matchers, value format, case-sensitivity, and availability.
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.
