Shopify does not let every discount pile onto every other one — and understanding exactly how discount stacking works is the first step to plugging the leak. Shopify only stacks discounts across classes: a product discount, an order discount, and a shipping discount can combine, but two discounts of the same class never stack (Shopify keeps the better one). The margin problem is almost never a bug. It’s that you enabled a combination — usually a code plus an automatic discount plus free shipping — and never stress-tested the worst-case cart it produces. The fix is not to kill promotions; it’s to audit them with a test matrix so you can see the deepest discount before a customer does.
How Shopify discount combinations actually work
Every Shopify discount belongs to one of three combination classes:
- Product discounts — a percentage or fixed amount off specific products or collections (for example “20% off hoodies”).
- Order discounts — a percentage or fixed amount off the cart subtotal (for example “$15 off orders”).
- Shipping discounts — free or reduced shipping.
Two rules govern how these interact, and both matter:
-
Same class does not stack. If a customer qualifies for two product discounts, Shopify applies the one that gives them the better price — not both. The same is true for two order discounts or two shipping discounts. This is Shopify’s built-in protection, and it’s why “double discounting” within a class is rarely your problem.
-
“Combinable” means across classes. When you create a discount you explicitly choose which other classes it can combine with. Mark an order discount as combinable with shipping discounts and a customer can take both at once. That cross-class combination is real stacking — and it’s exactly where unplanned margin loss lives.
So the honest picture is: Shopify prevents the naive case (same-class doubling) automatically, and hands you the switch for the dangerous case (cross-class stacking). If a cart looks over-discounted, someone turned that switch on.
Why this matters for margin
A single stacked cart looks harmless. The problem is scale and worst-case. If your average order carries a 20% product discount, and your “combinable” settings also allow an order-level code and free shipping, your effective discount on the wrong cart can quietly climb well past what you modelled. On a low-margin SKU, three combined discounts can erase the contribution margin entirely — you ship the order at a loss and pay to acquire the customer who placed it.
You usually don’t feel this in the dashboard, because the average hides it. The damage is in the tail: the small fraction of carts that hit every combination you left open. At volume, that tail is a recurring, invisible line item.
Where the leaks actually come from
Most stacking leaks trace to one of three sources:
- Combinations you enabled and forgot. A seasonal campaign set an order discount to combine with product and shipping discounts. The campaign ended; the automatic discount stayed live. Now every evergreen product promo silently combines with it.
- Codes plus automatic discounts plus shipping. Automatic discounts apply with no code, so a customer can paste a code on top of a discount they already have — and if you’ve also set a free-shipping threshold, that’s three classes at once. Our guide on how to run multiple discounts on Shopify walks through the combinability settings that control this.
- App-applied discounts landing on top of native ones. Bundle, upsell, and loyalty apps frequently apply their own discount to the cart. Depending on how the app is built, that discount can combine with your native Shopify discounts in ways neither system was designed to reason about. This is the hardest leak to see, because the two systems don’t share a single source of truth — see Shopify Functions vs discount apps for why server-side enforcement behaves more predictably here.
A related trap is assuming automatic discounts are “safe” because they need no code. They’re often the most stackable thing in the store — read how to create automatic discounts on Shopify before you leave one running alongside codes.
How to audit it: a worst-case-cart test matrix
You can’t reason about stacking in your head. Build a matrix of realistic carts and record the total discount each one produces. The goal is to find the deepest combination a real customer could assemble, then decide whether you’re happy shipping it.
For each row, build the cart in a test order (or a draft order) and read the actual total after all discounts apply. List price and effective discount are illustrative — fill in your own numbers:
| Test cart | Product discount | Order discount | Shipping discount | Combined? | Effective total discount | Verdict |
|---|---|---|---|---|---|---|
| Baseline single item | 20% off item | — | — | n/a | 20% | OK |
| Two product promos | 20% off + 15% off | — | — | Same class → better one only | 20% | OK (as designed) |
| Product + order code | 20% off item | $15 off order | — | Yes | ~30%+ | Review |
| Product + free shipping | 20% off item | — | Free ship | Yes | 20% + ship cost | Review |
| Full stack | 20% off item | $15 off order | Free ship | Yes | Worst case | Likely too deep |
| Low-margin SKU, full stack | 20% off item | $15 off order | Free ship | Yes | Below cost? | Fail — fix rules |
Run the matrix against your actual combinability settings, not what you think they are. Two rows earn special attention: the full stack row (does the deepest legal cart still clear margin?) and the low-margin SKU row (the same rules that are fine on a hero product can be fatal on an accessory). If any row dips below your floor, the rule set — not the customer — is the problem.
Safe design principles
Once you can see the worst case, design for it:
- Limit combinability deliberately. Default to not combinable. Only open a cross-class combination when you’ve tested the cart it produces and accepted the margin.
- Prefer best-deal-wins over stacking. For most promotions, letting the customer take the single best discount is both simpler and safer than layering. Shopify already does this within a class — extend the same instinct across classes.
- Cap the total. Decide the maximum effective discount any cart may reach, and design your rules so no legal combination exceeds it.
- Protect low-margin SKUs. Exclude thin-margin products from the deepest combinations, or scope order/shipping discounts to a subtotal threshold that guarantees contribution.
- Enforce the intent server-side. Native combinability settings are coarse. When your real rule is conditional — “free shipping only if no order code is applied,” “bundle price or the store-wide sale, never both” — you can’t express it with checkboxes. A Shopify Function evaluates the cart at checkout, server-side, and applies only the outcome you intend, regardless of what else the customer has assembled.
Common mistakes
- Trusting the average. The dashboard’s average discount hides the tail carts that actually hurt. Audit the worst case, not the mean.
- Leaving campaign discounts live. Every expired-but-active automatic discount is a permanent combination partner for your evergreen promos.
- Assuming apps respect native rules. An app’s discount may combine with yours in ways neither was designed for. Test the combined cart, not each system alone.
- Treating combinability as set-and-forget. Every new discount changes the matrix. Re-run it whenever you add one.
Stacking isn’t a Shopify flaw — it’s a set of switches you own. Map them, test the worst-case carts, and either accept each combination or shut it off. When the rule is too nuanced for native settings, move the decision into a Function so the cart is judged where it’s actually assembled. If you’d rather start from a full-store diagnosis, our free profit audit reviews your live discounts and flags the combinations quietly costing you.
Not sure which discount combinations are costing you? Send us your store URL — we’ll map your combinable discounts, stress-test the worst-case carts, and tell you what to change. See Custom Apps & Shopify Functions or get a free profit audit.