How to exclude products from discount codes on Shopify
Every Shopify merchant hits this one eventually. You launch a bundle at a price that already has the margin squeezed out of it, a customer finds last month's WELCOME15 code in their inbox, stacks it on top, and the order ships at a loss. You go into the discount editor to exclude the bundle. There is no exclude button.
This guide covers what the native editor actually offers, the workarounds merchants use and why they leak, and the checkout-level approach OfferGuard takes: when a protected product is in the cart, every applied code gets removed, whoever the buyer is.
What Shopify's discount editor can do
Shopify discounts are built around what a discount applies to, not what it avoids.
Applies to: products or collections
For "Amount off products" discounts, you choose specific products or specific collections. Anything not on the list is untouched. That is an inclusion list, and it works well when the discount is narrow.
For "Amount off order" discounts, there is no product selector at all. The discount applies to the whole cart. A percentage off the order is exactly the kind of code that lands on your bundle.
Combinations
Every discount has a Combinations setting: it can combine with product discounts, order discounts, shipping discounts, or none of them. This controls whether two discounts stack with each other. It does not control whether a discount applies to a specific product.
Minimum requirements and eligibility
You can require a minimum purchase amount or quantity, and you can restrict a code to specific customer segments. Neither of these looks at what is in the cart line by line.
Automatic discounts
Automatic discounts follow the same rules as codes, with one difference: a customer can only have one automatic discount and one code applied at the same time in most configurations. That limits stacking but does nothing to protect a specific product.
Where the native approach breaks
The "everything except" collection
The standard workaround is to build a collection that contains every product except the ones you want to protect, then point each discount at that collection instead of "entire order."
It works on day one. Then someone adds a new product and forgets to add it to the collection. Or a product discount and an order discount are both live and the order-level one still applies to the protected item because order discounts don't take a product list. Or you have twelve codes running and each one needs the same collection swapped in by hand.
Merchants have been asking for a cleaner answer for years. The community thread on discount codes working for all customers instead of specific groups is one version of the problem; the eligibility looks right in the editor and the checkout does something else.
Codes shared outside your control
Even a perfectly configured code leaks. Honey, Capital One Shopping, and RetailMeNot scrape codes and offer them to everyone at checkout. If a code exists, someone will try it on your protected product. The editor can't distinguish "this customer earned the code" from "this customer's browser extension found the code."
The bundle problem specifically
Bundles and multipacks are priced as offers already. The whole point is that the discount is baked into the variant price. Any additional code is a second discount on an item that has no room for it. What you want is not "exclude this product from code X" but "while this product is in the cart, no code works at all."
Shopify has no setting for that.
What OfferGuard does: discount exclusivity
OfferGuard checkout rules have an action called discount exclusivity. You turn it on for a rule whose trigger is the product, variant, or selling plan you want to protect. From then on, when a matching item is in the cart, the checkout extension removes every applied discount code.
How the removal works
The rule is evaluated server-side on every checkout. If any active rule with exclusivity turned on has its trigger products in the cart, the decision response tells the checkout extension that codes are not allowed right now.
The extension then walks the list of applied codes and removes each one through Shopify's checkout API. This runs whenever the applied codes change, not once. A code entered before the check, after it, or halfway through the shipping step is removed as soon as both facts are known: a protected product is present and a code is applied.
After removal, the buyer sees a short notice. The default text is:
Discount codes can't be combined with the offer in your cart, so the code was removed.
You can replace that with your own message, up to 300 characters.
Two scopes: contains and only
Scope decides how strict the cart match is.
Contains is the default. If the protected product is anywhere in the cart, codes are stripped, even if the buyer also has unrelated items. This is the right setting for a loss-leader bundle: you don't want a code discounting the rest of the order either, because the buyer will simply add a cheap item to unlock it.
Only is stricter about when it fires. Every line in the cart must match the trigger. Add one unrelated product and the codes are left alone. Use this when you are happy for codes to apply to regular items and only want to stop a cart that consists entirely of the protected offer from getting a second discount.
It applies to everyone
Exclusivity is a cart-presence check, not an identity check. It fires for new customers, returning customers, logged-in and guest checkouts alike. That is deliberate: the bundle price is already the offer, so nobody should stack on it.
This is separate from the identity side of OfferGuard, which decides whether a buyer qualifies for a new-customer offer in the first place. The two combine well. A rule can say: first-time buyers get this bundle at the intro price, returning buyers pay full price, and nobody applies a code on top.
Fail-open by design
If a code removal fails for any reason, the failure is logged and checkout continues. Exclusivity never blocks a purchase. The worst case is the same as not having the feature: a code stays applied. The buyer is never stuck.
Exclusivity versus gated-code rejection
OfferGuard has a second, different mechanism that is easy to confuse with this one.
Discount-code rules protect specific codes, for example a Bubblehouse referral code that should only work for a new customer. When the wrong buyer enters one of those codes, the code is rejected and the buyer sees why. That is about who may use a particular code.
Discount exclusivity is about what is in the cart. It does not care which code was entered or who entered it. Protected product present means no codes, full stop.
Most stores use both: exclusivity on bundles and intro offers, gated-code rules on referral and loyalty codes. The wholesale code pattern on the solutions page is a case where gated-code rules do the work; the bundle case is where exclusivity does.
Setting it up
- Open Checkout Rules in OfferGuard and create a rule, or edit the one that already protects your offer.
- Set the trigger to the product, variant, or selling plan you want to protect. Variant titles and product handles work too if you'd rather match by name.
- Turn on discount exclusivity in the rule's actions. Pick "contains" unless you specifically want the stricter "only" behaviour.
- Optionally write the notice the buyer sees when a code is removed.
- Activate the rule.
No theme edits, no collection maintenance, no per-code configuration. Add a new bundle to the trigger and it is protected the same way.
When you still need the native tools
Exclusivity is a blunt instrument on purpose. If your requirement is "code A applies to shoes but not socks, code B applies to socks but not shoes," the native product and collection selectors are the right tool, and they handle it fine.
Use exclusivity when the rule is about the cart, not the code: this offer is final, nothing stacks on it.
For the rest of the offer-protection picture, the features page covers identity checks, new-customer offers, and lifetime limits, and the limit one per customer guide explains why Shopify's native per-customer setting needs help of its own.
Learn more about OfferGuard
More posts
Referral rewards as Shopify store credit: holds, clawbacks, and limits
Shopify store credit is a better payout for referral rewards than a discount code: it can't be shared, it only spends at your store, and it can be taken back when the referred order is refunded. Here is how it works natively and how OfferGuard's referral program uses it.
Shopify "high risk order": what the flag means and what to do
Shopify marks some orders as high risk of fraud. Here is what the analysis looks at, how to review a flagged order without losing good customers, and how to automate the tag, hold, or cancel so it doesn't sit in your queue.