Blog

Shopify Subscribe & Save in Cart: Upsell One-Time Buyers

Subscribe and save subscription upsell inside a Shopify cart drawer

Short answer: To offer subscribe & save in a Shopify cart or cart drawer, the product needs a selling plan from a subscription app. An upgrade button on the cart line then posts the line number, quantity and selling_plan ID to /cart/change.js, and Shopify converts the line in place. Sending selling_plan: null switches it back to a one-time purchase.

Selling supplements or other refills? Your payment gateway decides whether subscription apps work at all. See the best Shopify apps for supplement brands for the gateway list and the full stack.

Key facts (re-checked October 11, 2026)

  • /cart/change.js accepts selling_plan. Shopify's Ajax Cart API reference says the endpoint changes the "quantity, properties, and selling_plan" of a line item that is already in the cart.
  • You must identify the line with line, not id. The same reference states that when you send selling_plan only the 1-based line index works, and that a missing quantity "is assumed to be 1".
  • null or an empty string removes the plan. That is the documented way to turn a subscription line back into a one-time purchase.
  • A subscription line carries a selling_plan_allocation. It appears in the cart JSON and in Liquid as line_item.selling_plan_allocation, with the plan name, the discounted price and the compare-at price.
  • Mixed carts are allowed. Shopify's subscription considerations confirm subscriptions can be bought together with one-time products, and that Shop Pay, Apple Pay, Google Pay and PayPal can buy subscriptions on Shopify Payments.
  • Not every subscription app has a cart widget. Seal Subscriptions' own documentation says its widget cannot be shown on the cart page "without a custom API solution" (source).

Disclosure: Oxify makes a cart drawer app that includes this feature. It is covered below alongside competitors, using only what each App Store listing or vendor help page states.

What is an in-cart subscription upsell, and how is it different from a product page widget?

A product page subscription widget asks the question before the add to cart: one-time or subscribe & save. An in-cart subscription upsell asks again after the add, on the cart line itself: "Upgrade to subscribe & save 10%". The shopper taps once, the line's price drops to the subscription price, and checkout creates a subscription contract instead of a one-off order.

The Shopify App Store labels this feature "Subscription upgrade" in its cart-customization feature taxonomy. Vendors use other names: Recharge documents a "cart upsell overlay" for converting one-time buyers, and Appstle calls its version a "Cart Widget". They all do the same thing underneath: change the selling plan on an existing cart line.

It is a different job from swapping a size or pack in the cart, which changes the variant, not the purchase option. That is covered in our guide to a variant upgrade in the cart drawer. For the wider playbook of cart offers, see the cart upsell strategy guide.

What has to be true before a cart line can become a subscription?

  1. A subscription app is installed and has created a selling plan group. Selling plans are created by apps, not by theme code. Shopify Subscriptions is the free first-party option; choosing between engines is covered in our Shopify subscription apps comparison.
  2. The exact variant in the cart belongs to that group. A plan attached to the 250 g bag does nothing for the 1 kg bag. In Liquid, check item.variant.selling_plan_allocations: if it is empty, do not render the button.
  3. The product is not subscription-only. If product.requires_selling_plan is true there is no one-time line to upgrade, and you must never offer a downgrade.
  4. Your payment setup can bill recurring orders. Shopify lists Shopify Payments, PayPal Express, Authorize.net, Adyen and Stripe as supported, subject to region, and warns that local, manual or non-card methods might not be available (source).

Can /cart/change.js convert an existing line item to a selling plan?

Yes. This is the part most answers get vague about, so here is exactly what Shopify documents for POST /cart/change.js:

ParameterWhat Shopify documentsWhat it means in practice
selling_plan: 183638"To set a specific selling plan for the line item."Converts a one-time line to a subscription, or switches frequency (30-day plan to 60-day plan).
selling_plan: null or ''"To remove any selling plan from the line item."Downgrades a subscription line to a one-time purchase.
lineWith selling_plan, "you can use only the line property for identifying the line item."Do not send id with a variant ID or line item key. Use the 1-based position.
quantity"If the quantity isn't specified, then it's assumed to be 1."Always resend the current quantity, or a line of 3 silently becomes 1.

