How to Make a Shopify Custom Cart Drawer With Ajax API

How to Make a Shopify Custom Cart Drawer With Ajax API

Introduction

Creating a custom cart drawer with the Shopify Ajax API involves several steps to handle adding items, updating the cart, and displaying the drawer content without requiring a full page reload. In this guide, we’ll guide you how to make a Shopify custom cart drawer with ajax api.


What is the Shopify Ajax API?

The Shopify Ajax API is a set of lightweight REST API endpoints for themes that enable dynamic storefront features, such as adding items to the cart, updating cart contents, and fetching product data without a full page reload. It provides a more seamless user experience by allowing developers to perform actions like modifying the cart, fetching recommendations, and suggesting search results directly from the front end.


Why Use Ajax Cart Drawer

An Ajax Cart Drawer is used to improve the online shopping experience by allowing customers to add items to their cart, view, and edit it without leaving the current page. This leads to higher conversion rates and more sales because it provides a faster, smoother, and more convenient shopping process, while also improving mobile usability. Key benefits of using an Ajax cart drawer are as follows:

  • Customers can add products to their cart and manage it from any page without needing a full page reload.
  • A quicker, less disruptive shopping process can lead to higher conversion rates and fewer abandoned carts.
  • Keeping customers on the same page can reduce bounce rates.
  • Customers can make quick edits, like changing quantities or removing items, directly within the drawer.
  • It provides a user-friendly way to access and manage the cart on smaller screens, eliminating the need to navigate to a separate page.
  • Customers can see changes, such as quantity updates or item removals, reflected instantly in their cart.


How to Make a Custom Cart Drawer Step-by-step

To create a custom cart drawer, utilize your theme's built-in customization options for basic changes, such as colors and layout, or use a Shopify app for advanced features like upsells and progress bars. For a complete code-based customization, you'll need to edit your theme's code to add a new snippet and integrate CSS and JavaScript, though this requires more technical skill. Follow these steps to create a custom Cart Drawer.

Step 1: Navigate to Online Store > Themes > Edit Code.

How to Make a Custom Cart Drawer With Ajax API

Step 2: Navigate to the Sections Folder and create a new section named custom-cart-drawer.liquid.

How to Make a Custom Cart Drawer With Ajax API

Step 3: Paste the following basic HTML code in your cart drawer file.

  
    <div id="ajax-cart-drawer" class="ajax-cart-drawer" aria-hidden="true" aria-labelledby="ajax-cart-title" role="dialog">
      <div class="ajax-cart-backdrop" data-action="close"></div>
      <aside class="ajax-cart-panel" role="document">
        <header class="ajax-cart-header">
          <h2 id="ajax-cart-title">Your cart</h2>
          <button class="ajax-cart-close" aria-label="Close cart" data-action="close">✕</button>
        </header>

        <main class="ajax-cart-body" id="ajax-cart-body">
          <div class="ajax-cart-loading" aria-hidden="true">Loading…</div>

          <div class="ajax-cart-empty" hidden>
            <p>Your cart is empty.</p>
            <a class="button" href="/">Continue shopping</a>
          </div>

          <ul class="ajax-cart-items list-unstyled" id="ajax-cart-items" aria-live="polite"></ul>
        </main>

        <footer class="ajax-cart-footer">
          <div class="ajax-cart-summary">
            <div class="ajax-cart-subtotal">
              <span>Subtotal</span>
              <span id="ajax-cart-subtotal" class="money">—</span>
            </div>
            <div class="ajax-cart-shipping-note" id="ajax-cart-shipping-note" hidden></div>
          </div>
          <div class="ajax-cart-actions">
            <a href="/cart" class="button button--secondary">View cart</a>
            <a href="/checkout" class="button button--primary" id="ajax-cart-checkout">Checkout</a>
          </div>
        </footer>
      </aside>
    </div>

    <button id="open-cart-drawer" class="cart-toggle" aria-haspopup="dialog" aria-controls="ajax-cart-drawer">
      Cart (<span id="ajax-cart-count">0</span>)
    </button>
  

