---
title: Standard storefront actions
description: >-
  Standard storefront actions are async functions on `Shopify.actions` that apps
  and agents call to trigger theme behaviors like cart updates without
  theme-specific workarounds.
source_url:
  html: 'https://shopify.dev/docs/storefronts/themes/best-practices/standard-actions'
  md: >-
    https://shopify.dev/docs/storefronts/themes/best-practices/standard-actions.md
---

# Standard storefront actions

Standard storefront actions are async functions on `Shopify.actions` that apps and agents call to trigger theme behaviors, such as adding to cart, opening the cart drawer, or fetching the current cart. The theme decides how to handle each call, so the caller doesn't need to know whether the theme renders a cart drawer, a cart page, or another UI.

Shopify provides a default implementation for every action. Themes override the defaults to replace the default behavior with their own UI updates.

[Standard storefront events](https://shopify.dev/docs/storefronts/themes/best-practices/standard-events) fire automatically when a configured action succeeds.

***

## How actions are loaded

Shopify injects the actions script on every Liquid storefront, so themes don't need to add a `<script>` tag. After the page loads, actions are available on `window.Shopify.actions` with default Storefront API-based handlers.

***

## Calling actions

Call actions on `Shopify.actions`. The promise resolves with the result whether the theme has configured the action or not:

```javascript
const { cart } = await Shopify.actions.updateCart({
  lines: [{ merchandiseId: "gid://shopify/ProductVariant/123", quantity: 1 }],
});
```

To check whether an action has been configured by the theme, call `isDefault()`:

```javascript
if (!Shopify.actions.updateCart.isDefault()) {
  // The theme has configured this action.
}
```

***

## Configuring actions

Configuring an action replaces the default handler with the theme's own behavior, such as opening a cart drawer or updating a counter in place.

**Note:**

`getCart` is intentionally not configurable. Calling `Shopify.actions.getCart.configure(...)` is a TypeScript error and a runtime `TypeError`.

Without a configuration, each action runs its default behavior:

* **`updateCart`**: Writes to the Storefront API, then attempts an in-place cart refresh, such as a `cart:update` event on Horizon-style themes or a section-render swap on Dawn-style themes, and falls back to a full page reload if neither pattern matches.
* **`openCart`**: Calls `.open()` on a `<cart-drawer-component>` or `<cart-drawer>` element if either is present, and otherwise redirects to `/cart`.
* **`getCart`**: Reads the current cart from the Storefront API without affecting the page.

Register a configuration inside a `DOMContentLoaded` listener placed above `{{ content_for_header }}` in the layout file, so that the configuration runs before any app code.

A configuration accepts two options: `eventTarget` and `handler`.

### event​Target (required for update​Cart)

A function that returns the element from which auto-emitted events should dispatch. `updateCart` is the only action that auto-emits events.

The function receives a `meta` object with `type` (the full event name, such as `'shopify:cart:lines-update'`). When `type` is `'shopify:cart:lines-update'`, `meta` also includes `action` (`'add'`, `'remove'`, or `'update'`). Use `meta` to route different events to different DOM elements:

```javascript
document.addEventListener('DOMContentLoaded', () => {
  Shopify.actions.updateCart.configure({
    eventTarget: (meta) => {
      if (meta.type === 'shopify:cart:note-update') return document.querySelector('cart-note');
      if (meta.type === 'shopify:cart:discount-update') return document.querySelector('cart-discount');
      if (meta.type === 'shopify:cart:lines-update' && meta.action === 'add') {
        return document.querySelector('product-form');
      }
      return document.querySelector('cart-items');
    },
    async handler(defaultHandler, payload, options) {
      const result = await defaultHandler();
      customUpdateUI(result);
      return result;
    },
  });
});
```

### handler (optional)

An async function that runs in place of the default handler. It receives `defaultHandler` (the Storefront API implementation), `payload`, and `options`. To preserve the default behavior and add custom logic, call `defaultHandler()` and use its result.

If `eventTarget` is provided without `handler`, then the default Storefront API handler runs, the page reload is skipped, and auto-emitted events dispatch from the element returned by `eventTarget`. Use this pattern when the theme already listens for standard events to update its UI.

The example below uses both `eventTarget` and a custom `handler` that fetches and replaces section HTML, and registers an `openCart` configuration in the same listener:

```javascript
document.addEventListener('DOMContentLoaded', () => {
  Shopify.actions.updateCart.configure({
    eventTarget: (meta) => {
      if (meta.type === 'shopify:cart:lines-update' && meta.action === 'add') {
        return document.querySelector('product-form');
      }
      return document.querySelector('cart-items');
    },
    async handler(defaultHandler, payload) {
      const result = await defaultHandler();
      const response = await fetch(window.location.pathname + '?sections=cart-drawer');
      const { 'cart-drawer': html } = await response.json();
      document.querySelector('cart-drawer').innerHTML = html;
      return result;
    },
  });


  Shopify.actions.openCart.configure({
    handler() {
      document.querySelector('cart-drawer')?.open();
    },
  });
});
```

***

## Preventing double UI updates

If a configuration re-renders the cart drawer and the theme's components also re-render when they receive `shopify:cart:lines-update`, then both render paths run and the UI updates twice. Use `detail.source` in the resolved promise to identify the source of the update and skip the second render:

```javascript
// In the configure handler: return a result with a detail field
async handler(defaultHandler, payload) {
  const result = await defaultHandler();
  return { ...result, detail: { source: 'configure' } };
}


// In the component event listener
event.promise?.then(({ detail }) => {
  if (detail?.source === 'configure') return;
  morphSection(sectionId);
});
```

***

## Auto-emitted events

When a configured action succeeds, the matching standard events fire automatically based on what changed:

| Action | Events auto-emitted |
| - | - |
| `updateCart` | `shopify:cart:lines-update` (if lines changed), `shopify:cart:note-update` (if the note changed), `shopify:cart:discount-update` (if discounts changed), `shopify:cart:error` (if the mutation fails) |
| `openCart` | None |
| `getCart` | None |

Events dispatch from the element returned by `eventTarget`. When an action is configured, the theme doesn't need to also dispatch these events from its own code.

***

## Error handling

The `updateCart` action promise always resolves with `{ cart, userErrors?, warnings?, detail? }`. It rejects only when the action couldn't run at all, such as a network failure or malformed payload.

A `userErrors` array indicates the mutation was rejected and cart state didn't change for the input. Common Storefront API codes include `INVALID` (for a malformed input) and `MAXIMUM_EXCEEDED` (for a quantity above the item's maximum). See [`CartErrorCode`](https://shopify.dev/docs/api/storefront/latest/enums/CartErrorCode) for the full list.

A `warnings` array indicates the mutation succeeded but with caveats worth surfacing. The cart did mutate. Common Storefront API codes include `MERCHANDISE_OUT_OF_STOCK` for a line whose merchandise is now out of stock and `DISCOUNT_NOT_FOUND` for an unknown discount code. See [`CartWarningCode`](https://shopify.dev/docs/api/storefront/latest/enums/CartWarningCode) for the full list.

Check `userErrors` and `warnings` before using `cart`:

```javascript
const { cart, userErrors, warnings } = await Shopify.actions.updateCart({
  lines: [{ merchandiseId: "gid://shopify/ProductVariant/123", quantity: 1 }],
});


if (userErrors?.length) {
  // Mutation rejected. Show userErrors[0].message to the buyer.
  return;
}


if (warnings?.length) {
  // Mutation succeeded with caveats. Show warnings[0].message.
}


// Use cart for the success path.
```

Apps and override handlers consume the result with this shape regardless of whether the default Storefront API handler ran or a configured handler did.

***

## get​Cart

Action: `Shopify.actions.getCart(payload?, options?)`

Retrieves the current cart from the Storefront API, or `null` if no cart exists. The `cartId` argument is optional and auto-detected from the `cart` cookie when omitted. Multiple concurrent calls reuse the same in-flight request.

```javascript
const { cart } = await Shopify.actions.getCart();
```

Returns: `Promise<{ cart | null }>` where `cart` is a subset of the [Storefront API Cart object](https://shopify.dev/docs/api/storefront/latest/objects/Cart).

Triggers: no events.

Pass `{ signal: abortController.signal }` as a second argument to cancel the in-flight request.

***

## update​Cart

Action: `Shopify.actions.updateCart(payload, options?)`

Adds, removes, or updates cart lines. Also handles notes and discount codes. Creates a cart if none exists.

```javascript
await Shopify.actions.updateCart({
  lines: [
    { merchandiseId: "gid://shopify/ProductVariant/123", quantity: 1 },
    { id: "gid://shopify/CartLine/456", quantity: 5 },
    { id: "gid://shopify/CartLine/789", quantity: 0 },
  ],
  note: "Gift wrap please",
  discountCodes: ["SAVE10"],
});
```

Line shapes:

* To add a new item, pass `merchandiseId` and `quantity`.
* To update an existing line, pass the line's `id` and the new `quantity`.
* To remove a line, pass the line's `id` with `quantity: 0`.

Returns: `Promise<{ cart, userErrors?, warnings? }>`.

Triggers: `shopify:cart:lines-update` (if lines changed), `shopify:cart:note-update` (if the note changed), and `shopify:cart:discount-update` (if discount codes changed).

You can pass `options` as a second argument. It can include:

* `signal`: an `AbortSignal` that cancels the underlying request.
* `event.detail`: an object added to the `detail` field of any auto-emitted events.
* `event.context`: sets the `context` field of any auto-emitted `cart:lines-update` or `cart:note-update` events. Accepts `'product'`, `'cart'`, `'dialog'`, or `'standard-action'`. Defaults to `'standard-action'`.

***

## open​Cart

Action: `Shopify.actions.openCart()`

Requests that the theme display the cart UI. The default opens a `<cart-drawer-component>` or `<cart-drawer>` element if either is present, and otherwise redirects to `/cart`. Themes configure this action to open a drawer or modal.

```javascript
await Shopify.actions.openCart();
```

Returns: `Promise<void>`.

Triggers: no events.

Configure example:

```javascript
Shopify.actions.openCart.configure({
  handler() {
    document.querySelector('cart-drawer')?.open();
  },
});
```

***