Do not confuse this with variants. /cart/change.js cannot change a line's variant; a variant swap needs a remove plus an add. The selling plan is the one product-level attribute the endpoint can rewrite in place. The plan can also be set at add time by passing selling_plan in the /cart/add.js items array, which is what product page widgets do.

How does selling_plan_allocation appear in the cart JSON and in Liquid?

After the change, /cart.js returns the line with a selling_plan_allocation object. This trimmed example follows the structure in Shopify's reference (prices are in cents):

"selling_plan_allocation": {
  "price": 900,
  "compare_at_price": 1000,
  "per_delivery_price": 900,
  "selling_plan": {
    "id": 610435137,
    "name": "Delivery every 1 Week",
    "recurring_deliveries": true,
    "price_adjustments": [
      { "value_type": "percentage", "value": 10 }
    ]
  }
}

A one-time line has no selling_plan_allocation key, so if (item.selling_plan_allocation) is a reliable subscription test in JavaScript. In a theme, the equivalent objects are:

  • item.selling_plan_allocation: nil on one-time lines; otherwise gives .selling_plan.name, .price and .compare_at_price.
  • item.variant.selling_plan_allocations: every plan this variant can be sold on, each with its own price. This is the list to build the upgrade control from.
  • item.product.selling_plan_groups: the groups ("Subscribe & save") and their plans ("Every 30 days"), useful for a frequency dropdown.

Shopify's subscription UX guidelines ask themes to show selling_plan.name on the cart line because the same text is used at checkout, and say the cart price should match what the product page shows.

How do I add a subscribe & save upgrade button to my cart with code?

The snippet below is written against the documented API and standard Liquid objects. We have not run it on your theme: every theme re-renders its cart drawer differently, so test on a duplicate theme first. Place the Liquid inside the cart line loop of your cart drawer or cart template (in Dawn-family themes that is the for item in cart.items loop).

{%- liquid
  assign allocations = item.variant.selling_plan_allocations
  assign first_alloc = allocations | first
-%}
{%- if item.selling_plan_allocation == nil and allocations.size > 0 -%}
  <button type="button" class="js-sub-upgrade"
    data-line="{{ forloop.index }}"
    data-quantity="{{ item.quantity }}"
    data-plan="{{ first_alloc.selling_plan.id }}">
    Upgrade to {{ first_alloc.selling_plan.name }} for {{ first_alloc.price | money }}
  </button>
{%- elsif item.selling_plan_allocation and item.product.requires_selling_plan == false -%}
  <button type="button" class="js-sub-upgrade"
    data-line="{{ forloop.index }}"
    data-quantity="{{ item.quantity }}"
    data-plan="">
    Switch to one-time purchase
  </button>
{%- endif -%}
document.addEventListener('click', async (event) => {
  const btn = event.target.closest('.js-sub-upgrade');
  if (!btn) return;
  btn.disabled = true;

  const root = window.Shopify?.routes?.root || '/';
  const res = await fetch(root + 'cart/change.js', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      line: Number(btn.dataset.line),          // 1-based; "id" is not allowed here
      quantity: Number(btn.dataset.quantity),  // omit it and Shopify assumes 1
      selling_plan: btn.dataset.plan ? Number(btn.dataset.plan) : null
    })
  });

  if (!res.ok) { btn.disabled = false; return; }
  const cart = await res.json();               // full cart JSON
  // Re-render the drawer from this response or via your theme's own refresh.
  // Do not reuse old line numbers or line item keys after this point.
  document.dispatchEvent(new CustomEvent('cart:subscription-changed', { detail: cart }));
});

Shopify's reference adds that /cart/change.js changes one line item at a time and does not add an item that is not already in the cart; it returns a 400 error instead, which is why the snippet checks res.ok. The last step is theme-specific. Many themes re-render the drawer through the Section Rendering API by adding a sections parameter to the same request; check how your theme handles its own quantity buttons and copy that path. If editing Liquid is not for you, an app does the same call behind a settings toggle, covered below. For general drawer edits, see how to customize the Shopify cart drawer.

