Skip to main content

Use MATCHES in ShopifyQL to filter by what customers did

Learn how to use the MATCHES operator in ShopifyQL to easily filter data based on real customer behaviors and actions.

· By Zakia · 13 min read

Why “MATCHES” is the missing piece in ShopifyQL customer-behavior filtering

If you have ever tried to answer a simple sounding question in Shopify Analytics like, “Show me customers who bought Product X” or “Customers who purchased at least 2 items in the last 30 days”, you have probably hit the same wall I did.

Regular filters, and even a lot of normal looking WHERE conditions, are great when you are filtering by what something is.

Like :

  • customer email contains “gmail”
  • customer tags include “VIP”
  • order channel is “Online Store”
  • country is “US”

But they get awkward fast when what you actually need is : what customers did. Behavior. Actions. Relationships between customers, orders, and line items.

That is where MATCHES comes in.

Shopify added MATCHES to the ShopifyQL editor in Analytics so you can filter reports by customer behavior, not just customer attributes. And if you have used Customer Segmentation before, the nice part is that this is the same idea. Same mental model. You define behavior in one consistent way, and now you can apply it in reports too.

A quick expectation reset though, because it matters.

MATCHES is not a generic SQL feature. You will not find it in Postgres tutorials. It is ShopifyQL specific, with ShopifyQL specific syntax and some report specific limitations. So if you are expecting full SQL parity, you will feel like something is missing.

You will use MATCHES mainly in two places :

  • Shopify Analytics when you open a report and switch to the ShopifyQL editor
  • Customer Segmentation when building segments using behavior conditions (and the ShopifyQL like logic behind them)

If you only remember one thing from this article, make it this :

Normal WHERE is for attributes. WHERE plus MATCHES is for actions.

Quick mental model : MATCHES vs a normal SQL WHERE clause

A normal WHERE clause filters rows based on values inside the row.

So something like this feels obvious :

sql FROM customers SHOW customer_id, email WHERE email LIKE '%@gmail.com'

You are not asking Shopify to interpret relationships. You are just saying, “filter customers where email has this pattern”.

But behavior questions are different. “Customers who purchased product X” is not a customer row attribute. That information lives in orders and line items and product references. So your filter has to cross that relationship.

This is basically what MATCHES does in ShopifyQL.

It lets you say : keep this entity (often a customer) only if it matches a defined behavior, like having purchased products under some conditions.

And yes, in practice you will usually see MATCHES appear inside a WHERE clause. So your SQL brain still works here. It is still “filtering”. It is just filtering based on relationship aware conditions that Shopify already understands.

In plain English, a MATCHES clause often reads like :

Keep these customers where their purchased products match these rules.
Shopify: Compare 2+ Metrics on ONE Chart (No Guessing)
There’s a weird gap in a lot of Shopify reporting setups. You go into Analytics, you open a chart, it shows you Total sales. Cool. Then you switch to Conversion rate. Also cool. Then you sort of… try to remember what the first chart looked like while staring at the second one.

Where you can use MATCHES : Analytics reports vs Customer Segmentation

There are two main surfaces you will bump into :

1. Shopify Analytics (reports with ShopifyQL)

You open a report, switch to ShopifyQL mode, and write a query. Now, you can use MATCHES in the WHERE clause to filter by customer behavior.

This is the newer workflow Shopify is pushing because it lets you validate behavior based segments with hard numbers. Revenue. Orders. Net sales. All the stuff stakeholders ask for five minutes before a meeting.

2. Customer Segmentation (segment builder / ShopifyQL-like logic)

Segmentation is where a lot of teams already defined behavior.

“Purchased more than twice” “Purchased in the last 30 days” “Purchased Product A but not Product B” That kind of thing.

The important part is consistency.

Because if marketing defines a segment one way, and analytics defines the report filter slightly differently, you get the usual chaos :

  • “Why does your report say 1,204 customers but my segment says 987 ?”
  • “Which one is right ?”
  • “Are we counting orders or items ?”
  • “Wait, what time window did you use ?”

Now, the idea is you can keep the behavior logic aligned. Build the condition in one place, reuse it in the other.

