Cart
Adding and updating line items, and how cart identity survives cross-origin, cookie-blocked headless integrations.
storefront.cart is an event emitter (see Emitter) wrapping the shop’s cart session.
storefront.cart.on('updated', function() {
document.querySelector('#cart-count').textContent = storefront.cart.units
})
// Add by numeric product/variant id — see addItem below for the full signature
storefront.cart.addItem(productId, variantId, 1)
// Add by a product's URL slug instead — no need to look up its numeric id yourself,
// the server resolves it (product must have exactly one variant, or you'll need
// to resolve the variant id yourself and use addItem)
storefront.cart.quickAdd('aurora-runner', function(item) {
if (item) console.log('added', item)
})
quickAdd(productIdOrSlug, fn?)— Add a product by numeric ID or by URL slug. A numeric-looking string/number is sent asproduct_id; anything else is sent asproduct_slugand resolved server-side. RespectsonBeforeAdd(see Config Reference) the same wayaddItemdoes.addItem(productId, variantId, quantity, formData?, fn?)— Add or increase quantity of a line item. PassvariantId: -1to let the server pick the product’s default/only variant.formData.product_slugresolves a slug the same wayquickAdddoes internally.
Cross-origin cart identity
The cart session is identified server-side by an id (_btc_oid) that hosted themes carry
in a same-site cookie automatically. A headless page served from a different origin than
apiBase — true for almost any headless integration that isn’t proxied under the shop’s
own domain — can’t rely on that cookie the normal way: SameSite/Secure cross-site
cookie rules would require the API to be served over HTTPS just for the cookie to be
honored.
To sidestep that entirely, the SDK’s cart client doesn’t depend on the cookie:
- Every cart response includes the cart’s id (
cart.id), which the SDK caches inlocalStorageand sends back as anX-Cart-Tokenheader on subsequent cart requests — a plain custom header, not a cookie, so none of theSameSite/HTTPS requirements apply. Server-side,cart_cookie(the single method every cart-resolving code path inapp.rbgoes through) accepts either the cookie or this header, in that order. - The one exception is the checkout button itself: it’s a real
<form method="post">full-page navigation (viacart-table-v3), which can’t carry a custom header. It carries the same id as a hiddencart_tokenfield instead, whichcart_cookiealso accepts as a final fallback. - The cookie is still set and read too (unchanged for hosted themes, and same-origin headless integrations get it for free) — the token/field are purely an additional path, not a replacement.
This means cart identity survives even with cookies fully blocked (verified by wiping all cookies mid-session and continuing to add items / checking out — same cart throughout).