FUNCTIONS · CHECKOUT · CRO · 19 MAY 2026 · 7 MIN READ
Gift with purchase, tiered discounts and cart-level promotions
The discount system reduces prices. It does not put things in carts. Once you know which half of the problem you are solving, the build stops being a fight.
Build cart-level promotions in three layers, and only move down when the layer above genuinely cannot express the rule. Native discounts handle most thresholds, spend-and-save offers and buy-X-get-Y mechanics with no code at all. Shopify Functions handle rules the discount form cannot describe — a ladder with several tiers, a rule that consults a customer segment, a cap that depends on cart composition. The third layer is the one people forget: adding a line to the cart, which no discount mechanism does. Shopify documents that the free or discounted "get" item in a buy-X-get-Y promotion is never automatically added; the customer must add it. Every gift-with-purchase build is really a build of that one sentence.
IN SHORT
- Shopify documents that a buy-X-get-Y promotion never adds the free item to the cart automatically — the customer must add it manually.
- Discounts reduce prices on lines that already exist, so gift with purchase is two problems: getting the line into the cart, and then making it free.
- Cart Transform can expand one line into several, which covers a gift bundled into a product but not a gift earned by hitting a cart value.
- Native discounts express one threshold each, so a three-tier ladder is three discounts that must be prevented from combining — or one Function.
- A store may have a maximum of 25 active automatic discounts, app-based discounts included, which is the ceiling a tier ladder eats into fastest.
- Most of the conversion effect of a threshold promotion comes from the progress message, not from the discount, and that lives in the theme.
The distinction that makes this easy
Cart promotions divide cleanly into two kinds, and almost every difficult build is difficult because somebody tried to solve one with the tools for the other.
Price changes. Spend £100 and save 10%. Free shipping over £75. Buy three, pay for two. Every one of these operates on lines the customer has already put in the cart, and every one of them is a discount problem — native first, Functions if the rule is too complex for the form.
Composition changes. A free gift appears. A bundle expands into components. A sample is included above a threshold. These need a line item that was not there, and the discount system does not create line items. It has no mechanism for it. This is not an oversight to be worked around with a cleverer discount; it is the shape of the platform.
Ask which kind you have before anything else. If the answer is "both" — a gift that also has to be free — then you have two builds, and they are independent.
Gift with purchase, and the sentence that governs it
Shopify’s documentation on buy-X-get-Y is unambiguous: customers must add all items to their cart manually, and the free or discounted "get" item is never automatically added. For a discount code the customer adds the qualifying products and then enters the code; for an automatic discount it applies once the items are in the cart.
That is the whole design constraint. A native buy-X-get-Y will happily make the gift free, and it will not tell anybody to put the gift in the cart. If you configure one and launch it, what happens is that a small, self-selecting group of shoppers who already knew about the offer get their gift, and everyone else pays full price for a promotion you paid for. It looks like the discount is broken. It is working exactly as documented.
So a gift-with-purchase build is two pieces:
- Getting the line in. Something has to add the gift. On a threshold-based offer that is cart-side logic — the theme watches the cart, and when it crosses the threshold it adds the gift variant and removes it if the cart drops back below. This is the part that needs building and the part that needs testing hardest, because it runs on every cart change.
- Making it free. Once the line exists, a native buy-X-get-Y or a Function discount zeroes it. This half is close to trivial and is where most of the attention goes anyway.
- Stopping it being abused. A gift the customer can add directly, or increase the quantity of, or keep after removing the qualifying items, is a gift somebody will take. Limit the quantity, hide the gift product from search and collections, and make the removal path unambiguous rather than silent.
Where Cart Transform fits, and where it does not
Cart Transform is the API people reach for here, and it is worth being precise about what it covers. Its operations work on lines that exist: expand one line into its components, merge several lines into one presented line, update a line’s price, title and image.
Expand is genuinely powerful for a gift *bundled into a product* — the customer adds one thing, and the cart contains that thing plus its components, including items they never picked individually. If your promotion is "this kit includes a free travel size", modelling it as a bundle parent that expands is cleaner than any discount.
What expand cannot do is conjure a line from a condition. There is no parent line to expand when the qualifying event is "the cart subtotal passed £75 because they added a fourth candle". That case still needs something cart-side to add the gift. Knowing this before scoping saves a fortnight, because "we will do it with Cart Transform" is a plan that survives right up until the first threshold requirement.
Tiered discounts: one rule or several?
A tier ladder — spend £50 save 5%, spend £100 save 10%, spend £200 save 15% — is the most commonly requested cart promotion and the one most often built badly.
Natively, each tier is its own discount with its own minimum purchase amount. Three tiers is three discounts, and the design problem is immediately that a £200 cart qualifies for all three. Shopify documents that multiple order discounts can combine, which is exactly what you do not want here, so every tier has to be configured not to combine with its siblings. That is a configuration that works and that somebody will get wrong in a hurry the week before a sale, because it is three separate records whose correctness depends on each other.
It also spends your ceiling. A store may have a maximum of 25 active automatic discounts, and that total includes app-based discounts. Two tier ladders, a shipping threshold and a handful of evergreen offers and you are closer to that number than you think.
A Function expresses the whole ladder as one rule: read the subtotal, pick the tier, apply it. One record, one place to change the numbers, no possibility of two tiers applying at once because the code returns one answer. That is the case for a Function here — not capability, but the elimination of a class of configuration error that only shows up under promotion pressure.
The honest counter-argument is that a three-tier ladder is rarely worth what it costs to run. It is harder to communicate, harder to test, and the evidence that shoppers optimise across tiers is thinner than the people proposing them assume. Two tiers, or one threshold, will usually be understood — and a promotion nobody understands does not convert regardless of how elegantly it is built.
The half that lives in the theme
Everything above is the mechanism. The conversion effect is somewhere else entirely.
A threshold promotion works because the shopper knows how close they are to it. "Add £12 more for free delivery" is the promotion; the discount record is just the accounting. That message has to appear where the decision is made — the cart drawer, the mini cart, the line under the add-to-cart button — and it has to be right after every cart change, including the ones caused by removing an item, changing a quantity, or applying another discount.
Three things this breaks on, all worth testing deliberately:
- Stale totals in the drawer. The cart updates, the message does not, and the shopper is told they need £12 more when they have already qualified. Re-render the whole region from the server rather than patching a number client-side.
- Disagreement with checkout. The cart says one thing, checkout applies another, and the shopper notices. Functions run inside checkout; theme messaging is a separate implementation of the same rule, which means the rule now exists twice. Keep the numbers in one place — settings, metafields — so at least the two implementations read from the same source.
- The gift on a threshold that the customer then breaks. They qualify, the gift appears, they remove an item, and now the cart holds a free product it should not. The removal path needs to be as well built as the addition path, and it is almost never as well tested.
How we would scope one
In order, and stopping as soon as the answer holds.
- Write the promotion as a sentence a shopper would understand. If it takes two sentences, expect the build to cost more than the promotion returns.
- Ask whether it changes prices, changes cart composition, or both. Both means two builds; say so before the estimate rather than after.
- Try it in the native discount form first. Thresholds, spend-and-save, buy-X-get-Y and shipping discounts cover more ground than most teams expect, and a native discount is one a marketer can change without a release.
- Move to a Function when the rule cannot be expressed, or when expressing it natively requires several interdependent records — a tier ladder being the standard example.
- Budget the theme work properly. Progress messaging, drawer behaviour and the gift removal path are usually more of the build than the discount logic, and they are what the shopper actually experiences.
- Test the unhappy paths: qualify then un-qualify, apply another discount on top, change quantity on the gift, arrive at checkout with a stale cart from yesterday.
What we would talk you out of
A gift picker — choose your free gift from six options — unless the promotion is the centrepiece of a campaign. It multiplies the cart-side logic, the inventory edge cases and the support load, and it is a lot of engineering to make free things slightly more free.
Rebuilding a promotion as a Function because it feels more robust. A native discount that a marketer can edit at four o’clock on a Friday is more robust in the way that matters. Use Functions where the rule genuinely will not fit, and leave everything else in the admin.
And an offer designed around what the platform can do rather than what a customer wants. The mechanics above will build almost anything. Whether the promotion is worth its margin is a different question, and it is the one worth arguing about first.
Questions this raises
How do you build cart-level promotions on Shopify?
In three layers. Native discounts cover thresholds, spend-and-save, buy-X-get-Y and shipping offers with no code. Shopify Functions cover rules the discount form cannot express, such as a multi-tier ladder or a rule that depends on cart composition. A third layer — cart-side logic in the theme or an app — is needed whenever a line has to be added to the cart, because no discount mechanism creates line items.
Does Shopify add the free gift to the cart automatically?
No. Shopify’s documentation states that customers must add all items to their cart manually and that the free or discounted "get" item is never added automatically. Configuring a buy-X-get-Y discount and launching it without cart-side logic means only shoppers who already knew about the offer will claim it.
Can Cart Transform add a free gift?
Only where there is a parent line to expand. Cart Transform expands one line into its components, merges several lines into one, and updates a line’s price, title and image — so a gift bundled inside a product works well. A gift earned by crossing a cart value has no parent line to expand from, and still needs something cart-side to add it.
Should a tiered discount be built natively or with a Function?
Natively, each tier is a separate discount with its own minimum, and because Shopify allows multiple order discounts to combine, every tier must be configured not to combine with its siblings — several interdependent records that are easy to get wrong under deadline. A Function expresses the ladder as one rule with one answer. Use a Function for three or more tiers; for one or two, stay native so marketing can change it without a release.
How many discounts can be active at once?
Shopify documents a maximum of 25 active automatic discounts per store, and that total includes app-based discounts. Tier ladders consume this fastest, since each tier is its own record. Separately, a customer can use at most five product or order discount codes and one shipping discount code on the same order.
Why does the cart show a different total from checkout?
Usually because the rule has been implemented twice. Functions run inside Shopify’s checkout; the progress message and totals in the cart drawer are theme code implementing the same rule again. Keep the thresholds and amounts in one place that both read from, and re-render the cart region from the server after every change rather than patching numbers client-side.
NEXT STEP
Free store audit
A senior Shopify engineer reviews your storefront, theme performance and checkout, then sends a prioritised list of fixes.