A practical workflow looks like this :

  1. Define behavior logic in Analytics using ShopifyQL and MATCHES
  2. Confirm the numbers in the report preview
  3. Reuse the same logic in Customer Segmentation, or the other way around

The UI is different, sure. Analytics is a query editor. Segmentation is more of a builder. But the operator concept stays the same : define behavior with MATCHES.

Core building blocks you’ll see in MATCHES-based queries

Before we jump into examples, a few building blocks you will see a lot.

customer_id is usually the anchor

If you are filtering by behavior, you almost always want to anchor on customer_id.

Not email. Not name. Not phone. Those can be missing, duplicated, or changed.

customer_id is the stable key that ties behavior together.

You will often group by customer_id

Behavior conditions can easily inflate counts if you do not respect the “grain” of the dataset.

If the dataset is at the line item level and you “show customer_id”, you might get multiple rows per customer. Then your counts look wrong, and you spend an hour doubting reality.

So a very common pattern is :

  • filter behavior
  • GROUP BY customer_id
  • then aggregate metrics safely

products_purchased (or equivalent behavior dimension)

In ShopifyQL examples, you will see fields like products_purchased used as the behavior subject.

That name can vary depending on the dataset behind the report you are editing. Same idea, different schema sometimes. So if you do not see products_purchased, do not assume you cannot do the analysis. Check the report schema or the Shopify Help Center docs for that specific report.

Orders and line items are where behavior usually lives

When someone says “purchased”, you need to decide what you mean :

  • purchased products (quantity of line items)
  • purchased orders (count orders)
  • purchased a specific variant vs any variant under a product

That decision changes your query. And it is also where misalignment happens between teams.

MATCHES syntax in practice (and the pattern to copy/paste)

Here is a repeatable pattern that tends to work well when you are filtering customers by actions :

  1. FROM a dataset (often customers)
  2. SHOW the columns you want
  3. WHERE your normal scope filters (dates, channels, etc)
  4. Add your behavior filter with MATCHES
  5. GROUP BY customer_id if you need customer level results
  6. Add HAVING for thresholds after aggregation

A simplified “pattern” version :

sql FROM customers SHOW customer_id, email, WHERE AND <behavior_field> MATCHES () GROUP BY customer_id HAVING

And how to read MATCHES, without overthinking it :

Keep rows where this entity matches customers with this behavior.

Also, a practical tip. Start broad, then tighten.

  • Start with the base dataset and just SHOW customer_id
  • Add the date constraint
  • Add the MATCHES clause
  • Then add aggregation and thresholds

If you go all in with a complex query on the first try, and it returns zero rows, you will not know which piece broke it.

Shopify Local Pickup Checkout Update: What Changed
Discover how Shopify’s updated checkout design makes local pickup clearer and removes confusion for online shoppers.

Example 1 : Filter customers who purchased a specific product (behavior-based filter)

Goal : return customers who purchased a specific product. You might use this when you want to analyze repeat rate after a hero product, or build a post purchase email audience, or just sanity check if a product launch brought in new buyers.

A basic structure looks like this, with MATCHES inside WHERE.

sql FROM customers SHOW customer_id, email WHERE products_purchased MATCHES (product_id = 123456789) GROUP BY customer_id

A couple notes before you copy paste that and get mad at me :

  • Your report dataset might not expose product_id inside products_purchased. Sometimes you get variant identifiers, sometimes titles, sometimes a different nested field. This varies.
  • If the dataset supports product title matching instead of IDs, it might look more like :

sql FROM customers SHOW customer_id, email WHERE products_purchased MATCHES (product_title = "My Product Name") GROUP BY customer_id

If you want the output to be more useful, you can include metrics like order count or net sales if the dataset supports them. For example :

sql FROM customers SHOW customer_id, email, count_orders, net_sales WHERE products_purchased MATCHES (product_id = 123456789) GROUP BY customer_id

Again, exact metric names depend on the dataset behind the report. But the idea is the same : you are returning customer level rows while keeping the behavior filter consistent.

What to check if it returns 0 rows

This happens constantly, even when your logic is correct.

