DocsAlpineWorking with the cart

Working with the cart

Drive Shopify's cart from a CascadeKit section using Alpine and the AJAX cart API.

The cart API

Shopify exposes the cart over a small set of JSON endpoints. CascadeKit's cart sections wrap these with Alpine so the UI updates without a page reload.

/cart.jsRead the current cart as JSON.
/cart/add.jsAdd a variant by id and quantity.
/cart/change.jsChange the quantity of a line item.
/cart/clear.jsEmpty the cart.

Add to cart

Post the variant id to /cart/add.js and reflect the result in Alpine state:

snippets/add-to-cart.liquid
<div x-data="{ loading: false }"><button  x-on:click="    loading = true;    await fetch('/cart/add.js', {      method: 'POST',      headers: { 'Content-Type': 'application/json' },      body: JSON.stringify({ id: {{ product.selected_variant.id }}, quantity: 1 })    });    loading = false;    $dispatch('cart:refresh');  "  :disabled="loading"  class="rounded-control bg-neutral-900 px-5 py-2.5 text-white">  <span x-text="loading ? 'Adding…' : 'Add to cart'"></span></button></div>

Refresh the drawer

The $dispatch('cart:refresh') above lets the cart drawer listen and re-fetch itself — components stay decoupled:

sections/cart-drawer.liquid
<divx-data="{ items: [] }"x-on:cart:refresh.window="  const cart = await (await fetch('/cart.js')).json();  items = cart.items;"><template x-for="item in items" :key="item.key">  <p x-text="item.product_title"></p></template></div>

Always re-read the cart

Don't trust optimistic counts. After any mutation, re-fetch /cart.js so totals and line items stay in sync with Shopify.