October 4, 2026 · Muhammad Rehan · More by Muhammad Rehan
Shopify Events GA: Triggers, Query, Query Filter
Shopify's Next Gen Events reached GA in API version 2026-10. Here's how triggers, query, and query_filter combine to replace classic webhooks.

On October 1, 2026, Shopify's Next Gen Events system reached general availability under API version 2026-10. Coverage spans 18 Admin GraphQL API topics — Product, Collection, Customer, Company, Order, FulfillmentOrder, Refund, Return, InventoryItem, InventoryShipment, InventoryTransfer, Location, Article, Blog, Page, MetafieldDefinition, Metaobject, and MetaobjectDefinition. Events is Shopify's declarative successor to classic webhooks, and understanding its three-part model is the difference between designing a subscription correctly the first time and rebuilding it after a flood of deliveries you didn't need.
Three independent pieces, two gates
A classic webhook gives you a bare notification — a topic fired, go fetch the rest yourself. An Events subscription instead declares three components that work together: triggers, query, and query_filter.
triggers gates at the change level, before any query runs. It's an array of field paths on the subscribed topic, and it's required whenever actions includes update (create- or delete-only subscriptions don't need it). A parent path like product.* catches every supported nested field change under that topic; list specific paths instead — say, product.variants.price — to narrow deliveries to exactly the changes you care about. List multiple trigger paths and they combine with implicit OR logic: any one matching path qualifies the delivery.
query is the GraphQL Admin API operation that runs after a qualifying change, with its result attached to the delivery as data. This is the part that eliminates the follow-up API call classic webhooks always forced on you — no more 'a collection changed, now go fetch it to see what's different.'
query_filter is the second gate, and it's the one engineers most often confuse with triggers. It runs after the query response exists, and it decides whether to actually send the delivery based on current field values from that query result — not on what changed. That distinction matters: query_filter can't express a previous-value-versus-new-value comparison. A filter like product.status:'ACTIVE' AND productVariant.price:>100 tells you the product is active and priced above $100 right now; it says nothing about whether status or price was what triggered the delivery in the first place. query_filter also can only reference fields the subscription's query actually returns, and it must match the query's root — if your query starts at productVariant, your filter path has to start there too, not at product.variants.
Why the distinction is easy to get wrong
It's tempting to treat triggers as a coarse filter and query_filter as the fine-grained one, layering them like successive narrowing passes over the same kind of data. They're not the same kind of gate. triggers answers "did this specific field path just change?" using a change signal in Shopify's pipeline. query_filter answers "is the current state of these queried fields what I want to act on?" using a snapshot taken after the change. A subscription that gets this backwards can silently drop deliveries it needed — for example, filtering only for product.status:'ACTIVE' suppresses the delivery for a product transitioning out of active status, which is exactly the transition an app tracking an active-products set needs to see.
Operational details worth knowing before you migrate
- Large deliveries overflow. When a payload would exceed the channel's size limit, Shopify stores the full content and sends a small payload with only topic, action, handle, payload_url, payload_size_bytes, and expires_at instead. Limits are 5 MB for HTTPS, 10 MB for Google Cloud Pub/Sub, and 256 KB for Amazon EventBridge.
- Queries are validated at deploy time, not runtime. Shopify checks that your query only references variables your triggers can actually supply when the subscription is saved; GraphQL errors that still occur at delivery time surface in the payload's errors field.
- Each subscription query has a 100-point complexity ceiling — a known constraint we've covered in detail elsewhere; it's a design input here, not the headline.
- Nothing forces a cutover. Classic webhook integrations keep working unchanged, Events and classic webhooks can run side by side in the same app, and migration can happen one workflow at a time instead of as a single risky switch.
For a team already running classic webhooks, the practical migration unit isn't "a topic" — it's a single handler that currently makes a follow-up call you can now fold into the query, filtered down to the state that actually matters with query_filter. Move one of those at a time, and the two systems coexisting isn't a transitional inconvenience; it's the intended path.