jelonyx
§ / Shopify · Error fixes · Fix 02

Shopify cart drawer
not updating.

The customer adds an item, changes quantity, or removes a line, and the cart drawer keeps showing the old state. The cart on the server has updated; the HTML has not. On Dawn 12+ and Horizon this is almost always an app calling /cart/add.js without using the Section Rendering API. Five ranked causes below, a console diagnostic, and a refresher snippet that fixes it in one file.

SeverityConversion-blocking
Time to fix~15–45 minutes
Theme accessRequired
Most common causeApp skipping section render
Affects:Dawn 12+HorizonSenseCraftMost OS 2.0 themes

Symptoms

  • Add to cart succeeds but the drawer count and line items show the previous state.
  • Quantity stepper in the drawer increments visually but the subtotal doesn't recalculate.
  • Remove line button deletes the item server-side (visible after refresh) but the drawer keeps rendering it.
  • Cart bubble in the header desyncs from the drawer: one updates, the other doesn't.
  • Drawer flashes empty for a frame, then re-renders the old items.

30-second diagnostic

Run this in the console, then add to cart, change quantity, or remove a line. The output tells you which cause below applies.

devtools console
JavaScript
// Jelonyx · Shopify cart drawer diagnostic
// Paste into devtools console. Then add a product to cart from the page.

