Skip to Content

Simulators

Carts

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:

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.

FieldWhat it holds
cost.subtotalAmountthe lines’ total
cost.totalAmountthe lines’ total, less the discount, plus the selected delivery option
cost.checkoutChargeAmountthe 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 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 thatThe delivery group
keeps a selected address and a line, with no PICK_UP preferencestays, with its options
removes the selected address or the last lineis removed
sets a delivery preference that includes PICK_UPis removed
restores a selected address and a line after the group was removed, with no PICK_UP preferenceis 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.

Last updated on