Skip to content

Cart support guide ​

Diagnostic and troubleshooting reference for the cart drawer + cart page + checkout bridge — applied-discount display, three-tier upsell cascade (metafield → API → collection), free-shipping progress bar, inline cart attributes (delivery date, marketing/SMS consent, gift wrapping, free-form cart-attribute blocks), optimistic quantity update with rollback, pre-emptive inventory cap, OOS-during-checkout banner, and sticky mobile checkout footer.

For when something does not work the way it should. The merchant-facing setup guide is at Cart customization guide.

FAQ ​

Q1: Where is the discount-code field? ​

There isn't one, by design. Shopify's Theme Store requirements name cart-level discount codes as app-like functionality that themes must not implement, so the theme collects no codes. Shoppers enter codes at Shopify checkout, and you can also apply one up front with a /discount/{code} link (for example from an email or a banner).

What the theme does do is display a discount Shopify has already applied: the cart page and the cart drawer each render a chip per entry in cart.cart_level_discount_applications, plus per-line savings from line_level_discount_allocations, and the subtotal reflects them.

If an applied discount is not showing:

  1. Visit /discount/{your_code} directly in the browser. It 302-redirects to the storefront home page by default (append ?redirect=/cart to land on the cart) and sets the discount cookie — the redirect fires whether or not the code is valid, so use step 2 to confirm validity.
  2. Run the console diagnostic snippet below and inspect cart.cart_level_discount_applications. If it is empty after the redirect, Shopify rejected the code's eligibility silently — check the date range, customer eligibility and minimum-order rules in Shopify admin → Discounts.

Q2: Why is my upsell empty? ​

The cart upsell uses a 3-tier cascade:

  1. Per-product product.metafields.uisce_cart.cross_sells (list of handles).
  2. Shopify Recommendations API (intent=complementary).
  3. Merchant-set upsell_collection setting on cart section.

If all 3 sources yield nothing, the upsell-grid stays empty. Check:

  • Is the upsell_collection setting populated? (Theme editor → Cart section → Upsell → upsell_collection)
  • Do your products have related/complementary items in Shopify admin → Products → Recommendations?
  • Have you created the uisce_cart.cross_sells metafield definition? (Admin → Settings → Custom data → Products)

You can override the cascade via upsell_source_tier setting (cascade / metafield_only / api_only / collection_only).

Q3: Why is the checkout button disabled? ​

Two possible gates:

  1. Policy-acknowledgement checkbox is unchecked (existing behavior — must check Terms & Conditions before checkout).
  2. OOS-during-checkout banner is active (an item in cart became unavailable mid-session). Click the OOS banner's "Try again" or remove the offending line.

Use the diagnostic snippet to read OOS banner mounted + OOS banner visible. If both true, the OOS banner is the gate.

Q4: What does "Only N available" mean? ​

The shopper tried to bump a quantity past inventory. Shopify's inventory tracking has determined N units are in stock. The theme caps the input at N + shows the inline message.

