Listen for events
Standard storefront events tell your app what a buyer is doing on the storefront. Someone adds an item to the cart, switches a variant, or filters a collection, and the storefront dispatches an event describing what changed. Your app reads the event instead of parsing the storefront's DOM or intercepting window.fetch.
Most of these events come from the storefront's own dispatch calls, so the theme decides which ones exist. Don't assume an event you depend on fires on a given store.
The cart events are the exception. Calling updateCart emits them on any storefront, so you can rely on those whether or not the theme dispatches anything itself.
Anchor to Adding a listenerAdding a listener
You listen with addEventListener, the same as any other DOM event. Attaching to document catches every one, because they bubble up from wherever they were dispatched:
You don't need to know which element dispatched the event. To scope a listener to one part of the page, attach it to a container element instead of document.
Because shopify:page:view fires as soon as the document is ready, you register your listeners synchronously at script load. A listener attached any later misses it.
Anchor to Removing a listenerRemoving a listener
When the part of your UI that needed an event goes away, you'll want its listener to go with it. Storefronts re-render sections in place, so you can't count on a page navigation to clean up for you.
With one AbortController, you can pass the same signal to every listener you add, then call abort() once to remove them all:
Anchor to Reading the payloadReading the payload
You read an event's data straight off the event object, like event.product or event.lines. You don't unpack a detail object first, the way you would with a CustomEvent:
Payloads follow the shape of the Storefront API with camelCase property names, and prices use the MoneyV2 format. Product and variant IDs are full GID strings.
Cart line IDs are the exception. You'll get a GID on some storefronts and a raw key on others, depending on which cart API the storefront uses. Either one works when you send it back, though you can't count on the format.
You might also see a detail field on a payload. That's extra data the storefront attached, not the DOM wrapper.
Each event's reference page lists its exact fields.
Anchor to Waiting for the resultWaiting for the result
Five events report something that already happened, like a buyer viewing a product. The other six report an operation that has just started, so you get them before there's an outcome to read.
Those six each have a promise:
| Event | Resolves with |
|---|---|
shopify:cart:lines-update | cart |
shopify:cart:note-update | cart |
shopify:cart:discount-update | cart |
shopify:product:select | variant |
shopify:collection:update | productsCount |
shopify:search:update | totalCount |
As soon as the event reaches you, you can show a loading state, then clear it when the promise settles:
Anchor to Handling errorsHandling errors
A cart update can fail in two ways. Sometimes the request never reaches Shopify, because the buyer's connection dropped. Other times it arrives fine and the cart turns down all or part of the change, because the quantity is above the item's maximum or the variant is out of stock.
You already handle the first kind with .catch(), the way you would any failed request. The
second kind resolves like any success, so the only way you'll notice is by reading userErrors
and warnings on the value you get back. If you don't check them, you'll show a success
message for an item that isn't in the cart.
You read userErrors when the cart refused the change outright, and warnings when it went
through with an adjustment, like a line capped at the stock available:
Changes you didn't ask for never reach your promise. When the theme or another app changes the
cart and it fails, you find out by listening for shopify:cart:error:
To work out what kind of failure it was, you look up the code on the event. INVALID means
a malformed input, and MAXIMUM_EXCEEDED means a quantity above the item's maximum. See
CartErrorCode for the full list.
When you triggered the change yourself, you'll see both the rejection and this event for the same failure, so handling one is enough.