Skip to content

Cart support guide

Diagnostic and troubleshooting reference for the cart drawer + cart page + checkout bridge — elevated discount UX, 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, single-code discount semantics, 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: Why isn't my discount applying?

Check the discount code is enabled in Shopify admin → Discounts. The theme calls Shopify's /discount/{code} endpoint; it sets a discount cookie if the code is valid + active. If you see "That code isn't valid", the code itself isn't matching Shopify's eligibility rules (date range, customer eligibility, minimum order, etc.) — verify in Shopify admin.

To diagnose:

  1. Visit /discount/{your_code} directly in the browser. Does it redirect to /cart? If no — code isn't valid in Shopify admin.
  2. Open DevTools → Network → click Apply. Does the request hit /discount/{code}? Does the subsequent /cart.js GET show cart_level_discount_applications populated? If no — discount eligibility issue (Shopify side).
  3. Run the console diagnostic snippet below. Inspect cart.cart_level_discount_applications. If empty after a successful redirect — Shopify rejected eligibility silently.

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. Use the diagnostic snippet to read mutationTokens count + debounceTimers count — non-zero values mean a mutation cycle is 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_wrap.label' | t }}). 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 defaults to cart-level. To make per-line, place the block inside a per-line section. (Note: 06.6 v1 ships cart-level for cart-attribute; per-line variants are the niche-specific blocks like cart-service-location for Uisce.)

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[marketing_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 templates/cart.uisce.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 to slide from inline-start in RTL. 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?"

Discount-code not applying

  1. Visit /discount/{your_code} directly in browser. Does it redirect to /cart? If no — code isn't valid in Shopify admin.
  2. Open DevTools → Network → click Apply. Does the request hit /discount/{code}? Does the subsequent /cart.js GET show cart_level_discount_applications populated? If no — discount eligibility issue (Shopify side).
  3. Run console snippet (below). Inspect cart.cart_level_discount_applications.size. If 0 after a successful redirect — Shopify rejected eligibility silently.

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 upsell will stay empty by design — switch back to cascade or populate the metafield.

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. If null initially, the bridge hasn't run yet; click Check out to trigger it.

Optimistic UI rolls back unexpectedly

  1. Run console snippet to read mutationTokens count + debounceTimers count.
  2. Check Network tab for failed /cart/change.js POST. Inspect 422 description — should match the inventory-cap regex (/^You can only add (\d+)/).
  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 inside a cart-attributes block container (or the cart section directly).
  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 — check that the form's <input type="hidden" name="form_type" value="cart-attributes"> (or equivalent) is present.
  1. Confirm viewport is < 1024 px (DevTools device emulation).
  2. Run diagnostic snippet → inspect sticky-footer present. If false, the [data-sticky-mobile-checkout] element isn't in the DOM — confirm cart.liquid renders the sticky-footer markup.
  3. Confirm the OOS banner is NOT active (the sticky footer is suppressed when OOS banner is visible — by design).
  4. 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 OOS pre-validate flow + cart.items[N].available + 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 mutationTokens = drawer && drawer._mutationTokens ? Array.from(drawer._mutationTokens.entries()) : [];
  const debounceTimers = drawer && drawer._debounceTimers ? Array.from(drawer._debounceTimers.entries()) : [];
  const inventoryAttrs = Array.from(document.querySelectorAll('[data-inventory-quantity]')).map((el) => ({
    key: el.dataset.lineKey,
    inventoryQuantity: el.dataset.inventoryQuantity,
    inventoryPolicy: el.dataset.inventoryPolicy,
  }));
  const upsellTier1 = upsellSlot ? upsellSlot.dataset.tier1Handles : null;
  const upsellTier3 = document.querySelector('[data-cart-upsell-cards]')?.dataset.tier3Handle;
  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,
    '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 on mobile (< 1024 px); false on desktop (by design).
  • inventoryAttrs count > 0 for any line-item with inventory_tracked + policy=deny.
  • mutationTokens count + debounceTimers count — non-zero means a mutation cycle is in flight; should drop to 0 once the network settles.
  • checkout-retry-counter increments on each Check-out failure (max 3); resets to null after successful redirect.
  • 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 -sI "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[].

Get cart with bundled section refresh

bash
curl -s 'https://{store}/cart.js?sections=cart,cart-drawer&sections_url=/cart' | jq .

Expected: JSON with sections key containing rendered HTML for cart + cart-drawer (the bundled-section-refresh pattern used by the optimistic-UI flow).

Apply discount via direct redirect (verifies Shopify accepts the code)

bash
curl -sILw '%{http_code} %{redirect_url}\n' 'https://{store}/discount/WELCOME20'

Expected: HTTP 302 + Location header redirecting to /cart.

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.

Apply discount via /cart/update.js (alternative to redirect)

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

Expected: JSON with cart_level_discount_applications[] populated if code is valid.

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 API)

bash
curl -s 'https://{store-myshopify-domain}/admin/api/2024-10/metafield_definitions.json?owner_resource=product&namespace=uisce_cart' \
  -H "X-Shopify-Access-Token: $UISCE_TOKEN" | jq .

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

Empirical inventory check

bash
curl -s 'https://{store}/products/{handle}.json' | jq '.product.variants[] | {id, sku, inventory_quantity, inventory_management, inventory_policy}'

Expected: per-variant inventory state. Use this to confirm inventory_policy=deny + inventory_quantity matches what the cart cap is showing.

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.