When native Shopify discounts can’t express the rule you actually need, you have three real paths for custom Shopify discount logic: an offer app that already covers your case, a custom app, or a Shopify Function that runs your rule server-side at checkout. Which one fits comes down to two questions — how complex is the logic, and how checkout-safe does it have to be. Native discounts handle simple, single-class offers. The moment your rule combines customer tags, specific products, tiers with exceptions, or markets, you move up the ladder to an app or code — and if the discount must hold at payment, a Function is the honest answer.
What native Shopify discounts can and can’t do
Shopify’s built-in discounts are organised into classes: product discounts, order discounts, shipping discounts, and buy-X-get-Y. Each is either a code the shopper enters or an automatic discount applied for them. Within those classes you get a fixed set of conditions — minimum quantity or spend, specific products or collections, and (for codes) customer eligibility by segment.
That covers a lot. Where it stops:
- Same-class discounts don’t stack. Two automatic order discounts won’t both apply — one wins. You can generally combine across classes (a product discount with a shipping discount) only when each discount is explicitly set to allow it. See how to run multiple discounts on Shopify for the combination rules in detail.
- Conditions are AND-only and shallow. You can’t express “10% off for wholesale-tagged customers, but only on full-price items, and never below cost.”
- No branching or exceptions. “Tier pricing, except for the clearance collection” isn’t a native concept.
- Limited customer targeting. Native targeting leans on segments; true per-tag or per-company B2B pricing quickly outgrows it.
If your offer lives inside those lines, use native — it’s free and reliable. Learn the ceiling first with how to create automatic discounts on Shopify.
Why getting this right matters
Complex offers exist because they work: tiered pricing, bundles, spend thresholds and segment-specific deals are some of the most reliable levers for average order value. But a discount is only worth building if it’s correct (it fires exactly when it should and never when it shouldn’t) and checkout-safe (the price the shopper sees survives to the payment screen). A clever offer that leaks margin — or that a shopper can game by editing their cart — costs more than it makes.
The options ladder
Work down this list and stop at the first rung that genuinely covers your rule.
1. Native discounts
Free, no app, reliable. Use for single-class offers with simple conditions: a percentage off a collection, a spend threshold for free shipping, a straightforward buy-X-get-Y.
2. An offer app
When you need a mechanic native discounts don’t ship — volume/tiered pricing on the product page, mix-and-match, a checkout-safe free gift — an app is the fast path. The one thing to verify: does it enforce the discount at checkout, or only in the storefront? A good offer app applies the rule with a Shopify Function so it can’t be bypassed. Many cheaper apps only change the displayed price in JavaScript, which is exactly the trap in the mistakes section below.
3. A custom app
When your logic is genuinely unique to your business — per-company contract pricing, an ERP lookup, rules no off-the-shelf app models — a custom app gives you full control over both the admin experience and the discount itself. This is also the right call when you’re consolidating three or four overlapping discount apps into one maintained build.
4. A Shopify Function
The engine underneath the top two rungs. A Shopify Function is code you deploy that Shopify runs server-side at checkout. A discount Function receives the cart and returns the discounts to apply — so any rule you can express in code becomes a real, enforced discount. It’s the checkout-safe foundation, whether wrapped in an app UI or driven by metafields.
Concrete examples
- Customer-tag / company pricing. “Members of the
wholesaletag get 15% off;vipgets 20%; everyone else full price.” Native targeting can’t branch on arbitrary tags cleanly — a Function reads the customer and applies the right rate at checkout. - Specific product / collection rules. “10% off the whole cart, but full-price items only — clearance never discounts further.” A Function inspects each line and excludes the ones that shouldn’t qualify.
- Tiered pricing with exceptions. “Buy 3+ save 10%, 6+ save 15% — but bundles and gift cards are excluded from the count.” Thresholds with carve-outs are code, not config.
- Market / currency conditions. “Free shipping over €75 in the EU market, but $120 in the US.” Rules that vary by market or currency are a natural fit for a Function reading the cart’s context.
- Best-of-several offers. Several promotions overlap and you want the shopper to get the single best one, not a stack or a conflict. A Function can compute each candidate and return only the winner.
The checkout-safety point
This is the hinge of the whole decision. Storefront-only discount logic can be gamed. If an offer is applied by JavaScript on the cart or product page, a shopper can trigger it — hit the quantity, add the qualifying item — then remove part of the cart after the price drops and keep the discount through to payment. You lose margin and never find out until you reconcile orders.
A Shopify Function closes that gap because it runs at checkout, on Shopify’s servers, and re-evaluates the cart at the moment of payment. If the conditions no longer hold, the discount doesn’t apply — there’s nothing for the shopper to game. That’s why “does it enforce at checkout?” is the question that should decide between two apps, and why genuinely custom logic almost always ends up as a Function.
How to decide
| Your situation | Best option |
|---|---|
| Single-class offer, simple conditions | Native discounts |
| A known mechanic (volume, mix-and-match, gift) that an app covers and enforces at checkout | Offer app |
| Logic unique to your business, or consolidating several apps | Custom app |
| Any rule that must hold at payment and native/apps can’t express | Shopify Function |
A quick way through it: can native do it? If not, does a checkout-safe app do it? If not, is the logic worth owning as a custom app — and either way, is the discount itself a Function so it can’t be bypassed? For a side-by-side on the app-versus-code trade-off, see Shopify Functions vs discount apps.
Common mistakes
- Faking the discount in JavaScript. The single biggest one — a price that looks right on the storefront but isn’t enforced at checkout. Always verify at the payment screen.
- Stacking assumptions. Expecting two same-class discounts to both apply. They won’t; one wins.
- Building custom before checking apps. If a maintained, checkout-safe app already covers your rule, that’s cheaper than owning code.
- Underestimating exceptions. “Simple 10% off” quietly becomes “except these, above this, for these customers, in this market” — which is code, and better scoped before you start.
Every rule above can be built correctly and enforced server-side. The work is deciding which rung of the ladder your logic actually needs — and where it doesn’t, staying on the free one. If you’re not sure, a free profit audit will tell you whether an existing app covers your case or whether a build is justified.
Rules too complex for native Shopify discounts? Tell us the logic you need — we’ll tell you whether an app covers it or whether a checkout-safe Shopify Function is the right build. See Custom Apps & Shopify Functions or get a free profit audit.