(() => {
  const drawer = document.querySelector('#cart-drawer, cart-drawer, [id*="CartDrawer"]');
  const bubble = document.querySelector('#cart-icon-bubble, [id*="cart-count"], [data-cart-count]');

  console.group('%c[jlx] cart drawer check', 'color:#B8D67A;font-weight:600');
  console.log('cart drawer element:', drawer ? '✅ found · id=' + drawer.id : '❌ missing');
  console.log('cart bubble element:', bubble ? '✅ found · id=' + bubble.id : '❌ missing');
  console.groupEnd();

  // Watch for cart events the theme might be dispatching
  ['cart:updated','cart:refresh','on:cart:update','cart:change','cart-update'].forEach((evt) => {
    document.addEventListener(evt, (e) => {
      console.log('[jlx] event fired →', evt, e.detail || '');
    });
  });

  // Sniff /cart/add.* and /cart/change.* requests in flight
  const _fetch = window.fetch;
  window.fetch = function (url, opts) {
    if (typeof url === 'string' && /\/cart\/(add|change|update|clear)/.test(url)) {
      const hasSections = url.includes('sections=') ||
        (opts && opts.body && String(opts.body).includes('sections'));
      console.log(
        '[jlx] cart request →', url,
        hasSections ? '· ✅ sections requested' : '· ❌ no sections param'
      );
    }
    return _fetch.apply(this, arguments);
  };
  console.log('[jlx] fetch hooked. add to cart now.');
})();
  • Cart drawer element missing → the theme has no cart drawer section, or its ID has been renamed (cause #4).
  • Request fires but no sections= param → an app or theme code is using the legacy add-to-cart endpoint (cause #1).
  • Sections requested but no event fires → the theme expects a different cart event name (cause #3).
  • Request 304 Not Modified → the cart response is being cached by a CDN or service worker (cause #5).
  • Sections requested, event fires, drawer still stale → the theme is rendering into the wrong selector (cause #2).

Most likely causes, ranked

  1. 01
    An app calls /cart/add.js without re-rendering the drawer.

    Older sticky ATC, bundle builder, and upsell apps fire the legacy AJAX endpoint and update only the cart bubble. They never request the cart-drawer section, so the drawer HTML stays frozen on whatever Liquid rendered at page load. Frequency: most common.

    Fix: in Shopify Admin → Apps, disable apps one at a time. The drawer starts updating when the conflicting app is off. Either contact the app developer for an OS 2.0 fix, replace the app, or install the refresher snippet below. It intercepts every /cart/add / /cart/change / /cart/update request and forces a drawer re-render after each one.

  2. 02
    The theme renders into the wrong selector.

    After a theme update or a custom build, the cart drawer's outer element ID may not match what the theme JS targets, e.g. the theme fetches cart-drawer but the rendered element is CartDrawer or wrapped in a custom block. The fetch succeeds, the response is correct, but document.querySelector('#cart-drawer') returns null.

    Fix: in the theme code editor, open sections/cart-drawer.liquid (or equivalent) and confirm the outer wrapper has id="cart-drawer". Then in the theme JS, search for getSectionsToRender or 'cart-drawer' and confirm the same key is used both for the section ID and the DOM lookup.

  3. 03
    Theme is listening for a different cart event name.

    Older Dawn versions dispatch cart:updated. Horizon listens for on:cart:update. Some custom themes invent cart:refresh. When an app dispatches one but the drawer listens for another, the cart updates server-side and the drawer never knows.

    Fix: in assets/global.js or the cart drawer's JS, search for addEventListener('cart: to see which event the drawer listens for. Either change the app to dispatch that event, or use the refresher snippet below. It dispatches cart:updated after every change so theme code that listens for it will react.

  4. 04
    Section ID was renamed or removed.

    After a theme migration, cart-drawer may have been renamed to cart, drawer-cart, or merged into a generic cart-template. Section Rendering API requests for the old name return a 404 or empty body, so the drawer never receives new HTML.

    Fix: in the theme's sections/ folder, confirm the actual filename. Update every place in theme JS where the old section name is referenced. If you cannot edit the theme JS, set the refresher snippet's SECTIONS array to the new names.

  5. 05
    Cart response cached by CDN or service worker.

    A misconfigured PWA service worker or a CDN edge cache may cache /cart.js or /cart?sections=... responses. The cart updates server-side, but the browser keeps reading the cached version. Symptom: the drawer updates after a hard refresh but not in normal flow.

    Fix: add cache: 'no-store' to all cart fetches (the refresher snippet does this). In Application → Service Workers, unregister and reload to clear a misbehaving worker. If a CDN is in front of the storefront, exclude /cart* from the cache rules.

The refresher (causes #1, #3, #5)

Use this when you cannot remove the conflicting app or rewrite theme JS. It intercepts every cart-mutating fetch, re-renders the cart drawer and cart bubble using the Section Rendering API, and dispatches cart:updated so legacy theme code reacts.

snippets/jlx-cart-refresh.liquid
Liquid · JS
{% comment %}
  Jelonyx · Cart drawer refresher
  Re-renders the cart drawer + cart bubble after every cart change using
  the Shopify Section Rendering API. Use when an installed app updates the
  cart but does not refresh the drawer.

  Drop into snippets/jlx-cart-refresh.liquid and render once on the layout
  (theme.liquid) above </body>.
  Source: jelonyx.com/shopify/fix/cart-drawer-not-updating
{% endcomment %}

<script>
  (function () {
    'use strict';

    var SECTIONS = ['cart-drawer', 'cart-icon-bubble'];
    var inflight = false;

    async function refresh() {
      if (inflight) return;
      inflight = true;
      try {
        var res = await fetch('/cart?sections=' + SECTIONS.join(','), {
          headers: { Accept: 'application/json' },
          cache: 'no-store',
        });
        if (!res.ok) return;
        var data = await res.json();
        SECTIONS.forEach(function (key) {
          var html = data[key];
          if (!html) return;
          // The section response is the full <section> wrapper. Find the
          // first element inside the live page that matches the same id.
          var parsed = new DOMParser().parseFromString(html, 'text/html');
          var fresh = parsed.querySelector('#' + key) || parsed.body.firstElementChild;
          var live = document.querySelector('#' + key);
          if (fresh && live) live.innerHTML = fresh.innerHTML;
        });
        document.dispatchEvent(new CustomEvent('cart:updated'));
      } finally {
        inflight = false;
      }
    }

    // Hook every fetch that mutates the cart
    var _fetch = window.fetch;
    window.fetch = function (url, opts) {
      var p = _fetch.apply(this, arguments);
      if (typeof url === 'string' && /\/cart\/(add|change|update|clear)/.test(url)) {
        p.then(function () { refresh(); });
      }
      return p;
    };

    // Also expose for theme code that doesn't use fetch
    window.jlxRefreshCartDrawer = refresh;
  })();
</script>

Render once on the layout: open layout/theme.liquid and add the render tag directly above the closing </body> tag:

{% render 'jlx-cart-refresh' %}

When to call a developer

  • The drawer is built by an app, not a theme section:the refresher won't help; the app owns the markup. Either replace the app or get the developer to expose a public refresh hook.
  • Cart updates but checkout total is wrong: that's not a drawer issue, it's a discount, gift card, or cart attribute problem. Different fix.
  • Drawer updates only after a delay: usually a CDN or service worker race condition. Needs hands-on debugging.
  • You also have the variant picker bug: see the variant picker fix. Both bugs often share the same culprit app, and fixing one without the other still loses sales.
No-App Shopify Fix Sprint

Need this fixed today?

A broken cart drawer reads as a broken store: customers assume the add-to-cart didn't work and leave. We diagnose, install the right fix, and verify across desktop and mobile in one fixed-scope sprint.