Add a Discount Code Field to Your Shopify Cart Drawer (2026)

Short answer: Shopify's cart drawer has no discount code field by default. Since May 21, 2025 you can add a real one for free — POST the code to /cart/update.js with the discount parameter, then re-render your cart drawer section in the same request so the price updates instantly. Full copy-paste code is below. If you would rather not touch Liquid, Oxify Cart Drawer & Upsells adds the same field, plus auto-apply and free gifts, in about 10 minutes.
Almost every other guide on this topic still tells you to do the old redirect trick — dump the code into /checkout?discount=CODE and hope. That is not applying a discount in the cart. The customer types a code, nothing changes on screen, and they bounce. This guide shows the method that actually updates the price inside the drawer, in real time, before checkout.
Why Shopify's cart drawer has no discount code field
This is architecture, not laziness — and understanding it tells you which method to pick.
Shopify splits the buying flow in two. Your cart drawer is just a front end for the cart (powered by cart.js), which is a dumb container: line items, quantities, raw prices. It historically knew nothing about which codes exist or who qualifies. The checkout is where the smart logic lives — code validation, customer eligibility, stacking rules, taxes, shipping.
Putting a discount input in the cart drawer means forcing the dumb cart to talk to the smart checkout backend before the customer ever reaches checkout. That was genuinely hard until Shopify closed the gap.
On May 21, 2025, Shopify shipped discount support on the /cart/update.js Ajax endpoint. Discount codes are now first-class cart data. It works on every Shopify plan, not just Plus, and it supports multiple codes at once. Most articles ranking for this keyword were written before that change and still teach the redirect hack.
The data behind why this matters
| Metric | Value | Source |
|---|---|---|
| Average documented cart abandonment rate | 70.22% | Baymard Institute (meta-analysis of 50 studies) |
| Abandon because extra costs were too high | 48% | Baymard checkout research |
| Mobile cart abandonment | ~78% | Device-split industry data |
| Mobile share of ecommerce traffic | 60%+ | Multiple aggregated sources |
| Typical Shopify add-to-cart rate | ~4.6% | Shopify benchmarks |
| Typical Shopify conversion rate | ~1.4% | Shopify benchmarks |
Look at the last two rows together. Roughly seven out of ten people who liked a product enough to add it never buy it. And because 60%+ of that traffic is mobile, the cart drawer — not the cart page — is the screen where the decision happens.
The single biggest stated reason for abandonment is price shock: "I thought it would be cheaper." A discount field in the drawer attacks that reason directly, because the customer sees the lower number before they commit to checkout instead of hoping it appears later.
4 ways to add a discount code to your Shopify cart drawer
| Method | Cost | Skill | Price updates live in drawer? | Works with Shop Pay / Apple Pay? |
|---|---|---|---|---|
| 1. Redirect hack | Free | Beginner | ❌ No | ❌ No |
2. cart/update.js (recommended free) | Free | Intermediate | ✅ Yes | ✅ Yes |
| 3. Storefront API GraphQL | Free | Advanced | ✅ Yes | ✅ Yes |
| 4. Cart drawer app | Free–$29.99/mo | No code | ✅ Yes | ✅ Yes |
Method 1: The redirect hack (free, 5 minutes — but do not use it)