Worked example: what the shopper sees

Cart: 2 bags of coffee at $30.00, one-time. The variant is on a "Deliver every 30 days" plan with a 10% price adjustment.

StepRequestLine total
Beforenone2 × $30.00 = $60.00
Correct upgrade{ line: 1, quantity: 2, selling_plan: 183638 }2 × $27.00 = $54.00, compare-at $60.00
Upgrade with quantity forgotten{ line: 1, selling_plan: 183638 }1 × $27.00 = $27.00 (a bag vanished)
Downgrade{ line: 1, quantity: 2, selling_plan: null }2 × $30.00 = $60.00

Note the side effect on thresholds: the upgrade took $6.00 off the subtotal. If your free shipping bar was already full at $60, the upgrade just un-filled it. Decide whether subscription lines should qualify for free shipping on their own, and say so next to the button.

What breaks when a cart line switches to a subscription?

AreaWhat happensWhat to do
Line identityShopify only accepts the line index for this call, and carts can hold several lines with the same variant (different properties, discounts or plans). After the change, positions you cached may be wrong.Re-read the cart from the response every time. Never queue two changes against stored line numbers or keys.
Duplicate linesIf the cart already holds the same variant on the same plan, you now have two lines describing the same purchase. Shopify's reference does not document whether they merge.Test it on your store, and render whatever the response returns rather than assuming.
QuantityOmitted quantity defaults to 1.Always send the current quantity.
Quantity breaks and automatic discountsShopify says automatic discounts "can be applied to subscription orders", so a volume discount can land on top of the plan's 10%.Set each discount's purchase type deliberately (next section). Background: quantity breaks vs regular discounts.
Discount codesA code limited to one-time purchases stops applying to the upgraded line, so the total can go up after the shopper clicks "save".Show the new line price before the click, and re-render the discount summary after it.
Free shipping bar, gift thresholdsThe subtotal falls by the plan discount.Recalculate progress from the new cart JSON.
Express buttons in the drawerShopify states Shop Pay, Apple Pay, Google Pay and PayPal can buy subscriptions on Shopify Payments, but "Apple Pay support for subscriptions is limited to Visa and Mastercard" and customers can use only Shop Pay on some stores. With Authorize.net, "PayPal is the only wallet option available to customers".Place a test order with each wallet you show. See express checkout buttons in the cart drawer.
Prepaid plansA prepaid plan charges several deliveries up front, so the line price jumps instead of dropping. In the cart JSON, per_delivery_price differs from price.Do not offer prepaid plans behind a "save" button. Offer pay-per-delivery plans, or label the full charge.
BundlesShopify's help center states "Bundles aren't compatible with the Shopify Subscriptions app."Hide the button on bundle lines unless your subscription app documents support.
Old Scripts logicShopify Scripts stopped executing on June 30, 2026, so any Script that used to block codes on subscription carts no longer runs.Move that rule to discount settings or a Shopify Function.

Sources for the quoted rows: Shopify's considerations for subscription products and the Ajax Cart API reference.

How does Shopify handle a cart with both one-time and subscription items?

Shopify allows mixed carts and mixed orders. The customer picks one shipping method for the first delivery, and it covers the subscription and one-time items together. Recurring shipments then "revert to the default shipping method for subscriptions", and checkout shows the delivery frequency under each subscription line.

  • Shipping rate. Per Shopify's considerations page, if your subscription shipping profiles are set up in the Shopify admin, a single rate applies to the entire order. If they live in your subscription app, the app's rate is added to the rate the customer picks for the one-time items. After an upgrade, re-check the shipping line in a test order.
  • Payments. Gift cards used to pay for subscriptions apply only to the first payment, and automatic discounts you create in the Discounts section can be applied to subscription orders.
  • Checkout. Shopify adds a purchase option agreement for the customer to confirm.
  • Cart labels. Label each line "One-time" or with the plan name, so the shopper can see which items will renew.

