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.

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

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 = '
';
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(/&/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;
}
});
});
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.

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.