Operating Cart with Shopify Storefront API
To implement Ajax cart, you'll use Shopify Storefront API's "Cart" functionality. This page explains how to call the API and integrate it with frontend state management.
Key API Mutations
Shopify Storefront API is GraphQL-based. For cart operations, you'll use these "mutations":
| Mutation | Purpose |
|---|---|
| cartCreate | Create a new cart |
| cartLinesAdd | Add items to cart |
| cartLinesUpdate | Change item quantities |
| cartLinesRemove | Remove items from cart |
| cartQuery | Fetch cart contents |
Overall Flow
The basic cart operation flow looks like this:
Creating Cart (cartCreate)
When a customer first visits, no cart exists yet. Create one on first item add or initial visit.
mutation cartCreate($input: CartInput!) {
cartCreate(input: $input) {
cart {
id
checkoutUrl
lines(first: 100) {
edges {
node {
id
quantity
merchandise {
... on ProductVariant {
id
title
price {
amount
currencyCode
}
}
}
}
}
}
}
userErrors {
field
message
}
}
}
Key points:
cart.idis needed for all subsequent operationscheckoutUrlis used when proceeding to purchase- Check
userErrorsfor errors
Adding Items (cartLinesAdd)
Use cartLinesAdd to add items to cart.
mutation cartLinesAdd($cartId: ID!, $lines: [CartLineInput!]!) {
cartLinesAdd(cartId: $cartId, lines: $lines) {
cart {
id
lines(first: 100) {
edges {
node {
id
quantity
merchandise {
... on ProductVariant {
id
title
}
}
}
}
}
cost {
totalAmount {
amount
currencyCode
}
}
}
userErrors {
field
message
}
}
}
Example variables:
{
"cartId": "gid://shopify/Cart/abc123",
"lines": [
{
"merchandiseId": "gid://shopify/ProductVariant/12345",
"quantity": 1
}
]
}
Changing Quantities (cartLinesUpdate)
Use cartLinesUpdate to change item quantities.
mutation cartLinesUpdate($cartId: ID!, $lines: [CartLineUpdateInput!]!) {
cartLinesUpdate(cartId: $cartId, lines: $lines) {
cart {
id
lines(first: 100) {
edges {
node {
id
quantity
}
}
}
cost {
totalAmount {
amount
currencyCode
}
}
}
userErrors {
field
message
}
}
}
Key point:
lines[].idtakes the cart line ID (not the product variant ID)- Use the
node.idreturned fromcartLinesAdd
Removing Items (cartLinesRemove)
Use cartLinesRemove to delete items from cart.
mutation cartLinesRemove($cartId: ID!, $lineIds: [ID!]!) {
cartLinesRemove(cartId: $cartId, lineIds: $lineIds) {
cart {
id
lines(first: 100) {
edges {
node {
id
quantity
}
}
}
}
userErrors {
field
message
}
}
}
Frontend State Management
Beyond calling APIs, you need to manage cart state on the frontend.
Using React Context
React Context is common for sharing cart state app-wide.
// Cart state
interface CartState {
cartId: string | null;
lines: CartLine[];
totalAmount: string;
checkoutUrl: string;
isLoading: boolean;
}
// Cart actions
interface CartActions {
addToCart: (variantId: string, quantity: number) => Promise<void>;
updateQuantity: (lineId: string, quantity: number) => Promise<void>;
removeFromCart: (lineId: string) => Promise<void>;
}
Using Zustand
The lighter state management library "Zustand" is another option. Less boilerplate, simpler to write.
Either choice works—what matters is consistent cart state across the app.
Implementing Optimistic UI
From button click to API response takes 0.2-0.3 seconds. If nothing changes during this time, users feel uncertain.
Optimistic UI updates the UI immediately while sending the API request.
async function addToCart(variantId: string, quantity: number) {
// 1. Update UI immediately (optimistic)
setCart(prev => ({
...prev,
lines: [...prev.lines, { variantId, quantity, pending: true }]
}));
try {
// 2. Call API
const result = await cartLinesAdd(cartId, variantId, quantity);
// 3. On success, replace with real data
setCart(prev => ({
...prev,
lines: result.cart.lines,
totalAmount: result.cart.cost.totalAmount.amount
}));
} catch (error) {
// 4. On failure, revert
setCart(prev => ({
...prev,
lines: prev.lines.filter(line => !line.pending)
}));
showError("Couldn't add to cart");
}
}
Persisting Cart ID
Save the cart ID to localStorage so cart contents survive page reloads.
// After cart creation
localStorage.setItem('shopify_cart_id', cart.id);
// On return visit
const savedCartId = localStorage.getItem('shopify_cart_id');
if (savedCartId) {
const cart = await fetchCart(savedCartId);
setCart(cart);
}
Note:
- Carts have expiration (typically 10 days on Shopify)
- Expired cart IDs cause errors—fall back to creating new cart
Error Handling
Consider cases where API calls fail.
Common Errors
| Error | Cause | Solution |
|---|---|---|
| Out of stock | Item sold out | Notify user, offer back-in-stock alert |
| Cart expired | Saved cart ID invalid | Create new cart |
| Network error | Connection dropped | Retry, offline handling |
Architecture Diagram
The final system architecture looks like this:
Cart UI (Button, Mini cart, Slide) + State Mgmt (Context/Zustand) + localStorage (cart ID storage)
cartCreate / cartLinesAdd / cartLinesUpdate / cartLinesRemove
Navigate via checkoutUrl, payment
Summary
Ajax cart with Shopify Storefront API can be implemented by covering these points:
- cartCreate to create cart and save ID
- cartLinesAdd/Update/Remove for cart operations
- React Context/Zustand for global state management
- Optimistic UI for perceived speed improvement
- Error handling for robustness
Combining these delivers a smooth shopping experience without page transitions.