Here is the short checklist I run :

  1. Date range : is the report date range excluding the purchases you expect ?
  2. Identifier type : are you filtering by product_id but the dataset uses variant_id (or title) ?
  3. Dataset mismatch : are you in a report that is not backed by the customer behavior fields you need ?
  4. Spelling and case (if using titles) : product titles have to match exactly in many contexts

Example 2 : Purchased at least 2 products in the last 30 days (threshold + time window)

Goal : identify customers who purchased at least 2 products within the last 30 days.

This is the example Shopify has been highlighting because it is a very real segmentation need. And it is the kind of thing that is annoying without a behavior aware operator.

Here is the core pattern :

sql FROM customers SHOW customer_id, email WHERE products_purchased MATCHES (quantity >= 2, date >= -30d) GROUP BY customer_id

That reads like : keep customers whose purchased products have a quantity of at least 2 and a purchase date within the last 30 days.

A few important clarifications.

Where the time window belongs

You might be tempted to apply the date window somewhere else, like a top level WHERE date >= -30d outside of MATCHES. Sometimes that is correct, sometimes it is not, depending on dataset and what date refers to.

For behavior definitions, you generally want the time window to live alongside the behavior rule itself, so the behavior is self contained and reusable.

Because otherwise you end up with this subtle bug :

  • The report date range is set to last 30 days.
  • The behavior is defined as "purchased at least 2 products" with no time window attached.
  • If the report range changes to 90 days, your "30 day segment" silently becomes a "90 day segment".

Not fun.

Products vs orders

“Purchased at least 2 products” is not the same as “placed at least 2 orders”.

If you actually mean orders, your logic needs to count orders, not product quantity. Depending on what fields are available, the pattern becomes :

  • group by customer
  • compute count_orders (or equivalent)
  • apply a HAVING threshold

Like :

sql FROM customers SHOW customer_id, email, count_orders WHERE orders MATCHES (date >= -30d) GROUP BY customer_id HAVING count_orders >= 2

That is a different business question. Similar vibe, totally different outcome.

The big takeaway : be explicit about what you are counting.

New Shopify Analytics Insights: Spot Trends Fast
If you have ever opened your analytics and thought, ok cool, I have numbers. Now what. You are not alone.

Example 3 : Use MATCHES to filter a report, then reuse the same logic for a Segment

This is where MATCHES starts paying for itself.

Workflow :

  1. Build your behavior condition in Analytics using ShopifyQL
  2. Confirm the counts and revenue in the report preview
  3. Reuse the same condition in Customer Segmentation, so the segment definition matches what the report is measuring

Let’s say you are building “Purchased 2+ products (30d)”.

In Analytics you might start with :

sql FROM customers SHOW customer_id, email WHERE products_purchased MATCHES (quantity >= 2, date >= -30d) GROUP BY customer_id

Once you confirm the customer count looks right, you create a segment using the same MATCHES logic.

This avoids that classic mismatch where :

  • the segment is defined as “2+ orders in 30 days”
  • the report is filtering “2+ products in 30 days”
  • everyone thinks they are talking about the same cohort, and they are not

Naming conventions help more than people think

A simple naming pattern keeps teams aligned :

  • Purchased X (30d)
  • Purchased 2+ products (30d)
  • Purchased Variant Y (7d)

Not “High intent customers v2 final FINAL”. Please.

How to sanity-check

After you build both :

  • Compare the customer count in the segment with the customer count in the report filtered with the same MATCHES
  • Compare revenue totals if your report includes net sales

If they are wildly different, do not assume Shopify is wrong. It is almost always grain, time window placement, or counting products vs orders.

Common mistakes (and how to fix them fast)

Mistake 1 : Filtering on customer fields when you actually need behavior

If you find yourself stacking customer attribute filters trying to approximate behavior, stop and use MATCHES.

Example : “customers who purchased product X” cannot be solved by customer tags unless you already tag them post purchase. And even then, it is delayed and messy.

Fix : move the logic into WHERE ... MATCHES (...).

Mistake 2 : Forgetting the correct grain (customer vs order vs line item)

This is the silent killer. Your query works, but numbers look inflated.

Fix : when your goal is a customer list or customer level metrics, GROUP BY customer_id.

Mistake 3 : Mixing time windows (report date range vs MATCHES behavior window)

