Cart
OpenShop.cart is the same cart the os-cart-button and os-cart-slideout components use. Read it to build your own cart count or “already in your cart” badge, and call it to add items or open the drawer from your own markup.
current
Section titled “current”OpenShop.cart.current: CartSummary | nullThe cart summary snapshot — the same data the slideout renders, including itemCount and subtotal. It is null when there is no cart, which covers both “not loaded yet” and “this shopper has no cart”. Like every snapshot it is frozen and replaced wholesale, so === is a valid change check.
Unlike customer.current, the cart has no unresolved state — it is never undefined. Treat null as “no cart to show” and let cart:change tell you when one arrives.
OpenShop.on('cart:change', (summary) => { document.querySelector('.nav-cart-count').textContent = String(summary?.itemCount ?? 0);});isOpen
Section titled “isOpen”OpenShop.cart.isOpen: booleanWhether the cart slideout is currently open. It flips alongside the cart:open and cart:close events.
get() and refresh()
Section titled “get() and refresh()”OpenShop.cart.get(): Promise<CartSummary | null>OpenShop.cart.refresh(): Promise<CartSummary | null>get() resolves with current when there is a summary in hand, and otherwise fetches one. refresh() always goes back to the API and publishes a cart:change with the result. Reach for refresh() after something outside Open Shop may have changed the cart.
const summary = await OpenShop.cart.get();console.log(summary?.itemCount ?? 0, 'items');open(), close(), toggle()
Section titled “open(), close(), toggle()”OpenShop.cart.open(): voidOpenShop.cart.close(): voidOpenShop.cart.toggle(): voidDrive the slideout from your own button. All three are synchronous and fire cart:open or cart:close when the state actually changes.
document.querySelector('.nav-cart').addEventListener('click', () => OpenShop.cart.toggle());The data-os-cart-open and data-os-cart-close attributes do the same thing without a script.
add(input)
Section titled “add(input)”OpenShop.cart.add(input: AddInput): Promise<CartSummary>Adds a product and resolves with the updated summary. It rejects with an OpenShopError when the add is refused, and also if the cart summary vanishes behind the add.
Like the components’ own add-to-cart buttons, a successful add() opens the cart slideout.
{ slug: 'vip-perks', // or productId: 123 — one of the two is required variantId: 45, // everything below is optional quantity: 1, subscriptionPlanId: 7, subscriptionPlanFrequencyId: 2, selections: []}| Field | Required | What it is |
|---|---|---|
productId | One of these two | The product’s numeric id. |
slug | One of these two | The product’s slug. It is resolved to a product before the add. |
variantId | No | The variant to add, for products that have variants. |
quantity | No | How many to add. Defaults to one. |
subscriptionPlanId | No | Add as a subscription on this plan instead of a one-time purchase. |
subscriptionPlanFrequencyId | No | The delivery frequency for that plan. |
selections | No | Selected product options, for products that ask for them. |
try { const summary = await OpenShop.cart.add({ slug: 'vip-perks', quantity: 1 }); console.log('cart total is now', summary.subtotal);} catch (error) { if (error.code === 'DUPLICATE') { console.log('already enrolled'); }}update(cartItemId, patch)
Section titled “update(cartItemId, patch)”OpenShop.cart.update( cartItemId: number, patch: { quantity?: number; subscriptionPlanId?: number | null }): Promise<CartSummary | null>Changes a line already in the cart and resolves with the updated summary, or null if no cart is left afterwards. Pass subscriptionPlanId: null to turn a subscription line back into a one-time purchase.
await OpenShop.cart.update(cartItemId, { quantity: 3 });await OpenShop.cart.update(cartItemId, { subscriptionPlanId: null });remove(cartItemId)
Section titled “remove(cartItemId)”OpenShop.cart.remove(cartItemId: number): Promise<CartSummary | null>Removes a line and resolves with the updated summary. Removing the last line empties the cart, so null is a normal result here — handle it rather than treating it as a failure.
contains(ref)
Section titled “contains(ref)”OpenShop.cart.contains(ref: number | string): booleanWhether the cart already holds a product. Pass a numeric productId or a string slug. This is a synchronous check against current, so it costs nothing to call inside a render — and it answers false whenever current is null.
function paintPromo() { const promo = document.querySelector('.vip-promo'); promo.hidden = OpenShop.cart.contains('vip-perks');}
OpenShop.on('cart:change', paintPromo);Cart events
Section titled “Cart events”| Event | detail | Fires when |
|---|---|---|
cart:change | CartSummary | null | Any summary replacement, including refreshes triggered by another tab. |
cart:open | undefined | The slideout opens. |
cart:close | undefined | The slideout closes. |
cart:add | { item, summary } | An add to cart succeeds. summary is the updated cart. item is the line that was added, or null when that line cannot be found in the refreshed summary — for example when a required product replaced it. |
cart:error | OpenShopError | A mutation made through OpenShop.cart.add(), update(), or remove() fails. Clicks on the components’ own buttons do not fire it. |
Each one is also dispatched on document as os:cart:change, os:cart:open, os:cart:close, os:cart:add, and os:cart:error.
The cart:add detail is { item: CartItem | null, summary: CartSummary }, so guard item before reading it:
OpenShop.on('cart:add', ({ item, summary }) => { analytics.track('add_to_cart', { itemCount: summary.itemCount, productId: item?.productId ?? null });});Recipe: lock page scroll while the cart drawer is open
Section titled “Recipe: lock page scroll while the cart drawer is open”When the slideout opens, the page behind it should stay put. Listen for the open and close events on document and toggle the lock:
<script> document.addEventListener('os:cart:open', () => { document.documentElement.style.overflow = 'hidden'; });
document.addEventListener('os:cart:close', () => { document.documentElement.style.overflow = ''; });</script>That is enough for a normal page. It is not enough if your site uses a smooth-scroll library — Lenis, Locomotive Scroll, or similar. Those libraries drive scrolling themselves, usually by transforming the page rather than by scrolling it, so overflow: hidden never reaches them and the page keeps gliding under the open drawer. Stop the library instead, and start it again on close:
<script> // `lenis` here is whatever variable your site already created the instance in. document.addEventListener('os:cart:open', () => lenis.stop()); document.addEventListener('os:cart:close', () => lenis.start());</script>