Why Your Shopify Cart Transform Function Stops Working the Moment a Customer Subscribes
A Cart Transform bundle that works perfectly on a one-time purchase can vanish the instant the same line carries a subscription option -- not because of a bug in your code, but because Shopify rejects the operation outright once a selling plan is attached to that line. Here's the exact operation-level breakdown of what Cart Transform can touch, which single operation actually requires Shopify Plus, and why installing a second app's transform can silently change what the first one sees.
If your Shopify Cart Transform bundle works for a one-time purchase but disappears the instant the same product is bought on a subscription, that isn't a bug you introduced -- it's documented, deliberate behavior. Shopify's Cart Transform Function API rejects the lineExpand, linesMerge, and lineUpdate operations outright on any cart line that has a selling plan attached, and the API is marked "Not supported" for the Subscription (Recurring Orders) surface entirely. There's no error in your function logs because your function never runs on that line -- Shopify filters it out before your code sees it.
That single fact explains most of the "my bundle randomly stops working" reports developers post about Cart Transform, and it's the first of three operation-level details that get flattened into vague "it's a Plus feature, good luck" advice almost everywhere else this gets covered.
What Cart Transform can actually do
Cart Transform is one of Shopify's checkout-time Functions, scaffolded with shopify app generate extension --template cart_transform and registered against a store through the cartTransformCreate mutation, which takes a functionHandle, an optional metafields payload for configuration, and a blockOnFailure flag. It runs on the cart.transform.run target and supports exactly three operations:
- lineExpand -- expands a single cart line into the individual components of a bundle, so a "Starter Kit" line can show the three products actually inside it.
- linesMerge -- the reverse: collapses several separate cart lines into one line that represents a bundle, useful when a customer builds a bundle by adding its pieces individually.
- lineUpdate -- overrides a line's price, title, or image without changing its structure, which is how "mystery box" pricing or a manual markdown gets applied at the cart level instead of the product level.
All three run entirely inside Shopify's backend, so the same logic applies consistently across the online store, checkout, and B2B -- you write it once and it's not something a theme or an app installed after yours can quietly override on the storefront.
Only one of the three operations needs Shopify Plus
This is the split almost nothing we read gets right. Shopify's own documentation states it twice, once under the operation list and once under the function target: "Only development stores or stores on a Shopify Plus plan can use apps with lineUpdate operations." lineExpand and linesMerge carry no such restriction -- a store on Basic or Grow can ship a fully functional bundle-expand-and-merge Cart Transform function today. The Plus requirement attaches specifically to overriding price, title, or image on a line, not to using Cart Transform at all.
The practical consequence: if a non-Plus merchant's actual need is "show my bundle's components in the cart" rather than "manually reprice a line," they don't need to budget for a Plus upgrade to get it. Conflating the two -- which most existing write-ups do by describing Cart Transform in general as Plus-only -- sends non-Plus merchants either to a more expensive plan they don't need, or away from a feature that would have solved their problem for free.
The function limit is per app, not per store
The second thing worth being precise about: Shopify's docs say a store "can install a maximum of one cart transform function per app on each store. If a store has cart transform functions from more than one app, all of them run." That's a per-app ceiling, not a store-wide one -- and it matters because merchant forums have genuinely confused themselves over this exact wording. A thread on Shopify's developer community starts from a merchant who read it as "one per store," concluded their bundle app and a second app both needing Cart Transform couldn't coexist, and only got the correction after someone quoted the doc back verbatim.
They can coexist, but "all of them run" is doing a lot of work in that sentence. When two apps' transforms both touch a cart, there's no documented guarantee of execution order between them. A developer on the same thread put the failure mode plainly: App B's transform "now sees a cart state different from what the buyer added, but App B has no way to know" what App A already changed. If you install a bundling app and, separately, a gift-wrapping or warranty app that also uses Cart Transform, and your line items start looking wrong in ways neither app's support team can reproduce, this ordering gap is the first thing to ask both vendors about -- not a coincidence, and not something either app is necessarily doing wrong on its own.
The workaround the same thread lands on, and the one we'd recommend to a client building custom functionality rather than relying on two third-party apps: consolidate everything -- bundling, pricing adjustment, add-ons -- into the one function you control, instead of stacking multiple apps that each register their own transform. It costs you more logic in one place, but it's the only way to guarantee your own execution order.
Where it can't reach at all: subscriptions and POS
Two more gaps are easy to miss because they don't throw errors, they just quietly exclude the line:
- Selling plans. As above, any line with a selling plan attached is excluded from
lineExpand,linesMerge, andlineUpdateat checkout, and the Subscription (Recurring Orders) surface itself is unsupported. If your bundle needs to survive a subscription purchase, Cart Transform is not the tool -- you need the bundle built at the product level (a parent product with a selling plan applied, using component products for the parts) rather than assembled at cart time. - POS. Cart Transform is only "partially supported" on POS, and full support requires the bundled variant's
ProductVariant.requiresComponentsfield to be set totrue. Skip that field and a bundle that displays correctly online can show as its full, unexpanded price at the register.
Neither of these is a Plus restriction -- they apply on every plan, and they're the reason "it works on the website but not in-store" and "it works until they subscribe" are two of the most common Cart Transform complaints once you look past the plan-gating question everyone asks first.
How to confirm this is actually what's happening
Before assuming your function has a bug, check whether the affected line even has a selling plan attached, since that's the condition that silently disqualifies it. A quick Admin GraphQL query against the cart or a recent order will tell you:
{
cart(id: "gid://shopify/Cart/CART_ID") {
lines(first: 20) {
nodes {
id
quantity
sellingPlanAllocation {
sellingPlan {
id
name
}
}
merchandise {
... on ProductVariant {
title
}
}
}
}
}
}
Any line where sellingPlanAllocation comes back non-null is a line your Cart Transform function will never be allowed to touch, regardless of what your cart.transform.run target logic does with it -- Shopify filters it before your function runs, so there's nothing to fix in your own code for that specific line. If you need to confirm the app-collision scenario instead, temporarily uninstall every app but your own that could plausibly register a Cart Transform function, reproduce the bad cart state, and reinstall them one at a time; since there's no documented ordering guarantee between two apps' transforms, isolating which one runs second is the only reliable way to find the interaction rather than reading either app's changelog.
Who should build one, and who shouldn't
If the actual need is a single, simple price override on non-subscription products -- a storewide markdown rule, say -- a Discount Function is usually the simpler build; it's designed for price adjustments and doesn't carry Cart Transform's structural line-item model or its app-collision risk. Cart Transform earns its complexity when you need the cart to actually restructure -- expanding a kit into its parts, or merging pieces a customer assembled themselves into one bundled line -- which a discount can't do.
If bundling needs to survive a subscription purchase, don't reach for Cart Transform at all; that's a product-and-selling-plan modeling problem, not a cart-time one. And if you're already running one app that uses Cart Transform and considering a second, get both vendors to confirm what happens when their transforms touch the same line before you install it, rather than finding out from a support ticket after a customer's cart shows the wrong price.
This is the kind of build where the constraints matter more than the code -- the function itself is a short GraphQL handler, but knowing which of these five limits actually applies to your catalog before you write it is what separates a bundle that works in testing from one that quietly breaks on the specific line configuration a real customer builds. It's exactly the kind of Shopify Functions work we do for clients moving off Scripts or building checkout extensibility from scratch -- if that's where you are, see what we build, or get in touch directly.