Why this happens
Shopify treats the compare-at price as a display field, a strikethrough hint for your theme. It isn’t a discount, and the checkout doesn’t know about it. So when a customer applies a code or an automatic discount, the math runs against price, not compareAtPrice. If you’ve lowered price below compareAtPrice to show a sale, every additional discount stacks on top.
The native workarounds break in common cases:
- Markets. Pricing varies per region; collection rules can’t capture that.
- Sitewide sales. Moving every product into and out of an “eligible” collection is brittle.
- Wholesale and VIP codes. You want “50% off retail,” not “50% off marked-down,” but Shopify can only do the latter.
- Ditch compare-at entirely. Works in theory, but you lose strikethrough prices, which kills perceived savings.
Two patterns merchants actually want
There’s no single “right” answer. The intent depends on the promotion, so Solo Discount supports both modes, switchable per discount.
Mode 1 · Discount off the compare-at price (with a floor)
Calculate the discount as a percentage or fixed amount off the compare-at price. If the current price is already lower than the discounted-from-compare-at target, no further discount applies, so customers never pay more than the visible sale price.
Worked example · 40% off code, mode = compare-at
Mode 2 · Skip variants already on sale
Apply the discount only to variants at full price (current price equals compare-at, or no compare-at set). On-sale variants are skipped entirely, so the discount only touches full-price items. This is usually the cleaner choice for sitewide promos where you don’t want to negotiate against your own markdowns.
Built-in vs Solo Discount, side by side
Same item: compare-at $100, current price $80. Customer applies a 20% off code.
| Shopify built-in | Solo Discount | |
|---|---|---|
| Math basis | current price ($80) | compare-at ($100) |
| 20% applied | $80 − $16 = $64 | target $80 → no extra discount |
| Customer pays | $64 | $80 |
| Effective markdown | 36% off retail | 20% off retail |
Why a Shopify Function
Both patterns have to run during checkout pricing, after Shopify computes line subtotals and before the cart total is finalized. Theme-level workarounds (Liquid, JavaScript on the product page) can’t do this. They don’t run at checkout, where it actually matters.
Solo Discount runs as a Shopify Function. The logic executes on Shopify’s infrastructure, on every cart, with no theme dependency and no performance hit. Any storefront that uses Shopify checkout (standard, Shop Pay, Hydrogen) gets the same behavior.
Use cases
01
Wholesale & VIP pricing
Negotiated 50% off retail for tagged customers, calculated against compare-at, regardless of whether the item is currently on sale.
Compare-at mode
02
Sitewide promo, no stacking
“10% off everything” without your existing sale items getting an extra 10% on top. Full-price items get the discount; on-sale items keep their existing markdown.
Skip-on-sale mode
03
Markets with regional pricing
Per-variant evaluation at checkout. Market-level pricing differences are handled automatically, with no collection rules to maintain and no per-market discount duplication.
Either mode
Frequently asked
Does this work with discount codes or automatic discounts?
Both. The protection strategy applies the same way regardless of how the discount is triggered.
Does it stack with shipping or other discounts?
You control stacking via Shopify’s standard “combinations” setting on each discount. Solo Discount respects whatever you configure there.
Does it work on Hydrogen or custom storefronts?
Yes. The function runs at Shopify checkout, so any storefront using Shopify checkout (standard, Shop Pay, Hydrogen) gets the same behavior.
Does it support fixed amount off, or only percentage?
Both. Fixed amount works the same way: calculated off compare-at, with the floor.
Where do decisions happen, at the product or variant level?
Variant. A product with one full-price variant and one on-sale variant gets the discount on the full-price variant only.