Step 4: Paste the following basic CSS in your file.

  
    <style>
      .ajax-cart-drawer { 
          position: fixed;
          inset: 0;
          z-index: 1200;
          display: none;
      }
      .ajax-cart-drawer.open { 
          display:block; 
      }
      .ajax-cart-backdrop { 
          position:absolute; 
          inset:0; 
          background:rgba(0,0,0,0.5); 
      }
      .ajax-cart-panel { 
          position: absolute; 
          right:0; 
          top:0; 
          height:100%; 
          width:380px; 
          max-width:100%; 
          background:#fff; 
          box-shadow:-8px 0 30px rgba(0,0,0,0.2); 
          display:flex; 
          flex-direction:column; 
          transform: translateX(100%); 
          transition: transform .28s ease; 
      }
      .ajax-cart-drawer.open .ajax-cart-panel { 
          transform: translateX(0); 
      }
      .ajax-cart-header { 
          padding:16px; 
          display:flex; 
          justify-content:space-between; 
          align-items:center; 
          border-bottom:1px solid #eee; 
      }
      .ajax-cart-body { 
          padding:16px; 
          overflow:auto; 
          flex:1; 
      }
      .ajax-cart-items { 
          margin:0; 
          padding:0; 
          list-style:none; 
          display:flex; 
          flex-direction:column; 
          gap:12px; 
      }
      .ajax-cart-item { 
          display:flex; 
          gap:12px; 
          align-items:center; 
      }
      .ajax-cart-item img { 
          width:64px; 
          height:64px; 
          object-fit:cover; 
          border:1px solid #eee; 
      }
      .ajax-cart-item .meta { 
          flex:1; 
      }
      .ajax-cart-item .title { 
          font-size:14px; 
          margin:0 0 6px; 
      }
      .ajax-cart-item .price { 
          font-weight:600; 
          font-size:14px; 
      }
      .ajax-cart-qty { 
          display:flex; 
          gap:6px; 
          align-items:center; 
      }
      .qty-btn { 
          width:28px; 
          height:28px; 
          display:inline-flex; 
          align-items:center; 
          justify-content:center; 
          border:1px solid #ccc; 
          background:#fafafa; 
          cursor:pointer; 
      }
      .qty-input { 
          width:42px; 
          text-align:center; 
          border:1px solid #ddd; 
          padding:6px; 
      }
      .ajax-cart-footer { 
          padding:16px; 
          border-top:1px solid #eee; 
      }
      .button { 
          display:inline-block; 
          padding:10px 14px; 
          text-decoration:none; 
          background:#333; 
          color:#fff; 
          border-radius:3px; 
      }
      .button--secondary { 
          background:#fff; 
          color:#333; 
          border:1px solid #ddd; 
      }
      .money { 
          font-weight:700; 
      }
      .ajax-cart-loading { 
          text-align:center; 
          padding:20px 0; 
      }
      @media (max-width:480px){ 
          .ajax-cart-panel { 
              width:100%; 
          } 
      }
    </style>
  

Step 5: Locate your Add to Cart button, usually in the main-product.liquid file. Inside your snippet, locate the product form.

  
    <product-form
      class="product-form"
      data-hide-errors="{{ gift_card_recipient_feature_active }}"
      data-section-id="{{ section.id }}"
    >
  

Step 6: Add a unique identifier ID or Class to the product form so JS can target it.

  
    <product-form
      class="product-form"
      id="main-product-form"
      data-hide-errors="{{ gift_card_recipient_feature_active }}"
      data-section-id="{{ section.id }}"
    >
  

Step 7: Save the changes.

