Segment query language
Customer segments are defined in SegmentQL, a small query language you edit
directly in the segment editor. If you have written a spreadsheet filter or a SQL
WHERE clause, you already know most of it. This page is the complete language
reference; for the list of every attribute you can filter on, see the
segment attribute reference, and for the
merchant-level guide see
Customers & segments.
The editor scaffold
The editor always shows a four-line query:
FROM customersSHOW customer_name, email, orders, amount_spentWHERE amount_spent >= 500 AND last_order_date > -30dORDER BY amount_spent DESCOnly the WHERE clause is editable — it is the segment definition.
FROM customers and SHOW are fixed (the SHOW columns mirror the preview
table below the editor), and ORDER BY is rewritten by the Sort by control
in the toolbar rather than typed. Keywords are case-insensitive when you type;
the editor prints them uppercase.
As you type, the editor offers context-aware completions (attributes, then the
operators valid for that attribute, then values), underlines invalid parts in
red with a hover explanation, and shows a live count above the editor —
Run (or Cmd/Ctrl + Enter) refreshes the matching-customers preview.
Counts above 10,000 display as 10,000+. A segment cannot be saved while the
query has errors.
Conditions and boolean logic
A condition is attribute operator value:
amount_spent > 1000customer_tags CONTAINS 'vip'last_order_date >= -90dCombine conditions with AND and OR. AND binds tighter than OR, so
amount_spent > 1000 AND orders_placed IS NOT NULL OR customer_tags CONTAINS 'vip'means (spent over 1,000 AND has ordered) OR is tagged vip. Use parentheses to group explicitly — they always win over precedence:
amount_spent > 1000 AND (customer_location.country = 'IN' OR customer_tags CONTAINS 'vip')There is no NOT (...) group operator. Negation is written per condition with
the negative operator forms: !=, NOT CONTAINS, NOT IN, NOT_MATCHES,
IS NOT NULL, NOT IN LAST.
Operators
Which operators an attribute accepts depends on its type — the attribute reference lists them per attribute, and the editor only ever suggests valid ones.
| Operator | Meaning | Example |
|---|---|---|
= / != | equals / not equals | customer.state = 'ENABLED' |
> >= < <= | numeric or date comparison | amount_spent >= 500 |
BETWEEN x AND y | inclusive range | amount_spent BETWEEN 100 AND 999.99 |
CONTAINS / NOT CONTAINS | list membership, or substring on text | customer_tags CONTAINS 'wholesale' |
CONTAINS ALL (...) / CONTAINS ANY (...) | every / at least one of the listed values | customer_tags CONTAINS ALL ('vip', 'newsletter') |
STARTS WITH / ENDS WITH | text prefix / suffix | customer.email ENDS WITH '@gmail.com' |
IN (...) / NOT IN (...) | one of a list of values | customer_location.country IN ('IN', 'AE') |
IS NULL / IS NOT NULL | value absent / present | customer.birthday IS NOT NULL |
IN LAST n UNIT / NOT IN LAST n UNIT | within the last n days/weeks/months/years | last_order_date IN LAST 90 DAYS |
IN NEXT n UNIT | within the coming n days/weeks/months/years | loyalty MATCHES (next_expiration_date IN NEXT 30 DAYS) |
IN MONTH | recurring date falls in a month | customer.birthday IN MONTH May |
ON month day | recurring date falls on a day | customer.birthday ON May 24 |
MATCHES (...) / NOT_MATCHES (...) | function attributes — see below | products_purchased MATCHES (id = '…', quantity >= 2) |
On function attributes, IS NOT NULL means has ever and IS NULL means
never — for example orders_placed IS NULL is “has never placed an order”.
Values
Text
Single-quoted. Escape a quote as \' (typing '' also works; the editor
prints \').
customer.note CONTAINS 'says \'call after 6\''Text comparison is case-sensitive, with three deliberate exceptions: tag
values, discount codes, and color metafields match case-insensitively.
CONTAINS on text attributes is a case-insensitive substring match;
CONTAINS on list attributes (like customer_tags) is an exact-member match.
Numbers and money
Plain numbers with a . decimal separator and no thousands separators. Money
values are written in major currency units — amount_spent > 500 means
₹500 (or your store currency), not 500 paise.
Booleans
true / false, unquoted:
product_review MATCHES (has_video = true)Dates and times
| Form | Example | Notes |
|---|---|---|
| Absolute date | 2026-05-25 | yyyy-mm-dd |
| Absolute date-time | 2026-05-25T18:30:00 | interpreted in the store timezone |
| Offset | -30d, -4w, -6m, -1y, +7d | units: d w m y, relative to now |
| Named | today, yesterday, tomorrow | resolved daily in the store timezone |
Offsets and named dates are dynamic: a segment using last_order_date > -30d
re-evaluates every day, so membership stays current without edits.
Measurements
Dimension, volume, and weight metafields take a number followed by a unit;
values are compared with unit conversion (a 5 cm condition matches a value
stored as 50 mm):
metafields.specs.max_width <= 5 cmmetafields.specs.capacity BETWEEN 250 AND 750 mlResource IDs and named values
Attributes that reference products, variants, collections, locations, brands,
or email campaigns take the resource’s ID — and you never have to type one:
the editor’s autocomplete searches by name and inserts the ID for you, and
hovering an ID shows the resource title. The same name-based suggestions cover
tag values (customer_tags, products_purchased.tag) and taxonomy categories
(products_purchased.category), which insert the quoted value instead of an
ID.
Function attributes and MATCHES
Function attributes describe related records — orders, purchased products,
reviews, gift cards, event tickets, and so on. Their conditions go inside
MATCHES ( ... ), separated by commas (a comma means AND; OR is not allowed
inside the parentheses):
orders_placed MATCHES (count >= 3, date >= -6m)products_purchased MATCHES (tag = 'sportswear', sum_quantity >= 3)product_review MATCHES (product_id = '018f2e6a-…', has_video = true)gift_cards MATCHES (balance > 0, next_expiry_date IN NEXT 14 DAYS)All conditions inside one MATCHES scope apply to the same related record
(or the same aggregation over them) — “rated this product below 3 stars” is
one scope:
product_review MATCHES (product_id = '018f2e6a-…', rating <= 2)NOT_MATCHES ( ... ) (also accepted as NOT MATCHES) selects customers with
no related record satisfying the scope. IN ( ... ) value lists are
allowed on ID parameters inside a scope:
products_purchased MATCHES (id IN ('018f…', '018e…'), date >= -90d)Two functions have special parameter forms:
- Distance —
customer_within_distance MATCHES (coordinates = (13.0827, 80.2707), distance_km = 25)(usedistance_mifor miles). - Anniversary sugar —
anniversary('customer.birthday') BETWEEN today AND +30dis accepted as shorthand for the recurring-date operators on any recurring-date attribute; the editor rewrites it to the canonical form when it reprints the query.
Metafield paths
Customer metafields are addressed as metafields.<namespace>.<key>
(customer.<namespace>.<key> is also accepted):
metafields.b2b.price_band = 'gold'metafields.facts.renewal_date IN NEXT 30 DAYSInside a MATCHES scope, metafields of the related record use
metafield.<namespace>.<key> and resolve against that record’s owner type:
products_purchased MATCHES (metafield.materials.fabric = 'cotton')A metafield must have Use as filter enabled on its definition to appear in segments. Namespaces or keys that don’t fit the identifier syntax are written in backticks. See the attribute reference for which value types are filterable and how list metafields behave.
Grammar summary
where = orExprorExpr = andExpr { OR andExpr }andExpr = primary { AND primary }primary = "(" orExpr ")" | conditioncondition = attribute operator [ value ] | function ( "MATCHES" | "NOT_MATCHES" ) "(" param { "," param } ")" | function ( "IS NULL" | "IS NOT NULL" )param = subfield operator [ value ]value = string | number [ unit ] | boolean | date | offset | id | "(" value { "," value } ")"Worked examples
-- Lapsed high-value customersamount_spent >= 5000 AND last_order_date NOT IN LAST 90 DAYS
-- Subscribed locals near the flagship storecustomer.email_subscription_status = 'SUBSCRIBED' AND customer_within_distance MATCHES (coordinates = (13.0827, 80.2707), distance_km = 25)
-- COD-heavy repeat buyers (for prepaid-incentive campaigns)orders_placed MATCHES (count >= 3) AND orders_placed MATCHES (is_cod = true, date >= -6m)
-- Bought from the Beauty category but never left a reviewproducts_purchased MATCHES (category = 'ha-15') AND product_review IS NULL
-- Loyalty members with points expiring this monthloyalty MATCHES (status = 'enrolled', next_expiration_date IN NEXT 30 DAYS)
-- Waiting for a restock of a specific productstock_notifications MATCHES (product_id = '018f2e6a-…', status = 'active')
-- Birthday campaign audiencecustomer.birthday IN MONTH May AND customer.email_subscription_status = 'SUBSCRIBED'How segments stay current
Dynamic segments are recomputed automatically: profile edits, orders, consent
changes, loyalty/review/referral activity, and metafield writes all mark the
affected customers for re-evaluation within seconds, and date-based rules
(offsets, IN LAST, birthdays) are re-scanned daily in the store’s timezone.
You never need to press anything to keep a segment fresh — Refresh on the
segment page just forces an immediate pass.