Skip to content

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.

OpenShop.cart.current: CartSummary | null

The 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.

app.js
OpenShop.on('cart:change', (summary) => {
document.querySelector('.nav-cart-count').textContent = String(summary?.itemCount ?? 0);
});
OpenShop.cart.isOpen: boolean

Whether the cart slideout is currently open. It flips alongside the cart:open and cart:close events.

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.

app.js
const summary = await OpenShop.cart.get();
console.log(summary?.itemCount ?? 0, 'items');
OpenShop.cart.open(): void
OpenShop.cart.close(): void
OpenShop.cart.toggle(): void

Drive the slideout from your own button. All three are synchronous and fire cart:open or cart:close when the state actually changes.

app.js
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.

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.

AddInput
{
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: []
}
FieldRequiredWhat it is
productIdOne of these twoThe product’s numeric id.
slugOne of these twoThe product’s slug. It is resolved to a product before the add.
variantIdNoThe variant to add, for products that have variants.
quantityNoHow many to add. Defaults to one.
subscriptionPlanIdNoAdd as a subscription on this plan instead of a one-time purchase.
subscriptionPlanFrequencyIdNoThe delivery frequency for that plan.
selectionsNoSelected product options, for products that ask for them.
app.js
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');
}
}
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.

app.js
await OpenShop.cart.update(cartItemId, { quantity: 3 });
await OpenShop.cart.update(cartItemId, { subscriptionPlanId: null });
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.

OpenShop.cart.contains(ref: number | string): boolean

Whether 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.

app.js
function paintPromo() {
const promo = document.querySelector('.vip-promo');
promo.hidden = OpenShop.cart.contains('vip-perks');
}
OpenShop.on('cart:change', paintPromo);
EventdetailFires when
cart:changeCartSummary | nullAny summary replacement, including refreshes triggered by another tab.
cart:openundefinedThe slideout opens.
cart:closeundefinedThe 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:errorOpenShopErrorA 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:

app.js
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:

Site footer custom code
<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:

Site footer custom code
<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>