Shopify growth

Affiliate Links vs. Discount Codes: Why They Collide on Shopify

A customer arrives through an affiliate’s referral link, then applies your popup discount code. Now you pay commission and a discount on the same order — or the affiliate gets nothing. Here is why Shopify can’t stop this natively, and the pattern that actually works.

This question comes up constantly in merchant communities, and the scenario is always the same. You run an affiliate program — GoAffPro, UpPromote, ReferralCandy, take your pick — where partners share referral links, not codes. Separately, your marketing hands out discount codes: a 10% welcome popup, codes in email flows, codes printed on packaging inserts.

Then a customer clicks an affiliate’s link on Monday, browses, and on Wednesday comes back through your Instagram bio, gets the popup code, and checks out. Two bad outcomes, depending on how your affiliate app attributes the order:

The merchant’s question is reasonable: can Shopify block discount codes for customers who arrived through an affiliate link? The short answer is no — not with any native setting. But there is a working architecture, and a newer piece of the Discount Function API makes the customer experience much cleaner than it used to be.

Why Shopify can’t do this natively

Discount eligibility in Shopify is conditioned on things like customer segments, products, collections, order minimums, and combinations with other discounts. What it never sees is how the customer arrived.

The referral source lives somewhere else entirely — in your affiliate app’s tracking script and its cookie. Discount codes are validated when they’re entered at checkout, and by that point the checkout has no idea the customer clicked a referral link three days ago. There is no eligibility rule, in any plan, that reads “came from affiliate X.”

So the fix has to bridge two systems: get the referral state into the cart, and then let checkout logic act on it.

The fixes that look right but backfire

Before the working pattern, it’s worth knowing why the common workarounds create new problems:

Setting commission to 0% when a code is used. This is the most common affiliate-app setting people reach for. It “solves” the double-payment by quietly transferring the cost to your affiliate. The first time a partner notices they drove a sale and earned nothing, they stop promoting you. You’ve fixed accounting and broken a relationship.

Blocking checkout with a Cart Validation Function. Technically you can stop the order. But the customer sees a generic error with no explanation tied to their code. It reads as “the store is broken,” not as “this code doesn’t apply to you.” Expect support tickets and abandoned carts.

Zeroing the discount with an automatic-discount Function. You can attach a Discount Function that returns no discount when referral state is present. The code gets accepted — and the total doesn’t move. From the customer’s seat, the store took their code and gave them nothing. That’s arguably the worst ending: the code was validated, so they blame you, and there’s no message to explain why.

The root problem in all three: the customer is never told, clearly, “this code can’t be used on orders that came through a referral link” — and the affiliate’s credit is handled as an afterthought.

The working architecture

The pattern that holds up has three parts: capture the referral state into the cart, run a Discount Function that reads it, and reject the code with a real message.

1. Capture referral state as a cart attribute

Cart attributes are key-value pairs that travel with the cart into checkout — and, critically, Discount Functions can read them. A small theme script does the work:

Now the referral state exists where checkout logic can see it.

2. Attach a Discount Function to an automatic discount

Code discounts are validated before Functions get a say — which is why you can’t “edit” someone else’s code. The workaround: your Function runs on an automatic discount you create yourself (via discountAutomaticAppCreate). That automatic discount doesn’t need to reduce anything; its job is to give your Function a seat at checkout.

The Function’s input query reads two things: the cart attribute you set in step 1, and enteredDiscountCodes — the codes the customer has typed but that haven’t been applied yet.

3. Reject the code with enteredDiscountCodesReject

If the referral attribute says “affiliate” and the customer has entered one of your marketing codes, the Function returns an enteredDiscountCodesReject operation naming those codes, with a message:

{
  "enteredDiscountCodesReject": {
    "codes": [{ "code": "WELCOME10" }],
    "message": "WELCOME10 can't be combined with a referral order — your referral already gives you the best deal."
  }
}

The customer sees a specific, human explanation at checkout instead of a silent failure. The affiliate keeps their commission because referral tracking was never touched. Nobody got double-paid.

Three constraints, straight from Shopify’s documentation:

  1. Only Functions backed by an automatic discount can reject codes. You can’t reject codes from a code-discount Function.
  2. You can only reject codes that are still “rejectable” — entered but not yet applied. This is why the Function needs to act on enteredDiscountCodes at entry time, not retroactively.
  3. The rejection message displays in checkout and should be localized if you sell in multiple languages.

The enteredDiscountCodesReject operation arrived with the 2026-01 API version, which is why older threads about this problem end in “you can’t do it cleanly.” You can now.

One decision will bite you if you skip it: affiliate referral cookies persist for days or weeks — whatever window you configured in the affiliate app. If your theme script writes the referral attribute and it lingers, a customer who clicked a partner’s link three weeks ago gets their birthday email code rejected and has no idea why.

Decide deliberately:

Whichever you pick, write it down and tell your affiliates. A partner who knows “codes don’t stack on referral orders” promotes with confidence; one who discovers it from a confused customer email does not.

Test these before you call it done

Where an agent fits

The detection-and-rejection logic above is a one-time build. The ongoing work is different: watching for orders where a code and a referral both fired, spotting new conflict patterns (a new affiliate partner, a new code campaign), and flagging anomalies before they become a month-end commission dispute.

That monitoring loop is exactly the kind of thing a store operations agent can own: it watches orders and attribution, surfaces conflicts with the evidence attached, and proposes the fix — while the rule itself (“never stack these”) stays a decision you make once, as the owner.


Verified against Shopify’s Discount Function documentation as of July 2026. The enteredDiscountCodesReject operation requires API version 2026-01 or later.