If you see this for an item that should have more inventory:

  • Double-check inventory_tracked + inventory_quantity on the variant in Shopify admin.
  • Confirm inventory_policy = deny (otherwise Shopify allows backorder; the cap doesn't apply).
  • Run the diagnostic snippet → inspect inventoryAttrs to see what data-inventory-quantity + data-inventory-policy are rendered on each line.

Q5: Why does the optimistic quantity update sometimes "flash back"? ​

Two cases:

  1. The mutation failed on the server side (rare; usually inventory drifted between cart-render and click — see Q4).
  2. The latest-wins guard rejected the response because a newer mutation superseded it (rapid +/+/+/+ clicks coalesce to a single mutation; intermediate UI states are discarded).

Both are expected behavior. To check whether a mutation is still in flight, read the diagnostic snippet's in-flight lines count (lines mid-mutation carry the cart__line-item--in-flight class) — it returns to 0 once the network settles. The mutationTokens count / debounceTimers count rows only show which lines have mutated this session; entries persist after settling, so non-zero there does not mean in-flight.

Q6: How do I customize the gift-wrap label? ​

The simple enable_gift_wrapping toggle uses a fixed label from your locale file ({{ 'cart.gift_wrapping' | t }} — "Add gift wrapping" in English). To customize:

  1. Disable enable_gift_wrapping on the cart section.
  2. Add a cart-attribute block.
  3. Set key=gift_wrapping, input_type=checkbox, and your custom label.

Multiple cart-attribute blocks can be stacked for richer gift-wrap variants (e.g., gift_wrap_message + gift_recipient_name).

Q7: How do I add a per-line attribute (vs cart-level)? ​

  • Cart-level attributes post as attributes[*] (order-level — visible on order confirmation under "Notes").
  • Per-line attributes post as properties[*] and bind to a specific line item (e.g., per-recipient name on a multi-gift order).

The free-form cart-attribute block ships a Scope select: Cart (default) posts attributes[*]; Line item posts properties[*]. Set the block's Scope to "Line item" and place it in the per-line dispatch slot so the input binds to that line item.

Setting enable_sms_consent=true renders a checkbox that posts as attributes[sms_consent]=true if the shopper checks it. You connect this to your SMS marketing app (e.g., Klaviyo, Postscript) via the Shopify admin app integration — the theme just captures consent.

Same applies to enable_marketing_consent for email — posts as attributes[email_consent].

Q9: Why is delivery date required on Uisce only? ​

Uisce ships the Services niche; merchants on Uisce sell appointments + bookings, where a delivery date IS the service date. So listings/uisce/templates/cart.json ships with delivery_date_required=true by default. Other presets default to OFF (you can toggle ON if your products require a delivery date — e.g., live-plant Talamh shops or perishable Neart products).

Q10: Why is the cart-drawer slide direction wrong in Arabic? ​

Verify the storefront has Arabic published (Shopify admin → Settings → Languages → Add language → Arabic). The theme uses inset-inline-end + a [dir='rtl'] keyframe override so the drawer enters from the inline-end edge in both directions — in Arabic that is the physical left edge, which is the intended mirrored behavior, not a bug. If the slide is still wrong:

  1. Check <html lang> and <html dir> are correctly set on the page (use the diagnostic snippet's cart.token line — it confirms window.CartAPI is reachable; for dir, inspect document.documentElement.dir).
  2. Confirm the storefront URL has the locale path /ar/cart (or your store's RTL locale path) — the locale routing must be active.
  3. Report the dev-store URL + Shopify-admin language settings + locale-routing path to support.

Decision tree: "Why is X not working?" ​

Applied discount not showing in the cart ​

The theme has no discount-code field (see Q1); it only displays what Shopify has already applied.

  1. Visit /discount/{your_code} directly in the browser. It 302-redirects to the storefront home page by default (append ?redirect=/cart to land on the cart) and sets the discount cookie — the redirect fires whether or not the code is valid, so use step 2 to confirm validity.
  2. Run console snippet (below). Inspect cart.cart_level_discount_applications.size. If 0 after the redirect — Shopify rejected eligibility silently; check the code's date range, customer eligibility and minimum-order rules in Shopify admin → Discounts.
  3. If it is non-zero but no chip renders, the cart section is stale — hard-refresh /cart (the drawer re-reads from the Section Rendering API on the next cart update).

Upsell cards not showing ​

  1. Open /recommendations/products.json?product_id={any_product_id}&intent=complementary&limit=4 directly. Does it return products? If no — Shopify hasn't computed complementary recommendations yet (requires sales history).
  2. Run console snippet. Inspect upsell tier1 handles (per-product metafield) + upsell tier3 handle (fallback collection). Tier 1 yielded? Tier 3 set?
  3. Verify upsell_collection setting is populated on the cart section.
  4. If upsell_source_tier=metafield_only is set and no products have uisce_cart.cross_sells populated, the JS cascade returns nothing and the server-rendered collection cards stay visible (the server renders upsell_collection cards whenever that setting is populated). The grid is empty only when upsell_collection is also unset — populate the metafield or switch back to cascade.

OOS-during-checkout banner not appearing ​

  1. Verify a product variant has inventory_quantity=0 + inventory_management=shopify + inventory_policy=deny. Without policy=deny, Shopify allows backorder.
  2. Run console snippet. After clicking Check out, inspect OOS banner mounted and OOS banner visible. If mounted=false, the banner element didn't render — confirm the cart-page has [data-checkout-bridge-banner] in the DOM.
  3. Inspect checkout-retry-counter in sessionStorage. null is the normal state — the counter is written only when the bridge's availability-validation fetch fails, and a successful validation removes it. A numeric value means recent validation-fetch failures; at 3 the bridge clears the counter and lets checkout proceed (fails open).

Optimistic UI rolls back unexpectedly ​

  1. Run the console snippet and read in-flight lines — non-zero means a mutation is still mid-cycle. (mutationTokens count / debounceTimers count persist after settling and do not indicate in-flight state.)
  2. Check Network tab for failed /cart/change.js POST. Inspect the 422 description — the theme matches it against three unanchored patterns: /Only (\d+) available/i, /You can only add (\d+) of this item/i, /All (\d+) of .+ are in your cart/i.
  3. If the rollback only happens on rapid +/+/+/+ clicks, this is the latest-wins guard working as designed (intermediate UI states are discarded).

Cart-attribute not posting to order ​

  1. Confirm the cart-attribute block is added to the cart section (theme editor → Cart → Add block) — it renders as a direct block of the cart section, inside the cart form.
  2. Inspect the rendered <input name="attributes[your_key]"> — confirm the name attribute is attributes[*] (cart-level) or properties[*] (per-line).
  3. Run the diagnostic snippet → inspect cart.attributes. Should reflect the field's value after a /cart/update.js POST.
  4. If cart.attributes is {}, the field's value didn't post — confirm the input sits inside the cart form and its name is exactly attributes[your_key]; the cart form posts attributes by input name alone.
  1. Confirm viewport is < 1024 px (DevTools device emulation) — the element always renders, but CSS shows it only below 1024 px (display: none on desktop, where the sticky sidebar handles the checkout CTA).
  2. Run diagnostic snippet → inspect sticky-footer present. It should be true at any viewport; if false, the [data-sticky-mobile-checkout] element isn't in the DOM — confirm cart.liquid renders the sticky-footer markup.
  3. iOS Safari sometimes mis-computes env(safe-area-inset-bottom) — try toggling between portrait + landscape orientation.

Shopify checkout rejection mappings ​

When Shopify checkout itself rejects a cart at /checkout commit time (after the theme's cart layer), Shopify shows error messages on the checkout page. Common rejections + their cart-side root causes:

Shopify messageLikely causeFix
"Some items are no longer available"OOS variant in cart at /checkout commitTheme banner should have caught this PRE-redirect; if it didn't, check the OOS pre-validate flow — /cart.js does not expose per-item availability, so the bridge probes GET /products/{handle}.js and reads variant.available. Run the console snippet to inspect inventory state.
"Discount code [X] is no longer valid"Discount expired / max-uses-reached / customer-ineligibleVerify in Shopify admin → Discounts. Check date range, customer eligibility, minimum order requirements.
"Cart is empty" on /checkoutCart cleared post-add (race)Network race during ATC; rare; refresh page. Check the diagnostic snippet's cart.item_count — should match what the shopper sees.
"Inventory is no longer sufficient"Inventory drifted after cart-render but before checkoutOptimistic UI should rollback to cap; if checkout still rejects, the cap drifted further. Refresh cart and retry.
"Required attribute X is missing"cart-attribute required=true field not filledFind the missing field on cart page; fill before checkout. The theme blocks checkout client-side via required validator on the input.

Console diagnostic snippet ​

Paste this into your browser DevTools Console on /cart or any page with the cart-drawer mounted. Produces a single console.table of cart state — safe, read-only, and adds zero theme code.

javascript
(async function () {
  const cart = await window.CartAPI.getCart();
  const drawer = document.querySelector('cart-drawer');
  const page = document.querySelector('cart-page');
  const upsellSlot = document.querySelector('[data-cart-upsell-slot]');
  const oosBanner = document.querySelector('[data-checkout-bridge-banner]');
  const stickyFooter = document.querySelector('[data-sticky-mobile-checkout]');
  const cartHost = page || drawer;
  const mutationTokens = cartHost && cartHost._mutationTokens ? Array.from(cartHost._mutationTokens.entries()) : [];
  const debounceTimers = cartHost && cartHost._debounceTimers ? Array.from(cartHost._debounceTimers.entries()) : [];
  const inFlightLines = document.querySelectorAll('.cart__line-item--in-flight').length;
  const inventoryAttrs = Array.from(document.querySelectorAll('[data-inventory-quantity]')).map((el) => ({
    key: el.dataset.lineKey,
    inventoryQuantity: el.dataset.inventoryQuantity,
    inventoryPolicy: el.dataset.inventoryPolicy,
  }));
  const upsellCards = document.querySelector('[data-cart-upsell-cards]');
  const upsellTier1 = upsellCards ? upsellCards.dataset.tier1Handles : null;
  const upsellTier3 = upsellCards ? upsellCards.dataset.upsellTier3 : null;
  const retryCounter = sessionStorage.getItem('uisce.state.v1.checkout-bridge-retry-counter');
  const freeShippingDebounce = sessionStorage.getItem('uisce.state.v1.free-shipping-announce');
  console.table({
    'cart.token': cart.token,
    'cart.item_count': cart.item_count,
    'cart.total_price (cents)': cart.total_price,
    'cart.note (length)': (cart.note || '').length,
    'cart.attributes': JSON.stringify(cart.attributes || {}),
    'cart.cart_level_discount_applications.size': (cart.cart_level_discount_applications || []).length,
    'drawer mounted': drawer !== null,
    'page mounted': page !== null,
    'upsell-slot present': upsellSlot !== null,
    'upsell tier1 handles': upsellTier1,
    'upsell tier3 handle': upsellTier3,
    'OOS banner mounted': oosBanner !== null,
    'OOS banner visible': oosBanner && !oosBanner.hidden,
    'sticky-footer present': stickyFooter !== null,
    'inventoryAttrs count': inventoryAttrs.length,
    'mutationTokens count': mutationTokens.length,
    'debounceTimers count': debounceTimers.length,
    'in-flight lines': inFlightLines,
    'checkout-retry-counter': retryCounter,
    'free-shipping-debounce': freeShippingDebounce,
    'window.uisceStrings size': Object.keys(window.uisceStrings || {}).length,
  });
  console.log('Full cart:', cart);
  console.log('Inventory attrs:', inventoryAttrs);
  console.log('Mutation tokens:', mutationTokens);
})();

This dumps cart state + drawer/page mount status + upsell tier resolution + OOS banner state + inventory cap state + attribute state + sessionStorage retry counters + uisceStrings registry size. Send the table to support.

What to look for in the output:

  • drawer mounted === true AND page mounted === true (on /cart) — both Custom Elements connected.
  • cart.item_count > 0 — cart has line items (otherwise upsell cascades to fallback collection, OOS banner is moot).
  • upsell-slot present === true if enable_upsell=true; if false, upsell setting is OFF.
  • upsell tier1 handles populated when uisce_cart.cross_sells metafield is set on a line-item product.
  • upsell tier3 handle set when upsell_collection setting is populated.
  • OOS banner mounted === true always (the element exists hidden); visible flips to true when an item is no longer available.
  • sticky-footer present === true at any viewport — the element always renders; CSS hides it at >= 1024 px (desktop uses the sticky sidebar instead).
  • inventoryAttrs count > 0 for any line-item with inventory_tracked + policy=deny.
  • mutationTokens count + debounceTimers count — which lines have mutated this session; entries persist after the network settles (cleared only when the element disconnects), so non-zero does NOT mean in-flight. Use in-flight lines for that — it drops to 0 once the network settles.
  • checkout-retry-counter — written only when the checkout-bridge's availability-validation fetch fails; a successful validation removes it, so null is the normal state. At 3 consecutive failures the bridge clears the counter and lets checkout proceed (fails open).
  • cart.cart_level_discount_applications.size > 0 when a discount is applied.

Embedded diagnostic tool: A full embedded diagnostic UI is planned for a future update. The console snippet above provides the core diagnostic value.

Curl cheat-sheet ​

Use these one-liners to verify your live store's cart flow. Replace {store} with your storefront domain (e.g., uisce.sionnach.solutions) and capture the storefront-password cookie first if your store is a dev store.

For dev stores, capture the auth cookie via:

bash
curl -s -o /dev/null -D - "https://{store}/password" \
  -d "form_type=storefront_password&password={password}" \
  -L -c cookies.txt | head -5
# Then add `-b cookies.txt` to all subsequent curl commands below

For live production stores (no password protection), no cookie is needed.

Get cart state ​

bash
curl -s 'https://{store}/cart.js' | jq .

Expected: JSON with token, note, attributes, total_price, item_count, items[], cart_level_discount_applications[].

Bundled section refresh ​

The theme refreshes sections two ways: the optimistic flow bundles a sections key into the mutation POST body, and the discount flow GETs the page URL via the Section Rendering API.

bash
# Sections bundled into a mutation (optimistic-UI flow)
curl -s -X POST 'https://{store}/cart/change.js' \
  -H 'Content-Type: application/json' \
  -d '{"line": 1, "quantity": 1, "sections": "cart-drawer"}' | jq '.sections | keys'

# Section Rendering API GET on the page URL (discount-refresh flow)
curl -s 'https://{store}/cart?sections=cart,cart-drawer' | jq 'keys'

Expected: the POST response carries a sections object with rendered HTML for the requested sections; the GET returns JSON keyed by section name.

bash
curl -sIw '%{http_code} %{redirect_url}\n' -c cookies.txt 'https://{store}/discount/WELCOME20'

Expected: HTTP 302 with a Location redirect to the storefront root (/) by default — append ?redirect=/cart to land on the cart instead. The redirect fires for unknown codes too, so it does not prove validity; verify with the next probe.

Change item quantity ​

bash
curl -s -X POST 'https://{store}/cart/change.js' \
  -H 'Content-Type: application/json' \
  -d '{"line": 1, "quantity": 3}' | jq .

Expected: JSON with the updated cart state. If line 1 has inventory < 3, the response will include description with the inventory-cap message.

Update cart attributes + note ​

bash
curl -s -X POST 'https://{store}/cart/update.js' \
  -H 'Content-Type: application/json' \
  -d '{"note": "Please ring doorbell", "attributes": {"delivery_date": "2026-05-01", "gift_wrapping": "true"}}' | jq .

Expected: JSON with note + attributes reflecting the posted values.

Verify the applied discount (the theme's own Apply check) ​

bash
curl -s -b cookies.txt 'https://{store}/cart.js' | jq '.cart_level_discount_applications'

Expected: a populated array when the code applied to the current cart; [] after the redirect means Shopify rejected eligibility (the theme surfaces "That code isn't valid." the same way).

Remove the currently-applied discount ​

bash
curl -s -X POST 'https://{store}/cart/update.js' \
  -H 'Content-Type: application/json' \
  -d '{"discount_code": ""}' | jq .

Expected: JSON with cart_level_discount_applications[] empty.

Get product recommendations (tier 2 of cascade) ​

bash
curl -s 'https://{store}/recommendations/products.json?product_id={id}&intent=complementary&limit=4' | jq .

Expected: JSON with products[] array of recommended products. Empty when Shopify has not yet computed complementary recommendations (requires sales history).

Verify metafield definition exists (Admin GraphQL) ​

Metafield definitions are exposed via the Admin GraphQL API (there is no REST metafield_definitions.json resource). You can also check Shopify admin → Settings → Custom data → Products.

bash
curl -s -X POST 'https://{store-myshopify-domain}/admin/api/2024-10/graphql.json' \
  -H "X-Shopify-Access-Token: $UISCE_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"query": "{ metafieldDefinitions(first: 20, ownerType: PRODUCT, namespace: \"uisce_cart\") { nodes { namespace key name type { name } } } }"}' | jq .

Expected: nodes[] containing the uisce_cart.cross_sells definition (and any other uisce_cart.* definitions you have created).

Empirical availability check ​

bash
curl -s 'https://{store}/products/{handle}.js' | jq '.variants[] | {id, sku, available}'

Expected: per-variant available boolean — the same GET /products/{handle}.js probe the checkout bridge uses. Public product JSON does not expose inventory_quantity / inventory_policy; read exact quantities and policy in Shopify admin (or via the Admin API), and cross-check the cap values the theme renders via the snippet's inventoryAttrs.

Verify cart attributes post correctly (round-trip) ​

bash
# Set
curl -s -X POST 'https://{store}/cart/update.js' \
  -H 'Content-Type: application/json' \
  -d '{"attributes": {"test_key": "test_value"}}' | jq '.attributes'

# Read back
curl -s 'https://{store}/cart.js' | jq '.attributes'

Expected: both responses show {"test_key": "test_value"}.

Need more help? ​

  • For deep diagnostic beyond what the console snippet captures, a full embedded diagnostic UI is planned for a future update.
  • File a GitHub issue with the console-snippet output + browser/OS + preset.
  • See the cart customization guide for configuration walkthrough.

Built for the Shopify Theme Store.