This is what nearly every competing tutorial publishes. You add an input, and on submit you push the shopper to checkout with the code in the URL:
<form onsubmit="event.preventDefault();
var c = this.discount.value.trim();
if (c) window.location.href = '/checkout?discount=' + encodeURIComponent(c);">
<input type="text" name="discount" placeholder="Promo code">
<button type="submit">Apply</button>
</form>
Why it fails: nothing changes in the drawer. The shopper gets zero confirmation the code was valid, so they either abandon or arrive at checkout anxious. Worse, Shop Pay, Apple Pay and Google Pay buttons build their own checkout session from the raw cart and ignore the URL parameter entirely — so your express-checkout shoppers silently pay full price. Use this only as a same-day stopgap.
Method 2: The cart/update.js discount API (free, ~30 minutes) — recommended
This is the correct free method in 2026. One request applies the code server-side and returns freshly rendered drawer HTML, so the price updates without a page reload and express checkout buttons respect it.
The core API call
fetch(window.Shopify.routes.root + 'cart/update.js', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ discount: 'SAVE20' })
})
.then(function (r) { return r.json(); })
.then(function (cart) { console.log(cart.total_discount); });
Three things the docs make possible that almost nobody uses:
- Stack multiple codes — comma-separate them, no spaces:
{ discount: 'SAVE20,FREESHIP' } - Remove every code — pass an empty string:
{ discount: '' } - Re-render your drawer in the same request — add a
sectionsparameter and Shopify returns the rendered HTML alongside the cart JSON (up to five sections per call)
Step 1 — Add the input to your cart drawer
In the theme editor, open snippets/cart-drawer.liquid (Dawn and most Dawn-based themes) or sections/cart-drawer.liquid. Paste this above the checkout button, inside the drawer footer:
One compatibility boundary before you paste: this snippet is for theme-owned drawers. If an app (UpCart, Oxify, any drawer replacement) renders your cart drawer, its markup is injected by that app's script — your Liquid edit will never appear inside it, and injecting into its DOM from JavaScript breaks on every app update. In that case, turn on the app's own built-in discount field instead.
<div class="ox-discount" data-ox-discount>
<button type="button" class="ox-discount__toggle" data-ox-toggle
aria-expanded="false" aria-controls="ox-discount-panel">
Have a promo code?
</button>
<div class="ox-discount__panel" id="ox-discount-panel" hidden>
<label class="visually-hidden" for="ox-discount-input">Discount code</label>
<div class="ox-discount__row">
<input id="ox-discount-input" type="text" name="discount"
autocomplete="off" autocapitalize="characters" spellcheck="false"
enterkeyhint="done" placeholder="Enter code">
<button type="button" class="ox-discount__apply" data-ox-apply>Apply</button>
</div>
<p class="ox-discount__msg" data-ox-msg role="status" aria-live="polite"></p>
</div>
{%- if cart.cart_level_discount_applications.size > 0 -%}
<ul class="ox-discount__applied">
{%- for d in cart.cart_level_discount_applications -%}
<li>
<span>{{ d.title }}</span>
<span>−{{ d.total_allocated_amount | money }}</span>
<button type="button" data-ox-remove aria-label="Remove {{ d.title }}">×</button>
</li>
{%- endfor -%}
</ul>
{%- endif -%}
</div>
Note the collapsed "Have a promo code?" toggle. That is deliberate — see UX mistake 1.
Step 2 — The JavaScript that applies it live
Create assets/ox-cart-discount.js and paste this in full. It applies the code, verifies it actually stuck, re-renders the drawer, and handles errors. Set SECTION_ID to your drawer's section id (cart-drawer in Dawn).
(function () {
var SECTION_ID = 'cart-drawer'; // your cart drawer section id
var DRAWER_SEL = '#CartDrawer'; // wrapper the section renders into
function root() { return document.querySelector('[data-ox-discount]'); }
function setMsg(text, isError) {
var el = document.querySelector('[data-ox-msg]');
if (!el) return;
el.textContent = text || '';
el.classList.toggle('is-error', !!isError);
}
function updateCart(code) {
return fetch(window.Shopify.routes.root + 'cart/update.js', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
body: JSON.stringify({ discount: code, sections: SECTION_ID })
}).then(function (res) {
if (!res.ok) throw new Error('Cart update failed');
return res.json();
});
}
// Shopify silently ignores invalid codes, so confirm the discount really applied.
function discountApplied(cart, code) {
var apps = cart.cart_level_discount_applications || [];
var codes = cart.discount_codes || [];
var wanted = code.trim().toUpperCase();
var hit = apps.some(function (d) {
return (d.title || '').toUpperCase() === wanted;
});
var listed = codes.some(function (d) {
return (d.code || '').toUpperCase() === wanted && d.applicable !== false;
});
return hit || listed || (cart.total_discount > 0 && apps.length > 0);
}
function repaint(cart) {
var html = cart.sections && cart.sections[SECTION_ID];
if (!html) { document.dispatchEvent(new CustomEvent('ox:cart:refresh')); return; }
var fresh = new DOMParser().parseFromString(html, 'text/html');
var next = fresh.querySelector(DRAWER_SEL);
var current = document.querySelector(DRAWER_SEL);
if (next && current) current.innerHTML = next.innerHTML;
}
function apply() {
var input = document.getElementById('ox-discount-input');
var btn = document.querySelector('[data-ox-apply]');
if (!input) return;
var code = input.value.trim();
if (!code) { setMsg('Enter a code first.', true); return; }
btn.disabled = true;
setMsg('Checking…', false);
updateCart(code)
.then(function (cart) {
if (!discountApplied(cart, code)) {
setMsg('That code is not valid for the items in your cart.', true);
input.classList.add('ox-shake');
setTimeout(function () { input.classList.remove('ox-shake'); }, 400);
return;
}
setMsg('Code applied — you saved ' + money(cart.total_discount) + '!', false);
repaint(cart);
})
.catch(function () { setMsg('Something went wrong. Please try again.', true); })
.then(function () { if (btn) btn.disabled = false; });
}
function money(cents) {
var f = (window.Shopify && Shopify.money_format) || '${{amount}}';
return f.replace(/\{\{\s*amount\s*\}\}/, (cents / 100).toFixed(2));
}
// Auto-apply codes arriving from /discount/CODE email + SMS links
function autoApply() {
var stored = sessionStorage.getItem('ox_discount');
var url = new URLSearchParams(location.search).get('discount');
var code = url || stored;
if (!code) return;
sessionStorage.setItem('ox_discount', code);
updateCart(code).then(function (cart) {
if (discountApplied(cart, code)) {
setMsg(code.toUpperCase() + ' applied — you saved ' + money(cart.total_discount) + '!', false);
repaint(cart);
}
}).catch(function () {});
}
document.addEventListener('click', function (e) {
if (e.target.closest('[data-ox-apply]')) { apply(); }
if (e.target.closest('[data-ox-remove]')) {
updateCart('').then(repaint).catch(function () {});
}
var toggle = e.target.closest('[data-ox-toggle]');
if (toggle) {
var panel = document.getElementById('ox-discount-panel');
var open = panel.hasAttribute('hidden');
panel.toggleAttribute('hidden', !open);
toggle.setAttribute('aria-expanded', String(open));
if (open) panel.querySelector('input').focus();
}
});
document.addEventListener('keydown', function (e) {
if (e.key === 'Enter' && e.target.id === 'ox-discount-input') {
e.preventDefault(); apply();
}
});
document.addEventListener('DOMContentLoaded', autoApply);
})();
Then load it from your theme layout or drawer section:
<script src="{{ 'ox-cart-discount.js' | asset_url }}" defer></script>
Step 3 — Minimum viable CSS (mobile-safe)
This is the part tutorials skip, and it is where mobile conversions leak. The env(safe-area-inset-bottom) padding and 16px font size stop iOS from zooming and from burying the Apply button under the keyboard.
.ox-discount { padding: 12px 0; }
.ox-discount__toggle {
background: none; border: 0; padding: 0; cursor: pointer;
font-size: 14px; text-decoration: underline; color: currentColor;
}
.ox-discount__row { display: flex; gap: 8px; margin-top: 10px; }
.ox-discount__row input {
flex: 1 1 auto; min-width: 0; height: 44px; padding: 0 12px;
font-size: 16px; /* prevents iOS zoom-on-focus */
border: 1px solid rgba(0,0,0,.2); border-radius: 6px;
}
.ox-discount__apply {
flex: 0 0 auto; height: 44px; padding: 0 18px; border-radius: 6px;
border: 0; cursor: pointer; font-size: 15px;
}
.ox-discount__msg { margin: 8px 0 0; font-size: 13px; color: #1a7f37; }
.ox-discount__msg.is-error { color: #b42318; }
.ox-discount__applied {
list-style: none; margin: 10px 0 0; padding: 0; font-size: 14px;
}
.ox-discount__applied li { display: flex; align-items: center; gap: 8px; }
.ox-shake { animation: ox-shake .32s ease; }
@keyframes ox-shake {
25% { transform: translateX(-4px); } 75% { transform: translateX(4px); }
}
/* keep the drawer footer above the iOS keyboard / home indicator */
.cart-drawer__footer { padding-bottom: calc(12px + env(safe-area-inset-bottom)); }
Verdict: the cleanest free option. Zero monthly cost, no extra scripts, and because the discount lands on the real cart object server-side, Shop Pay and Apple Pay honour it.
Method 3: Storefront API GraphQL (free, advanced)
For headless builds (Hydrogen, Next.js, custom storefronts) the Ajax cart does not exist. You manage discounts on the cart object with the cartDiscountCodesUpdate mutation:
mutation applyDiscount($cartId: ID!, $codes: [String!]!) {
cartDiscountCodesUpdate(cartId: $cartId, discountCodes: $codes) {
cart {
discountCodes { code applicable }
cost { subtotalAmount { amount } totalAmount { amount currencyCode } }
}
userErrors { field message }
}
}
The response gives you an applicable boolean per code, which is the cleanest validation signal Shopify offers anywhere — use it to drive your success and error states. Trade-off: you need a Storefront API public access token and must track cart IDs in GID format, and the integration is tied to your custom frontend rather than your theme.
Method 4: A cart drawer app (no code)
If you do not want to own Liquid and JavaScript forever — and if you also want auto-apply, free gifts, upsells and a progress bar working together in the same drawer — an app is the practical answer. That is the next section.
Best app for cart drawer discounts: Oxify Cart Drawer & Upsells

Oxify Cart Drawer & Upsells is Built for Shopify certified, rated 4.9 from 37 reviews on the Shopify App Store (verified August 2026), and starts at $9.99/month with a 14-day free trial. It is the option we recommend for stores that run promotions, because it does the two things a plain discount-field app cannot.
1. It applies the offer directly in the cart, not at checkout. The code validates against Shopify in real time, the line total strikes through, and the savings render inside the drawer before the shopper clicks checkout. Stacked codes, automatic discounts and cart-level offers all display correctly.
2. It auto-applies codes from your campaign links. A shopper clicking yourstore.com/discount/SUMMER20 from a Klaviyo email or an SMS blast opens the drawer to see "SUMMER20 applied — you saved $12.40." No typing, no remembering, no chance for a coupon extension to intercept it.
On top of the discount engine you get free gifts with threshold rules, BOGO, volume discounts, AI-driven cart upsells and frequently-bought-together, a free-shipping progress bar, a sticky cart, countdown timers, shipping protection, gift wrap, and post-purchase and thank-you-page upsells — in one app, with one design system, on one script. It supports 8 languages and multi-currency, and integrates with checkout extensions and apps like Judge.me and PageFly.
Why one app beats stacking four: merchants running a separate discount app, gift app, upsell app and progress-bar app typically pay $40–$80/month more, fight JavaScript conflicts between them, and end up with a drawer where four vendors' styling collide. Oxify replaces that stack.
Setup is roughly 10 minutes: install, enable the app embed in your theme, pick your drawer layout, and toggle "Discount code field" on. Nothing to paste, nothing to maintain when you change themes.
Install Oxify Cart Drawer & Upsells →
Other Shopify cart drawer discount code apps compared
Oxify is not the only option, and the right pick depends on whether you want a full drawer or just an input box.
| App | Rating | Price from | Best for | Stacking | Upsells | Free gifts |
|---|---|---|---|---|---|---|
| Oxify Cart Drawer & Upsells | 4.9 (37) | $9.99/mo · 14-day trial | Full drawer + discounts + marketing auto-apply | ✅ | ✅ | ✅ |
| Dr Stacked Discounts on Cart | 4.8 (151) | Free plan · $4.99/mo | Complex multi-code stacking | 🚀 Advanced | ❌ | ❌ |
| Dcart — Discount in Cart | 4.9 (99) | $5.99/mo · 7-day trial | Bare input field, light footprint | ✅ Combinations | ❌ | ❌ |
| UpCart — Cart Drawer | 4.7 (858) | $29.99/mo · 14-day trial | Pixel-perfect custom CSS control | ✅ | ✅ | ✅ |
| Cartix Cart Discount + Upsell | 4.9 (44) | $7.99/mo · 5-day trial | Budget slide cart with basic upsells | ⚡ Basic | ✅ | ❌ |
| DiscountX: Stack Combine Popup | 5.0 (3) | $4.49/mo · 5-day trial | Cheapest stacking widget | ✅ | ❌ | ❌ |
| Discount Code Display | 4.3 (38) | $3.99/mo | Showing shoppers which codes exist | ⚡ Higher tiers | ❌ | ❌ |
| KartDiscount — Coupon on Cart | 4.9 (91) | $4.90/mo · 3-day trial | Stacking up to 3 codes on a budget | ✅ Up to 3 codes | ❌ | ❌ |
| Snap Cart Drawer Cart Upsell | 4.9 (64) | Free | Zero-budget drawer replacement | ⚡ Basic | ⚡ Basic | ✅ |
A warning about dead apps: several guides still ranking for this keyword recommend Dcode — Discount codes in Cart, and older roundups list shoplab: Discount Code in Cart and Carter — Discount code in cart. All three are no longer available on the Shopify App Store (we re-checked each listing on August 26, 2026), so ignore any tutorial that sends you there. This niche churns fast — small single-purpose discount-field apps get delisted more often than most categories, which is worth weighing when you pick one. Ratings and pricing above were checked in August 2026; app store figures move, so confirm before you install.
Injection vs replacement: Dcart, Dr Stacked, DiscountX and KartDiscount inject a field into whatever drawer your theme already has — lighter, lower risk, limited features, and visual consistency depends on your theme's CSS. Oxify, Cartix, UpCart and Snap replace the drawer entirely — more features and one coherent design, but you lose custom styling on your original drawer. Preview either type on a duplicate theme before publishing.
Picking in one line each. Want the whole drawer solved — codes, gifts, upsells, auto-apply — with one script and one design? Oxify. Only need stacking logic and nothing else? Dr Stacked (there is a free plan). Only need a box to type a code into? Dcart. Want deep custom CSS and have the budget? UpCart. Want to advertise the codes you already run to shoppers who do not have one? Discount Code Display — but read UX mistake 1 first, because that strategy backfires on most stores.
Where to place the promo code box (and what it should look like)
Placement moves conversion as much as the code that powers it. Four patterns work, in rough order of effectiveness:
- Collapsed link directly above the checkout button. The default recommendation. It sits exactly where a shopper who has a code goes looking, and stays invisible to everyone else.
- Inline with the order summary. Put it beside the subtotal so the number it changes is in the same eyeline. Good on cart pages, tighter on mobile drawers.
- Shaded panel inside the summary block. A light background tint groups the field with the totals instead of letting it float as an orphan control. Useful when your drawer already has upsell and gift modules competing for attention.
- Promo box paired with a free-shipping progress bar. Strong combination: the code lowers the total, the bar shows how close they are to free shipping, and the natural response is to add one more item rather than check out smaller.
What to avoid: a wide, bold, always-open input at the top of the drawer. It reads as "everyone else is paying less than you," and it is the single fastest way to send a ready-to-buy shopper to a coupon site.
The coupon extension problem this quietly fixes
This is the argument nobody makes, and it is the strongest one.
When there is nowhere to type a code, shoppers do not give up on the discount — they go looking. They open Honey, Capital One Shopping, or RetailMeNot's extension, or they leave your tab to Google "[your brand] coupon code."
Three things go wrong from there. The extension may inject an affiliate code from a coupon site, so a third party gets commission on a sale your email already earned — and your influencer attribution is now wrong. It may apply a deeper discount than you intended, cutting margin for no incremental sale. Or the shopper lands on a coupon farm, gets served competitor ads, and never comes back.
A visible "Have a promo code?" link ends that detour. The shopper uses your code, from your campaign, in your drawer. Auto-apply removes even the typing step. Treat this as margin protection and attribution protection, not a convenience feature.
How cart drawer discounts multiply your marketing
Email. Abandoned-cart and welcome flows both hinge on a code the shopper has to remember. When /discount/WELCOME15 auto-applies on drawer open, the recovery email's promise is visible the second they return instead of two clicks later at checkout.
SMS. Codes have to be short and typed on a phone keyboard — the worst possible input environment. A tappable /discount/FLASH30 link plus auto-apply removes typing entirely, which matters more on SMS than any other channel.
Paid ads. If your creative says "use SUMMER20," and the drawer has no field, you have created a promise your storefront cannot keep. Every shopper who hunts for the input is ROAS you already paid for and are now leaking.
Influencer and affiliate. Codes like SARAH15 are your attribution mechanism. Entered in the drawer, attribution stays clean; hunted down via a browser extension, it gets overwritten by a coupon-site affiliate ID.
Retention. Birthday, loyalty and win-back codes are aimed at people who already like you. Making them fight the interface to redeem a gift is the fastest way to waste the goodwill.
How /discount/CODE share links actually work
Every Shopify store gets these links for free — no app, no theme code. yourstore.com/discount/SUMMER20 stores the code in the visitor's session (Shopify sets a discount_code cookie) and Shopify applies it automatically when they reach checkout. By default the link lands on your homepage, but you can control the destination with the redirect parameter:
https://yourstore.com/discount/SUMMER20?redirect=/collections/sale
https://yourstore.com/discount/SUMMER20?redirect=/products/best-seller
https://yourstore.com/discount/SUMMER20?redirect=/cart
The catch, and the reason this whole article exists: the session cookie makes the code appear at checkout, not in the cart drawer. The shopper still sees full price while they browse. That is why the autoApply() function in Method 2 reads the code on landing and POSTs it to /cart/update.js — it promotes the checkout-only cookie into a real cart discount the drawer can display. Pair the link with auto-apply and the shopper sees "SUMMER20 applied" the moment they open the cart.
5 UX mistakes that kill discount code conversions

1. Showing a big, empty discount box
A prominent empty field tells every shopper without a code that they are overpaying. They leave to find one. Collapse it behind a small "Have a promo code?" link — that targets only people who already have a code and leaves everyone else undistracted. The code in Method 2 does this by default.
2. No visible feedback when a code applies
Do not quietly change one number. Strike through the old total, show the new one in bold, and state the saving in words: "$50.00 → $40.00 — you saved $10.00." People need to feel the discount, not calculate it.
One legal footnote: reference pricing is regulated. In the EU and UK, "was/now" displays must reflect a genuine prior price (the EU Price Indication Directive pegs it to the lowest price of the previous 30 days), and California has similar former-price rules. Striking through the price the shopper was genuinely about to pay — which is what a discount code does — is fine everywhere. Inventing an inflated "was" price to make the discount look bigger is not.
3. Generic error messages (and silent failures)
Never use alert(). Show an inline message under the input with a short shake animation. Be specific: "This code doesn't apply to the items in your cart" beats "Invalid code." Critically — /cart/update.js returns HTTP 200 for a bad code, so if you do not verify the discount actually landed, your UI will cheerfully report success on a code that did nothing. The discountApplied() check above exists for exactly this.
4. Ignoring the mobile keyboard
Tap the input on an iPhone and the keyboard often covers the Apply button, and an input under 16px triggers an automatic zoom that breaks your layout. Browser dev tools do not reproduce either. Test on a real iPhone and a real Android before you publish.
5. Forgetting express checkout buttons
Shop Pay, Apple Pay and Google Pay buttons build their own session from the cart object. Any method that only passes the code as a URL parameter — the redirect hack — is bypassed completely, and those shoppers pay full price without noticing. Methods 2, 3 and a properly built app all write to the real cart, so express checkout inherits the discount.
Developer gotchas with cart/update.js discounts
Invalid codes return 200. Shopify does not error on a bad discount code via cart/update.js; it simply does not apply it. Always read the returned cart back and confirm before showing a success state.
Reading discount_codes back from the cart/update.js response
This trips up almost everyone coming from the Storefront API. GraphQL gives you a tidy discountCodes { code applicable } array with a per-code validity flag. The Ajax cart JSON that /cart/update.js returns does not — the documented places a discount shows up are cart_level_discount_applications (cart-wide offers, with title and total_allocated_amount) and each line item's discounts / line_level_discount_allocations arrays (product-specific offers). Read those, not a discount_codes field you are hoping exists:
.then(function (cart) {
var applied = (cart.cart_level_discount_applications || [])
.map(function (d) { return d.title; });
console.log('Applied:', applied.join(', ') || 'none', '· total_discount:', cart.total_discount);
});
This is why the discountApplied() check in Method 2 tests cart_level_discount_applications first and only treats cart.discount_codes as a defensive fallback — some themes and apps decorate the cart object with it, but it is not a documented part of the Ajax response, so never make it your primary check. A product-only discount also produces an empty cart-level array while the line-level arrays carry the saving, which is exactly the case that makes naive "did my code apply?" checks report false negatives.
Stacking uses arrays, not a single value. Loop cart_level_discount_applications (and the line-level discounts array) rather than checking for one code. Shopify supports combining an order discount with a shipping discount on the same cart — see our guide to stacking discounts without wrecking your margins before you turn combinations on.
Re-render, do not hand-patch prices. Passing sections in the same cart/update.js call returns server-rendered HTML with correct Liquid money formatting for every currency and locale. Hand-formatting totals in JavaScript is where multi-currency stores break.
Fetch fresh cart data on every drawer open. Cached drawer markup will happily show a stale full price to a returning shopper whose discount is still on the server.
Shopify Scripts are gone. Ruby-based Scripts stopped executing on June 30, 2026 (editing was already locked from April 15, 2026). If you were doing cart-level price manipulation with Scripts, that logic must now live in Shopify Functions or a discount app.
The upside of the post-Scripts world: automatic discounts — whether created in the admin or powered by a Shopify Functions discount — land in cart_level_discount_applications (and the line-level arrays) on their own. A drawer that already renders those arrays, like the Method 2 snippet, displays them with zero extra code and no input field at all. And because Shopify's combination rules let an automatic discount stack with an entered code, you can run "10% off over $50, automatic" alongside "WELCOME15 for subscribers" and show both lines in the same drawer.
Keep a checkout fallback. Session cookies occasionally desync between cart and checkout. Appending ?discount=CODE to your checkout URL as a belt-and-braces fallback costs nothing and prevents the rare vanishing-discount bug.
Will a cart drawer app slow my store down?
Well-built cart drawer apps add roughly 30–150KB of asynchronously loaded JavaScript. For scale, one unoptimised product image is often 500KB–2MB. Apps that replace the drawer also remove your theme's drawer JavaScript, so net impact is frequently near zero.
Measure it rather than guess: run PageSpeed Insights on a product page before installing, note mobile LCP and INP, install, and re-run the same URL. If LCP moves more than ~0.5s or INP more than ~100ms, investigate. The real performance mistake is not one good app — it is stacking three overlapping cart apps that each inject their own drawer. If you are still choosing, our roundup of the best Shopify cart drawer apps compares them on speed as well as features.
Which method should you actually use?
| Your situation | Use this | Why |
|---|---|---|
| Non-technical, want it live today | Oxify Cart Drawer | No code, handles express checkout and stacking, ~10 minutes |
| Running email / SMS / ad / influencer promos | Oxify Cart Drawer | Auto-applies codes from links and protects attribution |
| Comfortable in Liquid, zero budget | cart/update.js (Method 2) | Free, native, real-time, works on every plan |
| Headless / Hydrogen storefront | Storefront API (Method 3) | Only option outside a Liquid theme; gives an applicable flag |
| Only need a bare input box | Dcart | Simplest single-purpose app, one $5.99 plan, light footprint |
| Complex multi-code stacking rules | Dr. Stacked | Purpose-built for advanced combination logic |
FAQ
Can you add a discount code field to a Shopify cart drawer without an app?
/cart/update.js endpoint accepts a discount parameter, so you can add an input to cart-drawer.liquid and POST the code with JavaScript. It applies in real time with no page reload and works on every Shopify plan, not just Plus. Full copy-paste code is in Method 2 above.How do I apply a discount code directly in the cart instead of at checkout?
{ "discount": "YOURCODE" } to /cart/update.js. That writes the code to the real cart object server-side, which is what makes the price update in the drawer and what makes Shop Pay and Apple Pay respect it. Adding a sections parameter to the same request returns freshly rendered drawer HTML so the totals redraw instantly. The old /checkout?discount=CODE redirect does not do this — it only passes the code along at checkout.Does Shopify support multiple discount codes on one order?
{ "discount": "SAVE20,FREESHIP" }. Whether they combine depends on the combination rules set on each discount in your Shopify admin — typically one order discount plus one shipping discount. Read them back from cart_level_discount_applications (an array), not a single code field.How do I remove a discount code from the cart?
{ "discount": "" } to /cart/update.js. This clears all applied codes. To swap one code for another, just send the new code — it replaces the previous set.Why does my discount code not show in the Shopify cart drawer?
/cart/update.js returns HTTP 200 even for invalid codes, so you must verify the discount actually landed. Or you are checking a single-value field instead of looping the cart_level_discount_applications array.What is the difference between automatic discounts and discount codes?
/discount/CODE link. Both show up in the cart's discount applications once applied, but only codes need a field in your drawer.How do I auto-apply a discount code from an email or SMS link?
yourstore.com/discount/SUMMER20. Shopify stores the code in the session. To show it in the drawer, capture the code on landing, keep it in sessionStorage, and POST it to /cart/update.js when the cart loads — the autoApply() function in the Method 2 code does exactly this. Oxify Cart Drawer handles it with a toggle.Can I make a discount link that opens the cart with the code already applied?
redirect parameter to the share link: yourstore.com/discount/SUMMER20?redirect=/cart sends the shopper straight to the cart, and ?redirect=/collections/sale works for any path on your store. Shopify stores the code in a session cookie and applies it at checkout automatically. To make it visible inside the cart drawer too, pair the link with the auto-apply JavaScript from Method 2 or an app that supports discount links.Should the discount field be visible or hidden by default?
Will the discount work with Shop Pay, Apple Pay and Google Pay?
cart/update.js method, the Storefront API method and well-built apps all do this, so express checkout inherits the discount. The /checkout?discount=CODE redirect hack does not — express buttons build their own session and ignore the URL parameter entirely.What happened to Shopify Scripts?
Does Shopify have a discount code field in the cart by default?
Can customers apply a gift card in the Shopify cart drawer?
discount parameter on /cart/update.js only accepts discount codes, so posting a gift card number to it does nothing. If gift cards matter to your promotions, say so near your cart's discount field ("gift cards are redeemed at checkout") to stop shoppers from typing one there and concluding it is dead.Will the free code in this guide work if an app already powers my cart drawer?
Is there a free app for adding discount codes to the cart drawer?
cart/update.js method in this guide is genuinely free with no caps at all.What is the best Shopify app for cart drawer discount codes?
Start recovering that revenue
Nearly half of all abandonment traces back to price shock at checkout. Showing the discount in the drawer — where mobile shoppers actually decide — is the most direct fix available, and it takes under an hour either way.
- Fastest, most complete: install Oxify Cart Drawer & Upsells and have discounts, auto-apply, free gifts and upsells live in about 10 minutes.
- Free route: paste the Method 2 code into your theme. Around 30 minutes of Liquid work, no monthly cost.
- Either way: collapse the field behind "Have a promo code?", show savings with a strikethrough, verify the code actually applied before showing success, and test on a real phone.
The 70% abandonment rate is not going to fall on its own — but the shoppers who already have your code, and just cannot find where to type it, are the easiest sales you will ever recover.