ShopifyEcommerceAutomation

One-Tap Subscription Switching on Shopify Checkout

Andrew BeauchampAugust 18, 20267 min read

Every one-time item in this checkout now carries a one-tap offer: "Switch to Subscription & Save $12.00 on every order." Tap it and the line swaps to the subscription variant in place, on the native Shopify Plus checkout page, with the savings computed from live prices. No redirect, no cart rebuild, no second checkout. We built it for a subscription-heavy DTC brand on Shopify Plus after trying to get the same behavior out of Rebuy, including Rebuy paired with a subscription app, and coming up empty.

In its first three days with attribution running, 4 of the 9 orders that took one of our checkout cross-sell offers also used this card to switch that item to a subscription. The cross-sell adds the one-time variant on purpose. This card is the subscription on-ramp right behind it. A generic, MIT-licensed version of the whole build is on GitHub: shopify-checkout-cross-sell.

Why We Couldn't Buy This

The catch is the store's architecture. This brand has no Shopify selling plans and no subscription app. "Subscription" is just a variant on the product: a SKU flag, with the actual recurring billing handled by an external backend the brand has run for years. Shopify sells the first order; the backend bills the rest.

That setup is more common than you'd think on brands that predate Shopify's subscription APIs, and it breaks every off-the-shelf upsell tool we tried. Upsell apps that offer a "subscription upgrade" assume selling plans exist. Ask them to swap a line to a different variant that merely represents a subscription and there's nothing to configure, because to the app it's just another variant with no special meaning. We didn't guess at this: Rebuy's dev team told us directly that they couldn't support it, because their subscription features only work with selling plans. And to be clear, Rebuy pairs with subscription apps just fine. Those integrations work when the subscription runs through Shopify. This one doesn't. The recurring billing never touches Shopify at all, so there was nothing for Rebuy or any subscription app to hook into. The other blocker is placement: the checkout page itself is closed to theme code and app scripts. If you want an offer on checkout, it has to be a checkout UI extension.

So we wrote the extension ourselves. It's a few hundred lines, built with Claude Code inside the brand's repo the same way we build Shopify landing pages, and it does exactly what the store needs instead of what an app vendor assumed every store needs.

How the Swap Works

The card is a checkout UI extension on the purchase.checkout.cart-line-item.render-after target, so it renders under each line item in the order summary. For every one-time line of a configured product, it fetches that product's variants through the Storefront API and looks for the matching subscription variant. Matching means the swap keeps what the customer already chose: same size, same color, every option except the purchase-frequency ones. Among the candidates it prefers the product's default billing cadence, so the flagship product's one-time line offers its every-3-weeks subscription, not some odd cadence that happens to sort first.

The savings number is real, not copywriting. It's the live one-time price minus the live subscription price for that exact variant, the same math as the "Subscribe & Save" chip on the brand's product pages. If the prices change in the admin, the button text changes with them, no redeploy.

The tap itself is one API call:

const result = await shopify.applyCartLinesChange({
  type: 'updateCartLine',
  id: line.id,
  merchandiseId: offer.merchandiseId,       // the subscription variant
  attributes: subSwitchAttributes(line.attributes),
});
BEFOREPerformance CrewOne-Time · Size L$42.00Switch to Subscription& Save $12.00 on every orderone tapupdateCartLineAFTERPerformance CrewSubscription · Size L$30.00$42.00Ships TodayShips Every 3 Weeks · Future Billingappears automatically after the swap_sub_switch: "checkout"stamped on the line

The swap, rendered generically. One tap replaces the variant in place; the messaging block and the attribution stamp follow on their own.

updateCartLine swaps the variant in place. The customer sees their item flip to the subscription version with the new price, right there in the summary. And because our subscription messaging card watches every line, the moment the swap lands the same line grows the "Ships Today / Ships Every 3 Weeks / Future Billing" block automatically. No coordination code between the two features; one reacts to the state the other creates.

The Details That Keep It Honest

A few things we'd never have gotten from a configurable app, because they came out of reading this store's actual data:

Detection has to handle two conventions. Most of the catalog marks subscriptions with the word "Subscription" in the variant options. One product line carries no wording at all and only an -AS SKU suffix. The detector checks both, and explicit "one time" wording always wins. Get this wrong and the card offers a switch to a variant that was already a subscription.

Respect the checkout's rules. Some checkout configurations and accelerated checkout flows forbid line updates. The extension checks instructions.lines.canUpdateCartLine and renders nothing when a swap wouldn't be allowed. A button that errors on tap is worse than no button.

Don't destroy your own analytics. updateCartLine replaces the line's whole attribute set, so a naive swap would wipe the attribution stamps our cross-sell writes when it adds an item. The swap carries the existing attributes through and appends its own _sub_switch stamp. That's how we can say "4 of 9 cross-sold orders switched to subscription" instead of guessing: every order line tells us which surface added it and whether the switch card converted it.

We wrote more about that attribution layer in the companion post on the cross-sell system itself. Short version: every add and every switch is stamped with hidden line properties (surface, anchor product, slot, size behavior), which gives us per-slot, per-product, per-surface reporting that we couldn't get out of the app we replaced.

The Same Switch, Now in the Cart Too

Once the checkout version proved itself, we ported the pattern back into the theme's cart drawer, where the swap runs through the Ajax cart API instead of a checkout extension. Same stamp convention with a different surface value, so the weekly report (compiled by the same AI systems that run our client reporting) shows cart switches and checkout switches as separate rows. Two surfaces, one measurement system.

The whole build, both surfaces, was done with Claude Code working inside the brand's repos: reading the theme's existing logic, matching its conventions, and verifying the swaps against the live store before anything shipped. This is the kind of feature that looks impossible when you're shopping the app store with a nonstandard stack. It's a normal engineering task when you treat the checkout as a platform.

The generic version of this build is open source, MIT licensed: github.com/BeauchampAndrew/shopify-checkout-cross-sell has the switch card, the cross-sell block, and the reporting script, with one config file to adapt to your catalog.

Running a subscription program on Shopify with an architecture the app store doesn't recognize? That's most of our favorite projects. Talk to us.

Want results like these for your brand?

We help ecommerce brands build email and SMS programs that drive real revenue. Let's talk about what we can do for you.