# Carts

The cart and its fifteen mutations: lines, discount codes, buyer identity, notes, attributes, metafields, delivery addresses and delivery options, ending at the checkout URL.

The cart holds a buyer's session: its lines, a discount, a delivery address and a chosen delivery option. Every mutation returns the stored cart. Create a cart with a discount code, add a line, give it a delivery address and choose Express:

```bash
SF=("$RYSTIC_URL/api/2026-10/graphql.json" -H 'X-Shopify-Storefront-Access-Token: placeholder' -H 'Content-Type: application/json')
CART='gid://shopify/Cart/8ef910ca90bf4c9d8000000000000001?key=7c0a4019'
curl -s "${SF[@]}" -d '{"query":"mutation { cartCreate(input: {lines: [{merchandiseId: \"gid://shopify/ProductVariant/9002\", quantity: 2}], discountCodes: [\"WELCOME10\"]}) { cart { cost { subtotalAmount { amount } totalAmount { amount } } } } }"}' | jq -c '.data.cartCreate.cart.cost'
# -> {"subtotalAmount":{"amount":"48.0"},"totalAmount":{"amount":"43.2"}}
curl -s "${SF[@]}" -d '{"query":"mutation { cartLinesAdd(cartId: \"'"$CART"'\", lines: [{merchandiseId: \"gid://shopify/ProductVariant/9001\", quantity: 1}]) { cart { lines(first: 5) { nodes { quantity merchandise { ... on ProductVariant { title } } } } } } }"}' | jq -c '.data.cartLinesAdd.cart.lines.nodes | map({title: .merchandise.title, quantity})'
# -> [{"title":"S / Natural","quantity":1},{"title":"M / Natural","quantity":2}]
curl -s "${SF[@]}" -d '{"query":"mutation { cartDeliveryAddressesAdd(cartId: \"'"$CART"'\", addresses: [{selected: true, address: {deliveryAddress: {address1: \"1 Main St\", city: \"Portland\", provinceCode: \"OR\", countryCode: US, zip: \"97201\"}}}]) { userErrors { code } } }"}' | jq -c '.data.cartDeliveryAddressesAdd.userErrors'
# -> []
curl -s "${SF[@]}" -d '{"query":"{ cart(id: \"'"$CART"'\") { deliveryGroups(first: 1) { nodes { id selectedDeliveryOption { title } deliveryOptions { handle title estimatedCost { amount } } } } } }"}' | jq -c '.data.cart.deliveryGroups.nodes[0] | {selected: .selectedDeliveryOption.title, options: [.deliveryOptions[] | {title, amount: .estimatedCost.amount}]}'
# -> {"selected":"Standard","options":[{"title":"Standard","amount":"8.0"},{"title":"Express","amount":"15.0"}]}
GROUP=$(curl -s "${SF[@]}" -d '{"query":"{ cart(id: \"'"$CART"'\") { deliveryGroups(first: 1) { nodes { id deliveryOptions { handle title } } } } }"}' | jq -r '.data.cart.deliveryGroups.nodes[0].id')
EXPRESS=$(curl -s "${SF[@]}" -d '{"query":"{ cart(id: \"'"$CART"'\") { deliveryGroups(first: 1) { nodes { deliveryOptions { handle title } } } } }"}' | jq -r '.data.cart.deliveryGroups.nodes[0].deliveryOptions[] | select(.title == "Express") | .handle')
curl -s "${SF[@]}" -d '{"query":"mutation { cartSelectedDeliveryOptionsUpdate(cartId: \"'"$CART"'\", selectedDeliveryOptions: [{deliveryGroupId: \"'"$GROUP"'\", deliveryOptionHandle: \"'"$EXPRESS"'\"}]) { cart { cost { subtotalAmount { amount } totalAmount { amount } checkoutChargeAmount { amount } } } } }"}' | jq -c '.data.cartSelectedDeliveryOptionsUpdate.cart.cost'
# -> {"checkoutChargeAmount":{"amount":"72.0"},"subtotalAmount":{"amount":"72.0"},"totalAmount":{"amount":"79.8"}}
```

### What a mutation returns

`cartCreate` takes lines, a buyer identity, attributes, a note, discount codes, metafields and delivery addresses in one input. Every other mutation changes one part of the cart. Each payload returns the stored cart exactly as `cart(id:)` reads it, with `userErrors` and `warnings` in Shopify's shapes. Lines list newest first.

Reading an unknown cart id returns `cart: null`. Pass ids back whole: a cart's id carries its `?key=`, and a line's id its `?cart=`.

### Costs and discounts

Each `amount` is a decimal string in the cart's currency, so `"48.0"` in USD is $48.00.

| Field | What it holds |
|---|---|
| `cost.subtotalAmount` | the lines' total |
| `cost.totalAmount` | the lines' total, less the discount, plus the selected delivery option |
| `cost.checkoutChargeAmount` | the lines' total, with neither the discount nor delivery |

The cart above holds 72.00 of lines, less 10% for `WELCOME10`, plus 15.00 for Express. The `shopify-storefront_checkout_cart` cart returns its seeded `cost` instead, and [Limitations](https://www.rystic.ai/docs/simulators/shopify-storefront.md#limitations) says when that ends.

A code the store holds applies its percentage on every later write and appears in `discountAllocations`. An unknown code stays in the cart as `applicable: false` and repeats a `DISCOUNT_NOT_FOUND` warning on each later mutation. Every scenario holds `WELCOME10`, and `state_discount_code` seeds more.

### Delivery

A cart with a selected delivery address and at least one line has one delivery group, unless the buyer's delivery preference includes `PICK_UP`. A new group offers Standard at 8.00 and Express at 15.00, with Standard selected. `cartSelectedDeliveryOptionsUpdate` selects an option by its handle.

**Differs from Shopify:** a new group always offers these two options. Shopify takes its rates from the shop's delivery profile, and they can be free.

| A write that | The delivery group |
|---|---|
| keeps a selected address and a line, with no `PICK_UP` preference | stays, with its options |
| removes the selected address or the last line | is removed |
| sets a delivery preference that includes `PICK_UP` | is removed |
| restores a selected address and a line after the group was removed, with no `PICK_UP` preference | is created again, with Standard at 8.00 and Express at 15.00 |

`cartDeliveryAddressesReplace` can remove the group and create a new one in the same write, when the conditions above hold.

A scenario's seeded cart keeps its seeded options while its group survives. The `shopify-storefront_checkout_cart` cart offers Standard at 6.95, Express at 18.50 and Pickup in store at 0.00, and keeps them through writes such as `cartLinesAdd`. Once its group is removed, a new group offers the usual Standard and Express, so the seeded options don't return.

### Metafields

`cartMetafieldsSet` stores a metafield under the app's reserved namespace (`app--…`), `cart.metafield(key:)` reads it back and `cartMetafieldDelete` removes it.

### Checkout

The simulator ends at the cart's `checkoutUrl`. Payment, completion and orders are not simulated.

**Differs from Shopify:** `checkoutUrl` points at the simulator and serves nothing. Shopify's `checkoutUrl` sends the buyer to Shopify's web checkout.

---

Source: https://www.rystic.ai/docs/simulators/shopify-storefront/features/carts · Markdown: https://www.rystic.ai/docs/simulators/shopify-storefront/features/carts.md
