AgentReady
By Dylan HuntAugust 27th, 2026ShopifyCheckoutFunctions

Subscription-Aware Bundle Discounts with Shopify Functions

Subscription-Aware Bundle Discounts with Shopify Functions

Bundle discounts are easy until subscriptions show up. Then every off-the-shelf tool hits the same wall: the discount applies to the first order and vanishes on renewal, or worse, applies to renewals it shouldn't. For a DTC supplement brand we work with (two hero products that genuinely work better together) the requirement was precise:

  • Buy product A and product B together: 10% off both.
  • If they're subscriptions, both on the same delivery cadence. A monthly A paired with a bi-weekly B is not a bundle, it's two subscriptions.
  • The discount must survive renewals indefinitely, without re-running any logic.
  • And a shopper assembling the same two products by coincidence shouldn't trigger the offer that the storefront bundle widget presents deliberately.

That last one is the interesting bit. The theme's bundle widget stamps a line-item property when it builds the pair. The discount function, a Shopify Function running cart.lines.discounts.generate.run inside Shopify's infrastructure, requires that stamp to match a live campaign before it pays out. No stamp, no discount, even for an identical cart. Campaigns can opt out of the gate when a merchant wants organic pairs to qualify; the point is that it's a decision, not an accident.

Why a Function and not a script or an app

Three properties you can't get any other way:

It runs in the money path, on Shopify's servers. No third-party API call at checkout, no latency budget spent on a vendor, nothing to go down on Black Friday.

Renewals are free. With appliesOnSubscription and an uncapped recurring cycle limit, Shopify copies the discount onto the subscription contract at checkout. Renewal orders carry it automatically. The function never re-executes, so there's nothing to break twelve months in.

Untrusted input stays untrusted. Anything a theme writes into the cart (line properties, attributes, a _bundlePrice hint) is attacker-controllable; a curious shopper can set it in DevTools. The function treats those as claims and validates every one against trusted, merchant-owned data (metafields the storefront can't write) before discounting a cent. Cap the discount server-side and the worst a tampered cart can do is receive the legitimate offer. We wrote that rule up separately: why we treat _bundlePrice as attacker input.

This is the kind of store-specific logic Shopify Functions are for, and the kind of build that belongs on a Checkout Suite Care Plan instead of a volume-priced upsell invoice.

The quantity trick

One subtle failure mode: quantity mismatches. Two of A and one of B is one bundle plus a stray, not two bundles. The function discounts min(units(A), units(B)) matched units on both sides, so inflating one line never over-claims the offer. It's two lines of code and it closes a hole that we've seen live on stores running far more expensive tooling.

What it replaced

The brand was quoted the usual volume-priced SaaS for this. What that fee actually pays for is operations, not the logic. Instead they own a ~200-line function with a fixture suite that runs against the compiled WebAssembly binary (the exact artifact Shopify executes) on every change, plus an admin for campaigns and attribution analytics. The recurring cost is a flat care plan, not a percentage of their growth.

Caffeine and Commerce builds custom discount functions and checkout suites for Shopify stores. If your bundle logic has a "but" in it that no app quite handles, that's our favorite kind of brief. Start a project.

Quick answers

Frequently asked questions

Why do subscription bundles fail in most upsell apps?
The discount applies to the first order and vanishes on renewal, or it applies to renewals it should not. Matching cadence, surviving renewals, and ignoring coincidental pairs all need logic a generic widget does not have.
Does the discount function re-run on every renewal?
No. With appliesOnSubscription and an uncapped recurring cycle limit, Shopify copies the discount onto the subscription contract at checkout. Renewal orders carry it automatically.
Can a shopper trigger the bundle by adding the two products themselves?
Only if the campaign allows it. The function requires a line-item stamp from the storefront widget to match a live campaign before it pays out, unless the merchant opts out of that gate.

Shopify Scripts retires June 30, 2026

Need your checkout customizations moved to Functions in time?

Caffeine and Commerce migrates discount, shipping, and payment logic from Scripts to Shopify Functions and Checkout Blocks, so nothing breaks when the Script Editor shuts off. We can do the whole build for you, or hand you a clean plan.

Comments

Every comment here comes from a verified email. Write yours, confirm from your inbox, and it's live.

Loading comments…

Leave a comment

ShareXLinkedInFacebook

Written by Dylan Hunt, Founder, AgentReady. AgentReady measures what AI says about Shopify stores and helps teams improve the answer. See plans.