Step 8: Add the following JS code to your file.

  
    <script>
      (function () {
          var drawer = document.getElementById('ajax-cart-drawer');
          var openBtn = document.getElementById('open-cart-drawer');
          var closeEls = drawer.querySelectorAll('[data-action="close"]');
          var itemsContainer = document.getElementById('ajax-cart-items');
          var subtotalEl = document.getElementById('ajax-cart-subtotal');
          var countEl = document.getElementById('ajax-cart-count');
          var loadingEl = drawer.querySelector('.ajax-cart-loading');
          var emptyEl = drawer.querySelector('.ajax-cart-empty');
          var shippingNoteEl = document.getElementById('ajax-cart-shipping-note');

          function formatMoney(cents) {
              try {
                  var amount = (cents/100);
                  var currency = "{{ shop.currency }}";
                  return new Intl.NumberFormat(undefined, { style: 'currency', currency: currency, maximumFractionDigits: 2 }).format(amount);
              } catch (e) {
                  return (cents/100).toFixed(2);
              }
          }

          function openDrawer() {
              drawer.classList.add('open');
              drawer.setAttribute('aria-hidden', 'false');
              fetchCartAndRender();
              document.body.style.overflow = 'hidden';
              setTimeout(function(){ drawer.querySelector('.ajax-cart-close')?.focus(); }, 200);
          }
          window.openDrawer = openDrawer;

          function closeDrawer() {
              drawer.classList.remove('open');
              drawer.setAttribute('aria-hidden', 'true');
              document.body.style.overflow = '';
              openBtn?.focus();
          }

          openBtn?.addEventListener('click', function (e) { e.preventDefault(); openDrawer(); });
          closeEls.forEach(function(el){ el.addEventListener('click', function(e){ e.preventDefault(); closeDrawer(); }); });
          drawer.addEventListener('keydown', function(e){ if (e.key === 'Escape') closeDrawer(); });
          drawer.querySelector('.ajax-cart-backdrop').addEventListener('click', closeDrawer);

          function fetchCartAndRender() {
              showLoading(true);
              fetch('/cart.js', { credentials: 'same-origin' })
              .then(function(res){ if (!res.ok) throw res; return res.json(); })
              .then(function(cart){ renderCart(cart); })
              .catch(function(err){ console.error('Cart fetch error', err); })
              .finally(function(){ showLoading(false); });
          }
          window.fetchCartAndRender = fetchCartAndRender;

          function showLoading(state){
              loadingEl.hidden = !state;
              itemsContainer.hidden = state;
              emptyEl.hidden = true;
          }

          function renderCart(cart) {
              countEl.textContent = cart.item_count || 0;
              subtotalEl.textContent = formatMoney(cart.items_subtotal_price || cart.total_price || 0);
              var freeThreshold = 5000;
              if ((cart.items_subtotal_price || 0) >= freeThreshold) {
                  shippingNoteEl.hidden = false;
                  shippingNoteEl.textContent = 'You qualify for free shipping!';
              } else {
                  var remaining = freeThreshold - (cart.items_subtotal_price || 0);
                  shippingNoteEl.hidden = false;
                  shippingNoteEl.textContent = 'Add ' + formatMoney(remaining) + ' for free shipping.';
              }

              itemsContainer.innerHTML = '';
              if (!cart.items || cart.items.length === 0) {
                  emptyEl.hidden = false;
                  subtotalEl.textContent = formatMoney(0);
                  return;
              }

              cart.items.forEach(function(item, index){
                  var li = document.createElement('li');
                  li.className = 'ajax-cart-item';
                  li.dataset.key = item.key;
                  li.dataset.index = index+1;

                  var img = ''+ escapeHtml(item.title) +'';
                 

  var title = ' '+ escapeHtml(item.product_title) + (item.variant_title ? ' — ' + escapeHtml(item.variant_title) : '') +'




';
                  var price = '
'+ formatMoney(item.final_line_price) +'
';
                  var qtyControls = '
' + '−' + '' + '+' + '
';
                  var remove = 'Remove';



                  li.innerHTML = '
'+ img +'




'+ title + price + qtyControls + '




'+ remove +'
';
                  itemsContainer.appendChild(li);
              });



              bindItemEvents();
          }



          function escapeHtml(str){
              if (!str) return '';
              return String(str)
              .replace(/&amp;/g, '&amp;')
              .replace(//g, '&gt;')
              .replace(/"/g, '"')
              .replace(/'/g, ''');
          }



          function bindItemEvents(){
              itemsContainer.querySelectorAll('.qty-increase').forEach(function(btn){
                  btn.addEventListener('click', function(){
                      var input = this.parentElement.querySelector('.qty-input');
                      input.value = parseInt(input.value,10) + 1;
                      onQuantityInputChange(input);
                  });
              });



              itemsContainer.querySelectorAll('.qty-decrease').forEach(function(btn){
                  btn.addEventListener('click', function(){
                      var input = this.parentElement.querySelector('.qty-input');
                      var next = Math.max(0, parseInt(input.value,10) - 1);
                      input.value = next;
                      onQuantityInputChange(input);
                  });
              });



              itemsContainer.querySelectorAll('.qty-input').forEach(function(input){
                  var timer = null;
                  input.addEventListener('input', function(){
                      clearTimeout(timer);
                      var self = this;
                      timer = setTimeout(function(){ onQuantityInputChange(self); }, 300);
                  });
              });



              itemsContainer.querySelectorAll('.remove-item').forEach(function(btn){
                  btn.addEventListener('click', function(e){
                      e.preventDefault();
                      var key = this.dataset.key;
                      updateItemQuantityByKey(key, 0);
                  });
              });
          }



          function onQuantityInputChange(input) {
              var li = input.closest('.ajax-cart-item');
              var key = li.dataset.key;
              var qty = parseInt(input.value,10);
              if (isNaN(qty) || qty &lt; 0) qty = 0;
              updateItemQuantityByKey(key, qty);
          }



          function updateItemQuantityByKey(lineKey, quantity) {
              disableControls(true);
              var payload = { id: lineKey, quantity: quantity };



              fetch('/cart/change.js', {
                  method: 'POST',
                  credentials: 'same-origin',
                  headers: { 'Content-Type': 'application/json', 'Accept':'application/json' },
                  body: JSON.stringify(payload)
              })
              .then(function(res){ if (!res.ok) throw res; return res.json(); })
              .then(function(cart){ renderCart(cart); })
              .catch(function(err){ console.error('change.js failed', err); })
              .finally(function(){
                  disableControls(false);
                  updateHeaderCart();
              });
          }



          function disableControls(state){
              itemsContainer.querySelectorAll('input, button').forEach(function(el){
                  el.disabled = !!state;
              });
          }



          function updateHeaderCart(){
              fetch('/cart.js', { credentials: 'same-origin' })
              .then(function(r){ if (!r.ok) throw r; return r.json(); })
              .then(function(cart){ document.getElementById('ajax-cart-count').textContent = cart.item_count || 0; })
              .catch(()=&gt;{});
          }



          window.addToCart = function(variantId, qty){
              qty = qty || 1;
              fetch('/cart/add.js', {
                  method:'POST',
                  credentials:'same-origin',
                  headers: { 'Content-Type': 'application/json', 'Accept':'application/json' },
                  body: JSON.stringify({ id: variantId, quantity: qty })
              })
              .then(function(res){ if(!res.ok) throw res; return res.json(); })
              .then(function(){ openDrawer(); })
              .catch(function(err){ console.error('Add error', err); });
          };



          fetch('/cart.js', { credentials: 'same-origin' })
              .then(function(r){ if (!r.ok) throw r; return r.json(); })
              .then(function(cart){ countEl.textContent = cart.item_count || 0; }).catch(()=&gt;{});
      })();
      document.addEventListener("DOMContentLoaded", function() {
          const productForm = document.querySelector('#main-product-form form[action="/cart/add"]');
          if (!productForm) return;



          productForm.addEventListener("submit", async function(e) {
              e.preventDefault();



              const btn = productForm.querySelector('.product-form__submit');
              btn.classList.add('loading');
              btn.disabled = true;



              const formData = new FormData(productForm);



              try {
                  const res = await fetch('/cart/add.js', {
                      method: 'POST',
                      headers: { 'Accept': 'application/json' },
                      body: formData
                  });



                  if (!res.ok) throw res;



                  if (window.openDrawer) window.openDrawer();



              } catch (err) {
                  console.error(err);
              } finally {
                  btn.classList.remove('loading');
                  btn.disabled = false;
              }
          });
      });
     



  

Step 9: Your custom cart drawer full code appears as follows.

    
        <div id="ajax-cart-drawer" class="ajax-cart-drawer" aria-hidden="true" aria-labelledby="ajax-cart-title" role="dialog">
        <div class="ajax-cart-backdrop" data-action="close"></div>
        <aside class="ajax-cart-panel" role="document">
            <header class="ajax-cart-header">
            <h2 id="ajax-cart-title">Your cart</h2>
            <button class="ajax-cart-close" aria-label="Close cart" data-action="close">✕</button>
            </header>

            <main class="ajax-cart-body" id="ajax-cart-body">
            <div class="ajax-cart-loading" aria-hidden="true">Loading…</div>

            <div class="ajax-cart-empty" hidden>
                <p>Your cart is empty.</p>
                <a class="button" href="/">Continue shopping</a>
            </div>

            <ul class="ajax-cart-items list-unstyled" id="ajax-cart-items" aria-live="polite"></ul>
            </main>

            <footer class="ajax-cart-footer">
            <div class="ajax-cart-summary">
                <div class="ajax-cart-subtotal">
                <span>Subtotal</span>
                <span id="ajax-cart-subtotal" class="money">—</span>
                </div>
                <div class="ajax-cart-shipping-note" id="ajax-cart-shipping-note" hidden></div>
            </div>
            <div class="ajax-cart-actions">
                <a href="/cart" class="button button--secondary">View cart</a>
                <a href="/checkout" class="button button--primary" id="ajax-cart-checkout">Checkout</a>
            </div>
            </footer>
        </aside>
        </div>

        <button id="open-cart-drawer" class="cart-toggle" aria-haspopup="dialog" aria-controls="ajax-cart-drawer">
        Cart (<span id="ajax-cart-count">0</span>)
        </button>

        <style>
            .ajax-cart-drawer { 
                position: fixed;
                inset: 0;
                z-index: 1200;
                display: none;
            }
            .ajax-cart-drawer.open { 
                display:block; 
            }
            .ajax-cart-backdrop { 
                position:absolute; 
                inset:0; 
                background:rgba(0,0,0,0.5); 
            }
            .ajax-cart-panel { 
                position: absolute; 
                right:0; 
                top:0; 
                height:100%; 
                width:380px; 
                max-width:100%; 
                background:#fff; 
                box-shadow:-8px 0 30px rgba(0,0,0,0.2); 
                display:flex; 
                flex-direction:column; 
                transform: translateX(100%); 
                transition: transform .28s ease; 
            }
            .ajax-cart-drawer.open .ajax-cart-panel { 
                transform: translateX(0); 
            }
            .ajax-cart-header { 
                padding:16px; 
                display:flex; 
                justify-content:space-between; 
                align-items:center; 
                border-bottom:1px solid #eee; 
            }
            .ajax-cart-body { 
                padding:16px; 
                overflow:auto; 
                flex:1; 
            }
            .ajax-cart-items { 
                margin:0; 
                padding:0; 
                list-style:none; 
                display:flex; 
                flex-direction:column; 
                gap:12px; 
            }
            .ajax-cart-item { 
                display:flex; 
                gap:12px; 
                align-items:center; 
            }
            .ajax-cart-item img { 
                width:64px; 
                height:64px; 
                object-fit:cover; 
                border:1px solid #eee; 
            }
            .ajax-cart-item .meta { 
                flex:1; 
            }
            .ajax-cart-item .title { 
                font-size:14px; 
                margin:0 0 6px; 
            }
            .ajax-cart-item .price { 
                font-weight:600; 
                font-size:14px; 
            }
            .ajax-cart-qty { 
                display:flex; 
                gap:6px; 
                align-items:center; 
            }
            .qty-btn { 
                width:28px; 
                height:28px; 
                display:inline-flex; 
                align-items:center; 
                justify-content:center; 
                border:1px solid #ccc; 
                background:#fafafa; 
                cursor:pointer; 
            }
            .qty-input { 
                width:42px; 
                text-align:center; 
                border:1px solid #ddd; 
                padding:6px; 
            }
            .ajax-cart-footer { 
                padding:16px; 
                border-top:1px solid #eee; 
            }
            .button { 
                display:inline-block; 
                padding:10px 14px; 
                text-decoration:none; 
                background:#333; 
                color:#fff; 
                border-radius:3px; 
            }
            .button--secondary { 
                background:#fff; 
                color:#333; 
                border:1px solid #ddd; 
            }
            .money { 
                font-weight:700; 
            }
            .ajax-cart-loading { 
                text-align:center; 
                padding:20px 0; 
            }
            @media (max-width:480px){ 
                .ajax-cart-panel { 
                    width:100%; 
                } 
            }
        </style>

        <script>
            (function () {
                var drawer = document.getElementById('ajax-cart-drawer');
                var openBtn = document.getElementById('open-cart-drawer');
                var closeEls = drawer.querySelectorAll('[data-action="close"]');
                var itemsContainer = document.getElementById('ajax-cart-items');
                var subtotalEl = document.getElementById('ajax-cart-subtotal');
                var countEl = document.getElementById('ajax-cart-count');
                var loadingEl = drawer.querySelector('.ajax-cart-loading');
                var emptyEl = drawer.querySelector('.ajax-cart-empty');
                var shippingNoteEl = document.getElementById('ajax-cart-shipping-note');

                function formatMoney(cents) {
                    try {
                        var amount = (cents/100);
                        var currency = "{{ shop.currency }}";
                        return new Intl.NumberFormat(undefined, { style: 'currency', currency: currency, maximumFractionDigits: 2 }).format(amount);
                    } catch (e) {
                        return (cents/100).toFixed(2);
                    }
                }

                function openDrawer() {
                    drawer.classList.add('open');
                    drawer.setAttribute('aria-hidden', 'false');
                    fetchCartAndRender();
                    document.body.style.overflow = 'hidden';
                    setTimeout(function(){ drawer.querySelector('.ajax-cart-close')?.focus(); }, 200);
                }
                window.openDrawer = openDrawer;

                function closeDrawer() {
                    drawer.classList.remove('open');
                    drawer.setAttribute('aria-hidden', 'true');
                    document.body.style.overflow = '';
                    openBtn?.focus();
                }

                openBtn?.addEventListener('click', function (e) { e.preventDefault(); openDrawer(); });
                closeEls.forEach(function(el){ el.addEventListener('click', function(e){ e.preventDefault(); closeDrawer(); }); });
                drawer.addEventListener('keydown', function(e){ if (e.key === 'Escape') closeDrawer(); });
                drawer.querySelector('.ajax-cart-backdrop').addEventListener('click', closeDrawer);

                function fetchCartAndRender() {
                    showLoading(true);
                    fetch('/cart.js', { credentials: 'same-origin' })
                    .then(function(res){ if (!res.ok) throw res; return res.json(); })
                    .then(function(cart){ renderCart(cart); })
                    .catch(function(err){ console.error('Cart fetch error', err); })
                    .finally(function(){ showLoading(false); });
                }
                window.fetchCartAndRender = fetchCartAndRender;

                function showLoading(state){
                    loadingEl.hidden = !state;
                    itemsContainer.hidden = state;
                    emptyEl.hidden = true;
                }

                function renderCart(cart) {
                    countEl.textContent = cart.item_count || 0;
                    subtotalEl.textContent = formatMoney(cart.items_subtotal_price || cart.total_price || 0);
                    var freeThreshold = 5000;
                    if ((cart.items_subtotal_price || 0) >= freeThreshold) {
                        shippingNoteEl.hidden = false;
                        shippingNoteEl.textContent = 'You qualify for free shipping!';
                    } else {
                        var remaining = freeThreshold - (cart.items_subtotal_price || 0);
                        shippingNoteEl.hidden = false;
                        shippingNoteEl.textContent = 'Add ' + formatMoney(remaining) + ' for free shipping.';
                    }

                    itemsContainer.innerHTML = '';
                    if (!cart.items || cart.items.length === 0) {
                        emptyEl.hidden = false;
                        subtotalEl.textContent = formatMoney(0);
                        return;
                    }

                    cart.items.forEach(function(item, index){
                        var li = document.createElement('li');
                        li.className = 'ajax-cart-item';
                        li.dataset.key = item.key;
                        li.dataset.index = index+1;

                        var img = '<img src="'+ (item.image || '') +'" alt="'+ escapeHtml(item.title) +'">';
                        var title = '<p class="title">'+ escapeHtml(item.product_title) + (item.variant_title ? ' — ' + escapeHtml(item.variant_title) : '') +'</p>';
                        var price = '<div class="price">'+ formatMoney(item.final_line_price) +'</div>';
                        var qtyControls = '<div class="ajax-cart-qty">'
                            + '<button class="qty-btn qty-decrease">−</button>'
                            + '<input class="qty-input" type="number" min="0" value="'+ item.quantity +'">'
                            + '<button class="qty-btn qty-increase">+</button>'
                            + '</div>';
                        var remove = '<button class="button button--secondary remove-item" data-key="'+ item.key +'">Remove</button>';

                        li.innerHTML = '<div class="thumb">'+ img +'</div><div class="meta">'+ title + price + qtyControls + '</div><div class="actions">'+ remove +'</div>';
                        itemsContainer.appendChild(li);
                    });

                    bindItemEvents();
                }

                function escapeHtml(str){
                    if (!str) return '';
                    return String(str)
                    .replace(/&/g, '&')
                    .replace(/</g, '<')
                    .replace(/>/g, '>')
                    .replace(/"/g, '"')
                    .replace(/'/g, ''');
                }

                function bindItemEvents(){
                    itemsContainer.querySelectorAll('.qty-increase').forEach(function(btn){
                        btn.addEventListener('click', function(){
                            var input = this.parentElement.querySelector('.qty-input');
                            input.value = parseInt(input.value,10) + 1;
                            onQuantityInputChange(input);
                        });
                    });

                    itemsContainer.querySelectorAll('.qty-decrease').forEach(function(btn){
                        btn.addEventListener('click', function(){
                            var input = this.parentElement.querySelector('.qty-input');
                            var next = Math.max(0, parseInt(input.value,10) - 1);
                            input.value = next;
                            onQuantityInputChange(input);
                        });
                    });

                    itemsContainer.querySelectorAll('.qty-input').forEach(function(input){
                        var timer = null;
                        input.addEventListener('input', function(){
                            clearTimeout(timer);
                            var self = this;
                            timer = setTimeout(function(){ onQuantityInputChange(self); }, 300);
                        });
                    });

                    itemsContainer.querySelectorAll('.remove-item').forEach(function(btn){
                        btn.addEventListener('click', function(e){
                            e.preventDefault();
                            var key = this.dataset.key;
                            updateItemQuantityByKey(key, 0);
                        });
                    });
                }

                function onQuantityInputChange(input) {
                    var li = input.closest('.ajax-cart-item');
                    var key = li.dataset.key;
                    var qty = parseInt(input.value,10);
                    if (isNaN(qty) || qty < 0) qty = 0;
                    updateItemQuantityByKey(key, qty);
                }

                function updateItemQuantityByKey(lineKey, quantity) {
                    disableControls(true);
                    var payload = { id: lineKey, quantity: quantity };

                    fetch('/cart/change.js', {
                        method: 'POST',
                        credentials: 'same-origin',
                        headers: { 'Content-Type': 'application/json', 'Accept':'application/json' },
                        body: JSON.stringify(payload)
                    })
                    .then(function(res){ if (!res.ok) throw res; return res.json(); })
                    .then(function(cart){ renderCart(cart); })
                    .catch(function(err){ console.error('change.js failed', err); })
                    .finally(function(){
                        disableControls(false);
                        updateHeaderCart();
                    });
                }

                function disableControls(state){
                    itemsContainer.querySelectorAll('input, button').forEach(function(el){
                        el.disabled = !!state;
                    });
                }

                function updateHeaderCart(){
                    fetch('/cart.js', { credentials: 'same-origin' })
                    .then(function(r){ if (!r.ok) throw r; return r.json(); })
                    .then(function(cart){ document.getElementById('ajax-cart-count').textContent = cart.item_count || 0; })
                    .catch(()=>{});
                }

                window.addToCart = function(variantId, qty){
                    qty = qty || 1;
                    fetch('/cart/add.js', {
                        method:'POST',
                        credentials:'same-origin',
                        headers: { 'Content-Type': 'application/json', 'Accept':'application/json' },
                        body: JSON.stringify({ id: variantId, quantity: qty })
                    })
                    .then(function(res){ if(!res.ok) throw res; return res.json(); })
                    .then(function(){ openDrawer(); })
                    .catch(function(err){ console.error('Add error', err); });
                };

                fetch('/cart.js', { credentials: 'same-origin' })
                    .then(function(r){ if (!r.ok) throw r; return r.json(); })
                    .then(function(cart){ countEl.textContent = cart.item_count || 0; }).catch(()=>{});
            })();
            document.addEventListener("DOMContentLoaded", function() {
                const productForm = document.querySelector('#main-product-form form[action="/cart/add"]');
                if (!productForm) return;

                productForm.addEventListener("submit", async function(e) {
                    e.preventDefault();

                    const btn = productForm.querySelector('.product-form__submit');
                    btn.classList.add('loading');
                    btn.disabled = true;

                    const formData = new FormData(productForm);

                    try {
                        const res = await fetch('/cart/add.js', {
                            method: 'POST',
                            headers: { 'Accept': 'application/json' },
                            body: formData
                        });

                        if (!res.ok) throw res;

                        if (window.openDrawer) window.openDrawer();

                    } catch (err) {
                        console.error(err);
                    } finally {
                        btn.classList.remove('loading');
                        btn.disabled = false;
                    }
                });
            });
        </script>
    

Step 10: Open theme.liquid file.

How to Make a Custom Cart Drawer With Ajax API

Step 11: Locate the start body tag <body> and add the code at your desired location.

    
        {% section 'custom-cart-drawer' %}
    

Step 12: Save the changes.

Conclusion 

Building a custom cart drawer with the Shopify Ajax API is an effective way to create a faster, smoother, and more engaging shopping experience. By loading cart data dynamically and allowing customers to update their cart without page reloads, you improve usability, reduce friction, and support higher conversion rates. With the step-by-step approach shared in this guide, you can fully control the design, behavior, and performance of your Ajax cart drawer while keeping your Shopify theme lightweight and scalable. Once implemented, you can further enhance it with features like upsells, free shipping messages, or animations to better match your brand and boost overall store performance.



 

Frequently Asked Questions

A Shopify Ajax cart drawer is usually better for conversions because customers can add and manage items without leaving the page. It creates a faster, smoother shopping experience compared to redirecting users to a separate cart page.

No, you don’t need an app. Shopify’s Ajax API allows you to build a fully custom cart drawer using HTML, CSS, and JavaScript, giving you more control and better performance than many third-party apps.

Yes, a custom Ajax cart drawer can work on most Shopify themes, including Online Store 2.0 themes. You may need minor adjustments depending on how the theme handles product forms and cart behavior.

No, an Ajax cart drawer only changes how the cart is displayed and updated. Shopify checkout and payment processing remain unchanged and fully secure, since checkout still happens through Shopify’s native checkout flow.

Yes, the Shopify Ajax API is designed specifically for live storefronts. When implemented correctly, it safely handles cart updates, quantity changes, and item removals without impacting store stability or customer data.

Comments(0)
You don't have any comments yet

Leave a comment
Are You Looking for a Shopify Expert?

We design, develop, optimize, and scale high-converting Shopify stores.

Get in touch
Let's Connect
Shopify Plus eCommerce Development Agency ecomx agency

Looking for a Trusted Ecommerce & Shopify Plus Agency?

We design, develop, optimize, and scale high converting Shopify Ecommerce stores that drive sales.

Get in Touch
Ecomx shopify plus agency about us page Yasin

Looking for a Shopify expert to handle a small task? Shopify Migration Experts Agency Ecomx shopify plus agency

Let’s talk about your project

Shopify Custom Theme Development Services Live Chat