---
title: Standard storefront events
description: >-
  Standard storefront events are DOM events that themes dispatch when commerce
  interactions occur on the storefront, so apps can listen for activity without
  parsing theme DOM structure.
source_url:
  html: 'https://shopify.dev/docs/storefronts/themes/best-practices/standard-events'
  md: >-
    https://shopify.dev/docs/storefronts/themes/best-practices/standard-events.md
---

# Standard storefront events

Standard storefront events are DOM events that themes dispatch when commerce interactions occur on the storefront, such as a product view, a cart update, or a collection filter change. Apps listen for these events with `addEventListener` to respond to storefront activity without parsing theme DOM structure or intercepting `window.fetch`.

Each event uses the `shopify:` namespace prefix (for example, `shopify:cart:lines-update`) and follows a `category:action` naming pattern. Event payloads follow the shape of the [Storefront GraphQL API](https://shopify.dev/docs/api/storefront) with `camelCase` property names, so listeners can read most of the data they need from the event directly.

Events dispatch from the most specific available contextual element and bubble to `document`. Typical targets include a product card, cart drawer, or collection container. Listeners can attach at any level in the tree.

***

## Loading the library

The standard events library is hosted on the Shopify CDN. How you load it depends on whether your theme uses JavaScript modules.

If your theme uses JavaScript modules, as Horizon does, then register the library in an import map in `theme.liquid`:

```html
<script type="importmap">
  { "imports": { "@theme/standard-events": "https://cdn.shopify.com/storefront/standard-events.js" } }
</script>
```

Type definitions are available at `https://cdn.shopify.com/storefront/standard-events.d.ts`.

If your theme uses plain `<script>` tags without modules, such as some Dawn-derived themes, then assign the library to a global instead:

```html
<script type="module">
  import * as SE from 'https://cdn.shopify.com/storefront/standard-events.js';
  window.Shopify = window.Shopify || {};
  window.Shopify.StandardEvents = SE;
</script>
```

***

## Dispatching events

Create events with `new ClassName(payload)` and dispatch them with `element.dispatchEvent()`. Dispatch `page:view` inside a `DOMContentLoaded` listener so that app scripts have time to attach their listeners:

```javascript
import { PageViewEvent } from '@theme/standard-events';


document.addEventListener('DOMContentLoaded', () => {
  document.dispatchEvent(new PageViewEvent({
    page: {
      template: 'product',
      title: document.title,
      url: window.location.href,
    },
  }));
});
```

Dispatch on the most specific container element:

* Product events dispatch on the product card or section.
* Cart events dispatch on the cart drawer or cart page section.
* `page:view` dispatches on `document`.

***

## View events with the Liquid filter

For `product:view`, `collection:view`, and `cart:view`, use the `standard_event_data` Liquid filter together with the view event custom element. The filter generates the payload, and the custom element dispatches the event when the element scrolls into view.

Register the custom element once in your theme's JavaScript:

```javascript
import { createViewEventElement } from '@theme/standard-events';


customElements.define('s-view-event', createViewEventElement());
```

Then wrap your content in Liquid:

```liquid
<s-view-event view-event-payload='{{ product | standard_event_data: "view" | escape }}'>
  <!-- product card content -->
</s-view-event>
```

You can pass a `context` to the filter: `{{ product | standard_event_data: "view", context: "page" }}`. If no context is provided and the element is inside a `<dialog>`, then the context is set to `dialog` automatically.

***

## Deferred promises

Some events represent async operations: `cart:lines-update`, `cart:note-update`, `cart:discount-update`, `product:select`, `collection:update`, and `search:update`. These events include a `promise` field. Dispatch the event before the async operation starts so listeners can show loading states or optimistic UI, then resolve the promise when the operation completes:

```javascript
import { CartLinesUpdateEvent, CartErrorEvent } from '@theme/standard-events';


const deferred = CartLinesUpdateEvent.createPromise();


element.dispatchEvent(new CartLinesUpdateEvent({
  action: 'add',
  context: 'product',
  lines: [{ merchandiseId: variantId, quantity: 1 }],
  promise: deferred.promise,
}));


try {
  const response = await fetch(window.Shopify.routes.root + 'cart/add.js', { method: 'POST', body, headers });
  if (!response.ok) throw new Error('Add to cart failed');
  const ajaxCart = await fetch(window.Shopify.routes.root + 'cart.js').then(r => r.json());
  deferred.resolve({
    cart: CartLinesUpdateEvent.createCartFromAjaxResponse(ajaxCart),
  });
} catch (e) {
  element.dispatchEvent(new CartErrorEvent({
    error: e.message,
    code: 'SERVICE_UNAVAILABLE',
  }));
  deferred.reject(e);
}
```

Always resolve or reject the promise. Unresolved promises cause listeners to hang.

***

## AJAX cart response converter

Cart event classes have a static `createCartFromAjaxResponse(ajaxCart)` method that converts the AJAX API `/cart.js` response into the cart format that event payloads expect (a subset of the [Storefront API Cart object](https://shopify.dev/docs/api/storefront/latest/objects/Cart)). Use this method when your theme uses the AJAX cart API.

If your theme uses the Storefront API directly, then build the cart payload from the GraphQL response.

***

## Listening to events

You can listen for any standard event on `document`:

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


  event.promise?.then(({ cart }) => {
    console.log(cart.cost.totalAmount.amount);
  });
});
```

***

## Payload conventions

Follow these conventions so payloads validate against the dev build and stay consistent with the Storefront API:

IDs accept raw numbers, strings, or full GID strings. Event constructors normalize them.

Prices use the `MoneyV2` format: `{ amount: "10.99", currencyCode: "USD" }`. Use the `money_amount` Liquid filter to convert from cents.

Payload shapes follow the Storefront GraphQL API with `camelCase` property names.

The sections below are a reference for each event, grouped by category, with its name, payload shape, and recommended dispatch target.

***

## Page events

Page events fire on navigation. Dispatch them on `document`.

### Page view

Event name: `shopify:page:view`

Fires on every page load. Dispatch inside a `DOMContentLoaded` listener so that app scripts have time to attach their listeners.

```javascript
new PageViewEvent({
  page: {
    template: "product",
    title: "Classic T-Shirt",
    url: "https://example.myshopify.com/products/classic-tee"
  }
})
```

Dispatch target: `document`.

***

## Product events

Product events fire as buyers browse and configure products. Dispatch them on the product card, section, or form.

### Product view

Event name: `shopify:product:view`

Fires when a product becomes visible in the viewport. Use the [view event element and Liquid filter](#view-events-with-the-liquid-filter) rather than dispatching manually.

```javascript
new ProductViewEvent({
  context: "collection", // "page" | "collection" | "search" | "dialog" | "recommendation"
  selectedOptions: [
    { name: "Size", value: "Medium" },
    { name: "Color", value: "Blue" }
  ],
  product: {
    id: 123456,
    title: "Classic T-Shirt",
    handle: "classic-tee",
    selectedVariant: {   // null if no variant is selected
      id: 789,
      title: "Medium / Blue",
      availableForSale: true,
      price: { amount: "29.99", currencyCode: "USD" },
      selectedOptions: [
        { name: "Size", value: "Medium" },
        { name: "Color", value: "Blue" }
      ]
    }
  }
})
```

Dispatch target: product card or product section.

### Product select

Event name: `shopify:product:select`

Fires when the buyer changes a variant selection.

```javascript
new ProductSelectEvent({
  selectedOptions: [
    { name: "Size", value: "Large" }
  ],
  product: {
    id: 123456,
    title: "Classic T-Shirt",
    handle: "classic-tee"
  },
  promise: deferred.promise // resolves to { variant: { id, title, availableForSale, price, selectedOptions } | null }
})
```

Dispatch target: product section or product form.

***

## Cart events

Cart events fire when the cart is viewed or mutated. Dispatch them on the cart drawer or cart page section. The events for mutations include a `promise` field, covered in [Deferred promises](#deferred-promises).

### Cart view

Event name: `shopify:cart:view`

Fires when the cart becomes visible, either as a drawer or modal, or as a full page.

```javascript
new CartViewEvent({
  context: "dialog", // "page" | "dialog"
  cart: {            // null if no cart exists
    id: 12345,
    totalQuantity: 3,
    cost: {
      totalAmount: { amount: "89.97", currencyCode: "USD" }
    },
    lines: [
      {
        id: 1001,
        quantity: 2,
        cost: {
          totalAmount: { amount: "59.98", currencyCode: "USD" }
        }
      }
    ],
    discountCodes: [
      { code: "SAVE10", applicable: true }
    ]
  }
})
```

Dispatch target: cart drawer, cart modal, or cart page section.

### Cart lines update

Event name: `shopify:cart:lines-update`

Fires when cart lines are added, updated, or removed.

```javascript
new CartLinesUpdateEvent({
  action: "add",      // "add" | "update" | "remove"
  context: "product", // "product" | "cart" | "dialog" | "standard-action"
  lines: [
    // For "add": { merchandiseId, quantity }. For "update" / "remove": { id, quantity }. Set quantity to 0 to remove a line.
    { merchandiseId: 789, quantity: 1 }
  ],
  promise: deferred.promise // resolves to { cart }
})
```

Dispatch target: cart element for updates and removes; product element for adds from a product page.

The `"standard-action"` value is reserved for events auto-emitted by [standard storefront actions](https://shopify.dev/docs/storefronts/themes/best-practices/standard-actions) when a configured action runs. Themes dispatching the event manually should use `"product"`, `"cart"`, or `"dialog"`.

### Cart note update

Event name: `shopify:cart:note-update`

Fires when the cart note changes.

```javascript
new CartNoteUpdateEvent({
  context: "cart", // "product" | "cart" | "dialog" | "standard-action"
  note: "Please gift wrap",
  promise: deferred.promise // resolves to { cart }
})
```

Dispatch target: cart element.

### Cart discount update

Event name: `shopify:cart:discount-update`

Fires when discount codes are applied or removed.

```javascript
new CartDiscountUpdateEvent({
  discountCodes: [
    { code: "SAVE10" }
  ],
  promise: deferred.promise // resolves to { cart }
})
```

Dispatch target: cart element.

### Cart error

Event name: `shopify:cart:error`

Fires when a cart mutation fails.

```javascript
new CartErrorEvent({
  error: "Product is out of stock",
  code: "INVALID"
})
```

Dispatch target: cart element, or `document` if no cart element is available.

***

## Collection events

Collection events fire when a collection page loads or its filters change. Dispatch them on the collection container element.

### Collection view

Event name: `shopify:collection:view`

Fires when a collection page loads.

```javascript
new CollectionViewEvent({
  collection: {
    id: 5001,              // can be null
    handle: "summer-sale",
    productsCount: 42
  }
})
```

Dispatch target: collection container element.

### Collection update

Event name: `shopify:collection:update`

Fires when collection filters or sort order change. Use `CollectionUpdateEvent.parseProductFilters(urlSearchParams)` to build the `productFilters` array from the current URL.

```javascript
new CollectionUpdateEvent({
  collection: {
    id: 5001,               // can be null
    handle: "summer-sale",
    productsCount: 18
  },
  productFilters: [ ... ],  // optional
  sortKey: "PRICE",         // optional
  promise: deferred.promise // resolves to { productsCount }
})
```

Dispatch target: collection container element.

***

## Search events

Search events fire when search filters or sort order change. Dispatch them on the search container element.

### Search update

Event name: `shopify:search:update`

Fires when search filters or sort order change. Use `SearchUpdateEvent.parseProductFilters(urlSearchParams)` to build the `productFilters` array from the current URL.

```javascript
new SearchUpdateEvent({
  search: {
    query: "blue shirt",
    productFilters: [ ... ], // optional
    sortKey: "RELEVANCE"     // optional
  },
  promise: deferred.promise  // resolves to { totalCount }
})
```

Dispatch target: search container element.

***
