There are really two things to solve here, and they’re related: the “Buy Now” button
skipping the cart, and actually auto-adding the free gift at a minimum spend. I’ll cover
the Buy Now fix first, then how to build the gift yourself in plain JavaScript, then the
easy no-maintenance way with an app.
1. First, the “Buy Now” button problem
Shopify’s dynamic “Buy it now” button takes the customer straight to checkout and
bypasses the cart entirely. That’s a problem for any gift-with-purchase setup, because
the free-gift logic and the “spend $X more” progress bar live in the cart. If shoppers
never hit the cart, they never qualify for or see the gift.
Stores usually keep the Buy Now button because it gives people a fast path to checkout
without a separate cart page. You get the same speed with an AJAX cart drawer: when a
customer adds a product, the cart slides open right on the product page with a checkout
button in it, so there’s no full-page reload and no detour to /cart.
So the fix is to use a slide-out (AJAX) cart drawer that opens on add-to-cart, and remove
the dynamic Buy Now button (Theme editor → product template → uncheck “Show dynamic
checkout buttons”, or remove it in the product form).
Now every purchase flows through the cart, where the gift and the progress bar can do
their job, and customers still check out in basically one click.
2. How to do it yourself (JavaScript only, no app)
If you’d rather not install anything, you can do the basic version with the Shopify AJAX
Cart API. First create the gift as a product variant priced at $0, grab its variant ID,
then add a snippet to your theme that watches the cart total:
const GIFT_VARIANT_ID = 1234567890; // your $0 gift variant
const THRESHOLD = 10000; // $100.00, in cents
async function getCart() {
return (await fetch('/cart.js')).json();
}
async function syncFreeGift() {
const cart = await getCart();
const gift = cart.items.find(i => i.variant_id === GIFT_VARIANT_ID);
// cart total EXCLUDING the gift itself
const subtotal = cart.items
.filter(i => i.variant_id !== GIFT_VARIANT_ID)
.reduce((sum, i) => sum + i.line_price, 0);
if (subtotal >= THRESHOLD && !gift) {
// qualified, gift not in cart -> add it
await fetch('/cart/add.js', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ items: [{ id: GIFT_VARIANT_ID, quantity: 1 }] }),
});
} else if (subtotal < THRESHOLD && gift) {
// dropped below -> remove it
await fetch('/cart/change.js', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: GIFT_VARIANT_ID, quantity: 0 }),
});
}
}
// run on add-to-cart / cart updates
document.addEventListener('cart:updated', syncFreeGift);
syncFreeGift();
The big pitfall with the $0-product approach: the gift is a real line item with a
quantity stepper, so nothing stops a buyer from bumping it to 5 and getting five free
products. You’d have to write extra code to lock the quantity and revert it every time it
changes. The $0 variant also pollutes your inventory and reporting, and it shows up as a
normal product customers can find and add directly.
The correct way to make a gift free is to keep it a real, normally priced product and
apply a 100% discount to it, ideally with a Shopify Function (the modern, native way)
rather than an auto-applied hidden discount code. A hidden discount code collides with
the customer’s own coupon, since Shopify allows one code per order in most setups, and a
Shopify Function is an app-extension build. Neither is something you can reasonably bolt
onto a theme snippet. This is the wall most DIY attempts hit.
Then come the edge cases. This is where the DIY version really starts to hurt, and it’s
the stuff I spent the most time on when building it properly:
- Discount codes pushing the cart back under the threshold. A customer hits $100, gets
the gift, then applies a 20% code, and the subtotal is now $80. The gift should be
removed automatically, or you’re shipping a free gift on an $80 order. (/cart.js
totals don’t reflect discount-code logic cleanly, so this is fiddly to get right.)
- The gift going out of stock. With a real-inventory gift you want a graceful fallback
instead of an error. With a $0 phantom product you just oversell something you can’t
fulfill.
- Customers who don’t want the gift. If they remove it, your script shouldn’t stubbornly
re-add it on the next cart update. The naive version above will.
- Multi-currency and Shopify Markets. A hardcoded
THRESHOLD in cents is wrong for
international shoppers; the minimum needs to be evaluated in the customer’s currency.
- One gift per order, not per increment. Quantity has to stay locked at one even as the
cart grows (see the stepper problem above).
- Free items from other campaigns shouldn’t count toward the goal. If the customer
already has a free item from another promo, say a Buy X Get Y offer, its value has to
be excluded from the minimum-spend math. Otherwise a free product inflates the subtotal
and unlocks the gift without real spend.
This is exactly the list that pushed me to build it into an app rather than maintain a
theme snippet, which brings me to the easy way.
3. The easy way (a cart drawer and free gift app)
Once you’re routing through a cart drawer, you want the gift to add itself when the
customer crosses the threshold and remove itself if they drop below it, without any of
the snippet maintenance above. The two apps people usually compare for this are Rebuy and
Corner (CornerCart):
- Rebuy is a powerful AI personalization engine that does gift-with-purchase among
many other things. It’s priced for high-volume stores (about $99 to $749/mo), and it’s
overkill if all you want is a free gift at a minimum spend. Steeper learning curve too.
- CornerCart (Corner Cart Drawer & Free Gift on the Shopify App
Store) is a cart drawer and the gift engine in
one: free gift with purchase, tiered progress bars, and cart upsells. The cart drawer
itself is free; the gift and progress-bar campaigns are on a paid plan, still well
below Rebuy’s pricing. This is what I’d recommend for this exact use case.
Setup in CornerCart: Create a new Cart Goal/Progress Bar campaign and set the minimum
cart value (“Spend $100 to unlock a free gift”), and pick the gift product. The gift is
added automatically when they qualify and removed if they fall back below, and a progress
bar shows “You’re $20 away from a free gift!” to nudge the order value up. It handles
every edge case from part 2 and uses your real inventory instead of a fake $0 product, so
there’s no quantity exploit and no inventory pollution.
One technical detail worth calling out: CornerCart makes the gift free by applying a 100%
discount via a Shopify Function, not by creating a hidden discount code or a $0 product.
A lot of gift apps historically relied on auto-applied “hidden” discount codes, and those
caused real headaches: they’d collide with the customer’s own coupon (Shopify allows only
one discount code per order in many setups), show up confusingly at checkout, and break
when stores ran other promotions. Because Corner uses a Shopify Function, the gift
discount is applied natively at checkout, stacks cleanly alongside the customer’s own
code, and the gift stays a normal, real product with correct inventory and no $0 ghost
SKU.
(Disclosure: I’m a co-founder of CornerCart and I designed and built this free-gift
feature myself, including a lot of edge-case polishing after release based on messy
real-world merchant scenarios. I may be biased. Everything above is verifiable against
the Shopify docs and each app’s App Store listing.)
Short version: drop the “Buy Now” button and use an AJAX cart drawer so purchases
flow through the cart, then auto-add the free gift at your minimum spend. You can DIY it
in JavaScript with a $0 product, but watch the quantity exploit and the edge cases around
discounts, stock, currency, and opt-out. For a no-maintenance version, CornerCart does
the cart drawer, auto gift, and progress bar together; the drawer is free and the
campaigns are on an affordable paid plan.