If the report date range is doing the filtering instead of your MATCHES clause, the behavior definition changes when someone changes the date range in the UI.

Fix : put the time window inside the MATCHES rules when the time window is part of the behavior definition.

Mistake 4 : Using the wrong identifier for products (title vs product_id vs variant_id)

Product titles can change. IDs do not, but they are harder to read. Variants add another layer.

Fix : use the identifier your dataset actually exposes, and stay consistent. If you are unsure, check the report schema and Shopify Help Center docs for that report.

Mistake 5 : Expecting full SQL parity

ShopifyQL is not a general purpose database language. It is designed for Shopify's datasets and relationships. Some patterns you might want from SQL just are not supported in certain reports.

Fix : treat the Shopify Help Center as part of your toolset, especially for syntax, examples, and limitations.

Practical tips for writing MATCHES queries that stay readable (and debuggable)

Keep one behavior per MATCHES clause. If you need two behaviors, consider two MATCHES conditions, or build incrementally.

Build queries in layers in this order : start with the base dataset, add scoping date filters, add MATCHES, add aggregation with GROUP BY, then add thresholds with HAVING.

Prefer explicit keys. Use customer_id as the key, and only include email if your dataset supports it.

Save working queries somewhere. A small personal library of proven patterns beats rewriting from memory every time.

Closing : When to reach for MATCHES (and when a simple WHERE is enough)

The rule of thumb is simple, and it holds up :

  • If you are filtering by attributes, use a normal WHERE.
  • If you are filtering by actions or relationships, use WHERE plus MATCHES.

That is the point of this addition in Analytics. You can now filter reports by what customers did, not just who they are. And because the same MATCHES logic works in Customer Segmentation, you can keep segments and reports consistent, which is honestly half the battle in real teams.

Keep a small library of patterns that you know work. Useful starting points include : filtering for customers who purchased a specific product, customers who purchased two or more products in the last 30 days, distinguishing repeat orders from repeat items, and applying a time window inside the behavior definition.

With those patterns on hand, you can move faster and stop second-guessing whether your report and your segment are secretly measuring two different things.

Conclusion

MATCHES is ShopifyQL’s behavior aware filter operator. It is built for the exact problems that normal row based filtering struggles with : purchases, relationships, time windows, and customer behavior definitions that you want to reuse across Segmentation and Analytics.

If you want syntax details, more examples, and the current limitations per report type, Shopify’s own documentation is the source of truth. The Shopify Help Center is worth keeping open in another tab while you build your first few queries.

FAQs (Frequently Asked Questions)

What is the purpose of the MATCHES operator in ShopifyQL ?

The MATCHES operator in ShopifyQL is designed to filter reports by customer behavior, such as actions and relationships between customers, orders, and line items, rather than just filtering by static customer attributes.

How does MATCHES differ from a regular WHERE clause in ShopifyQL ?

A regular WHERE clause filters rows based on attributes within that row (like email or country), while MATCHES allows filtering based on related behaviors or actions that span multiple tables or entities, such as customers who purchased specific products.

Where can I use the MATCHES operator within Shopify's tools ?

MATCHES can be used primarily in two places : 1) Shopify Analytics when writing queries in the ShopifyQL editor to filter reports by customer behavior, and 2) Customer Segmentation when building segments using behavior conditions with ShopifyQL-like logic.

Why is it important to use customer_id as an anchor in MATCHES-based queries ?

Customer_id is a stable and unique identifier that ties behavioral data together reliably. Unlike email or phone numbers which can be missing or duplicated, using customer_id ensures accurate filtering and aggregation of customer behavior across orders and line items.

How can I ensure consistency between Customer Segmentation and Analytics reports when using MATCHES ?

By defining behavior logic once using MATCHES in either Analytics or Customer Segmentation and then reusing that same logic across both surfaces, you maintain alignment. This avoids discrepancies like different customer counts due to inconsistent definitions or time windows.

What are common practices when aggregating data filtered by MATCHES to avoid inflated counts ?

Because datasets may be at the line item level causing multiple rows per customer, it's common to filter behavior with MATCHES, then GROUP BY customer_id before aggregating metrics. This ensures counts reflect unique customers accurately without duplication.

About the author

Updated on Jun 8, 2026