Blocking discount codes on sale items is the standard fix for the double-discount problem, and it works. But it has a cost nobody puts on a dashboard: the customer whose code “doesn’t work” at checkout, who was maybe going to spend $150, and who closes the tab instead.
If codes are part of how your brand sells, there’s a smarter shape for this. Don’t block the code on sale items. Shrink it.
All items 20% off. Sale items 5% off. One code.
The customer’s code always works. You just decided in advance what it’s worth on each side of the markdown line. The double-dip still happens, technically, but at a rate you priced in, and a rate you priced in is a plan rather than a leak.
Why this converts better than blocking
A rejected code feels like a broken promise, even when the fine print was on your side. A code that applies, but applies modestly to clearance items, feels like it worked. The customer sees a discount land; you see 5% instead of 20% on the lines that were already marked down 30%.
For stores where a big share of traffic arrives holding a code (from email, from an influencer, from a welcome popup), that difference is real revenue. The all-or-nothing lock trades those checkouts away for margin. The two-rate version keeps both.
How to build it in PromoLock
The whole thing lives inside one discount, using cart groups with a sale-status filter. No collections to maintain, no product tagging. The split happens live at checkout based on each item’s price versus its compare-at price.
- Create your discount in PromoLock (code or automatic).
- In the right rail, under Margin guards, set On-sale items to Shopify default. This matters. A new discount ships with it on No discount, and on that setting sale items are removed before your offer ever sees them, so a sale-item rate would have nothing to apply to. The sale-status filter doesn’t even appear until you change it. Skip past Sale or code for this build (more on that option below).
- Create two cart groups. Group one, call it
Full-price items: turn on Filters, tick Sale status, choose Only full-price items. Group two,Sale items: same filter, Only items on sale. Leave the catalog pickers empty on both. The filter alone splits the entire cart, and every item lands in exactly one group. - In the offer, add two product discounts: 20% aimed at
Full-price items, 5% aimed atSale items. - Set Cap total discount so even a giant mixed cart has a ceiling.
Because the groups are computed from live prices, the setup maintains itself. Mark a product down tomorrow and it moves to the 5% side on its own. Restore the price and it moves back.
The lazier version: Sale or code
If two groups feel like more setup than the problem deserves, the same On-sale items guard has a third setting, Sale or code. Whichever saves more, not both. The code still works on sale items, but the markdown the sale already gave counts toward it. A 20% code on a $10 hoodie marked down to $7 gives nothing extra, because the sale was already worth $3 and the code is only worth $2. A 50% code on the same hoodie takes it to $5. Full-price items get the full 20% as before.
It’s a different promise than the two-rate build. Sale or code says “20% off the original price, and never more than that.” The two-rate discount says “sale items always get a little something for the code.” If your customers mostly hold codes for the feeling of a discount landing, the two rates convert better. If what you care about is that no item ever leaves at more than 20% below its original price, Sale or code is one click and you’re done. It has its own post with the full math.
One thing to switch off
If you’ve been running PromoLock’s sale-items lock in the Controller, turn it off before launching this. That lock blocks all codes on sale-heavy carts, including your carefully built two-rate one. The lock and the two-rate discount are two answers to the same question, so pick one.
And keep the stacking lock on. The two-rate discount prices in the dip; a second code stacked on top un-prices it.
Which stores should still just block
There is another side to this. If your sale prices are final-final, if you run deep clearance where even 5% on top hurts, or if codes are rare on your store anyway, the plain block is simpler and there’s nothing wrong with it. The two-rate structure earns its keep when code usage is high and checkout abandonment has a price.
What to watch afterwards
In PromoLock’s analytics, the sale-item loss report won’t go to zero under this policy, and that’s correct, because the 5% is planned spend now. What you want is a line that stays low and doesn’t trend anywhere. If it starts climbing, some other code is reaching your sale items ungoverned, and the report’s top-discounts list will tell you which one.