---
title: Listen for events
description: >-
  Listen for standard storefront events to respond to cart updates, product
  views, and other commerce interactions on the storefront.
source_url:
  html: 'https://shopify.dev/docs/api/storefront-events-and-actions/events/listen'
  md: 'https://shopify.dev/docs/api/storefront-events-and-actions/events/listen.md'
api_name: storefront-events-and-actions
---

# 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](https://shopify.dev/docs/api/storefront-events-and-actions/events/dispatch), 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`](https://shopify.dev/docs/api/storefront-events-and-actions/actions/update-cart) emits them on any storefront, so you can rely on those whether or not the theme dispatches anything itself.

***

## Adding 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:

```javascript
document.addEventListener('shopify:cart:lines-update', (event) => {
  console.log(event.action, event.lines);
});
```

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`](https://shopify.dev/docs/api/storefront-events-and-actions/events/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.

***

## Removing 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:

```javascript
const controller = new AbortController();


document.addEventListener('shopify:cart:lines-update', handler, {
  signal: controller.signal,
});


controller.abort();
```

***

## Reading 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`](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent):

```javascript
document.addEventListener('shopify:product:view', (event) => {
  const { product, selectedOptions, context } = event;


  console.log(product.title, product.selectedVariant?.price.amount);
});
```

Payloads follow the shape of the [Storefront API](https://shopify.dev/docs/api/storefront) with `camelCase` property names, and prices use the [`MoneyV2`](https://shopify.dev/docs/api/storefront/latest/objects/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](https://shopify.dev/docs/api/storefront-events-and-actions/events) lists its exact fields.

***

## Waiting 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`](https://shopify.dev/docs/api/storefront-events-and-actions/events/cart-lines-update) | `cart` |
| [`shopify:cart:note-update`](https://shopify.dev/docs/api/storefront-events-and-actions/events/cart-note-update) | `cart` |
| [`shopify:cart:discount-update`](https://shopify.dev/docs/api/storefront-events-and-actions/events/cart-discount-update) | `cart` |
| [`shopify:product:select`](https://shopify.dev/docs/api/storefront-events-and-actions/events/product-select) | `variant` |
| [`shopify:collection:update`](https://shopify.dev/docs/api/storefront-events-and-actions/events/collection-update) | `productsCount` |
| [`shopify:search:update`](https://shopify.dev/docs/api/storefront-events-and-actions/events/search-update) | `totalCount` |

As soon as the event reaches you, you can show a loading state, then clear it when the promise settles:

```javascript
document.addEventListener('shopify:cart:lines-update', (event) => {
  showSpinner();


  event.promise
    .then(({ cart }) => updateCartCount(cart?.totalQuantity))
    .catch(() => showMessage('Something went wrong.'))
    .finally(hideSpinner);
});
```

***

## Handling 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:

```javascript
document.addEventListener('shopify:cart:lines-update', (event) => {
  event.promise
    .then(({ cart, userErrors, warnings }) => {
      if (userErrors?.length) return showMessage(userErrors[0].message);
      if (warnings?.length) showMessage(warnings[0].message);


      updateCartCount(cart?.totalQuantity);
    })
    .catch(() => showMessage("We couldn't reach the store. Try again."));
});
```

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`:

```javascript
document.addEventListener('shopify:cart:error', (event) => {
  console.log(event.code, event.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`](https://shopify.dev/docs/api/storefront/latest/enums/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.

***

## Next steps

[Events reference\
\
](https://shopify.dev/docs/api/storefront-events-and-actions/events)

[Look up the payload, resolved value, and dispatch target for every event.](https://shopify.dev/docs/api/storefront-events-and-actions/events)

[Call actions\
\
](https://shopify.dev/docs/api/storefront-events-and-actions/actions/call)

[Trigger storefront behaviors like adding to cart or opening the cart.](https://shopify.dev/docs/api/storefront-events-and-actions/actions/call)

***