Can a discount code stack on top of the subscribe & save discount?

Yes, unless you stop it. The subscription discount is a price adjustment inside the selling plan; discount codes and automatic discounts are a separate layer. Shopify's subscription discounts page documents two controls:

  • Purchase type. When you create a discount, choose whether it applies to subscription purchases, one-time purchases or both. Setting your welcome code to one-time only is the cleanest way to stop 10% subscribe & save plus 15% WELCOME15 on the same line.
  • Recurring payments for subscriptions. Under usage limits, this caps how many billing cycles a code keeps applying. Shopify calls it "the only way to automatically limit how many billing cycles a discount applies to". Leave it unlimited and a 15% code can ride on every renewal, because a snapshot of the discount is saved to the subscription contract.

In a mixed order the purchase type decides which lines a discount touches: a code limited to one-time purchases skips the subscription lines. One related rule from the same page: in a buy X get Y discount, the free "get Y" item must be a one-time purchase, so a free gift cannot itself be a subscription. For how classes of discounts combine in general, and for the code field itself, see our guide to the discount code box in the cart drawer.

Which cart drawer apps offer an in-cart subscription upgrade?

We checked each live App Store listing on October 11, 2026. "Lists the feature" means the listing itself shows "Subscription upgrade" in its feature list or names subscription upsells in a plan. It does not tell you which subscription engines each one supports; confirm that with the vendor before installing.

AppRating (reviews)Built for ShopifyWhat the listing says
Oxify Cart Drawer & Upsells (ours)4.9 (40)Yes"In-cart subscription upgrades" named in the plan features; "Subscription upgrade" in the feature list.
UpcartNot shownNot shownOn October 11, 2026 the Upcart listing showed "not currently available"; check the listing before installing. On September 19, 2026 it showed 4.7 (943) and "Subscription upgrade" in the feature list.
Amp Slide Cart Upsell Drawer5.0 (773)Yes"Subscription Upsells" named in plan features; "Subscription upgrade" in the feature list.
Kaching CartDrawer Cart Upsell5.0 (1,484)Yes"Subscription upgrade" in the feature list.
Rebuy Personalization Engine4.7 (853)No"Subscription upgrade" in the feature list.

Every listing that was open on October 11, 2026 shows it, so the feature alone will not decide between them. Price, drawer design and the rest of the upsell toolkit will; that comparison lives in our best Shopify cart drawer apps roundup, with head-to-heads in the Upcart alternative and Rebuy alternative posts.

Do subscription apps like Recharge, Appstle or Seal have their own cart widget?

Ratings below are from the live App Store listings on October 11, 2026.

Subscription appRating (reviews)In-cart upgrade, per vendor sources
Appstle Subscriptions5.0 (8,982), Built for ShopifyYes. Its Cart Widget help article describes a Subscribe option on eligible cart products, but says it "needs to be manually added to your theme's code" via Appstle support.
Recharge Subscriptions4.8 (3,133)Yes, as a "cart upsell overlay" that prompts one-time buyers to switch, documented in Recharge's help center. We could not open the full article to confirm plan or theme requirements.
Seal Subscriptions4.9 (3,135), Built for ShopifyNo native cart widget. Seal's FAQ says it cannot be displayed on the cart page "without a custom API solution".
Bold Subscriptions4.0 (381)Listing shows "Subscription upgrade" in its feature list; it does not say whether that is in the cart.
Skio (listed as "Skio, a Recharge Company")5.0 (243)Skio documents an "Upgrade & Save" checkout extension for Shopify Plus. That is at checkout, not in the cart.
Loop Subscriptions5.0 (781)Not stated on the listing. Ask the vendor.
Shopify Subscriptions3.7 (852)Not stated on the listing. It creates the selling plans; the cart control comes from your theme code or a cart app.

Because every Shopify subscription app stores its plans as native selling plans, the /cart/change.js method above works regardless of which engine created the plan. That is also why a cart drawer app can sit on top of a subscription app it has never heard of, in principle. In practice, test the pair before you rely on it.

How do I set it up without code in Oxify Cart Drawer & Upsells?

This part is first-party: it is our app, and the screenshot is our settings screen.

Subscription Upgrades settings in Oxify Cart Drawer showing the button text variable and Prevent Downgrades option
  1. Create the selling plan first in your subscription app and attach it to the specific variants you want upgradable.
  2. Open the Subscription Upgrades settings in the slide cart editor and switch the toggle on.
  3. Set the button text. The default is Upgrade to {{selling_plan_group_name}}; the variable fills in each product's own plan group name, so a coffee line and a filter line can show different offers.
  4. Decide on "Prevent Downgrades". When ticked, shoppers cannot switch a subscription line back to one-time inside the drawer. It suits subscription-first product pages. The setting carries a caution in the app for a reason: removing a choice at the last step can cost you the order, so watch checkout starts after enabling it.
  5. Style the button to be secondary to the checkout button, then place a test order and confirm the order shows the selling plan.

"In-cart subscription upgrades" is named in the plan features on the App Store listing, where pricing starts at $9.99/month; details are on the Oxify cart drawer page. Oxify reads the selling plans already attached to the product, so it works with every Shopify subscription app, including Recharge, Appstle, Seal, Loop, Skio and Shopify Subscriptions. We have not published app-by-app test results, so still place one test order with your own plans before going live.

Should I build it with code, a subscription app widget, or a cart drawer app?

ScenarioBest routeWhy
You use the theme's own drawer and have a developerTheme code with /cart/change.jsNo extra app, full control. You own the re-render and edge cases above.
You are on Appstle or Recharge and only want this one featureThe subscription app's own cart widget or overlayOne vendor, plans and widget always in sync. Appstle's needs a support-led theme install.
You are on Seal or Shopify SubscriptionsCart drawer app or theme codeNeither documents a native in-cart control.
You already want a drawer with a shipping bar, gifts and upsellsCart drawer appThe upgrade button is one toggle among features you are paying for anyway.
Products are subscription-onlyNothingThere is no one-time line to upgrade.

Where should the upgrade button go, and what should it say?

These are our opinions, not measured results. We have no sourced conversion-lift figure for this feature and you should be wary of anyone quoting one.

  • On the line, not in a banner. The offer is about one product, so it belongs under that product's title, where the plan name will later appear.
  • State the new price and the cadence. "Subscribe & save 10%: $27.00 every 30 days" beats "Upgrade". Add "skip or cancel anytime" only if your customer portal really allows it.
  • Offer it only on replenishable products. Coffee, supplements, pet food, skincare refills. A subscribe button on a sweater is noise.
  • Keep the drawer uncluttered. One subscription offer competes with every other widget; see adding upsells to the cart drawer and in-cart recommendations for how many offers a drawer can carry, and the cart drawer best practices checklist.
  • Measure two numbers, not one. Upgrade rate (upgraded lines ÷ eligible lines shown) and cancellations before the second order. A high first number with a high second number means the button is selling a discount, not a subscription.

Related: Timing the upgrade to how fast products run out? See the best Shopify apps for coffee and tea brands, best Shopify apps for pet stores and best Shopify apps for beauty and skincare brands.

FAQ

How do I add subscribe and save to the Shopify cart?

Install a subscription app and create a selling plan for the variants you want. Then add an upgrade control to each eligible cart line, either with theme code or a cart drawer app. On click, the control posts the line number, current quantity and selling plan ID to /cart/change.js, and Shopify converts that line to a subscription before checkout.

Does /cart/change.js accept a selling_plan parameter?

Yes. Shopify's Ajax Cart API reference says /cart/change.js changes the quantity, properties and selling_plan of an existing line item. Pass a selling plan ID to set a plan, or null or an empty string to remove it. With selling_plan you must identify the item by its 1-based line index, not by variant ID or line item key.

Why did my cart quantity drop to 1 after switching to a subscription?

The request left out quantity. Shopify documents that when you send selling_plan to /cart/change.js without a quantity, the quantity is assumed to be 1. Always read the line's current quantity and send it in the same request, so a line of three bags stays three bags after the upgrade.

How do I switch a subscription item back to a one-time purchase in the cart?

Send the same /cart/change.js request with selling_plan set to null or an empty string, plus the line index and quantity. Shopify removes the selling plan and the line returns to its one-time price. Do not offer this on products where requires_selling_plan is true, because those cannot be sold without a plan.

How can I tell in Liquid whether a cart item is a subscription?

Check item.selling_plan_allocation inside the cart items loop. It is nil for one-time purchases. For subscription lines it exposes selling_plan.name, the discounted price and compare_at_price. To find which plans a one-time line could be upgraded to, read item.variant.selling_plan_allocations or item.product.selling_plan_groups.

Can a cart contain both subscription and one-time products?

Yes. Shopify's help center confirms subscriptions can be purchased together with one-time products. The first shipment uses the shipping method the customer picks; later subscription shipments revert to your default subscription shipping method. Label every cart line clearly so shoppers can see which items will renew.

Can customers use a discount code on top of subscribe and save?

Yes, if the code's purchase type allows it. Every Shopify discount can be limited to one-time purchases, subscriptions or both. Set promotional codes to one-time only to prevent stacking, and use the "Recurring payments for subscriptions" usage limit to control how many renewals a subscription-eligible code keeps discounting.

Do Shop Pay and Apple Pay work with subscription items in the cart?

On Shopify Payments, Shopify says customers can use accelerated checkouts such as Shop Pay, Apple Pay, Google Pay or PayPal to buy subscriptions. Documented limits: Apple Pay support for subscriptions is restricted to Visa and Mastercard, and some stores can use only Shop Pay. Place a test order with each express button in your drawer after enabling upgrades.

Does Seal Subscriptions have a cart page widget?

Not natively. Seal's documentation states that its subscription widgets cannot be displayed on the cart page without a custom API solution; they appear on product pages and wherever add-to-cart buttons show. To offer an upgrade in the cart with Seal, use theme code against /cart/change.js or a cart drawer app that lists subscription upgrade.

Does Recharge offer a subscription upsell in the cart?

Recharge's help center documents a cart upsell overlay designed to convert one-time buyers into subscribers when they add an eligible product. We could not review its full requirements, so check with Recharge which plans and themes it supports. Appstle documents a similar Cart Widget that its support team installs into your theme code.

What does "Prevent Downgrades" do in Oxify Cart Drawer & Upsells?

It stops shoppers from converting a subscription line back to a one-time purchase inside the drawer. It fits stores whose product pages sell subscription-first. Because it removes a choice right before checkout, enable it cautiously and watch whether checkout starts fall. Oxify is our app, so treat this as first-party documentation rather than an independent review.

Do I need Shopify Plus to offer subscriptions in the cart drawer?

No. The Ajax Cart API and selling plans are available to Online Store themes on any plan, and the free Shopify Subscriptions app creates the plans. Plus only matters for checkout-stage offers, such as Skio's "Upgrade & Save" checkout extension, which Skio documents as exclusive to Shopify Plus merchants.

The bottom line

Shopify already supports the whole flow: one documented call, /cart/change.js with line, quantity and selling_plan, converts a one-time item to subscribe & save and back. The work is in the edges: resend the quantity, re-render from the response, set discount purchase types, avoid prepaid plans behind a "save" label, and test every express button.

If you would rather switch it on than build it, Oxify Cart Drawer & Upsells (our app; 4.9 from 40 reviews, Built for Shopify, 14-day free trial, verified October 11, 2026) includes in-cart subscription upgrades, as do the other open listings in the table above.

Not ready to install?

Get the free Store Optimization Checklist

Cart friction, upsell gaps, merchandising and trust fixes — the audit we run on every store. No call, no install.

Ask AI about Oxify App