Shopify Builds>Capabilities>Subscription purchase options on the product page

Subscription Purchase Options on the Shopify Product Page

Definition

Subscription purchase options rendered on a Shopify product page: the selling-plan selector, the subscribe-or-one-time choice, the per-frequency price beside it, and the placement and styling work a subscription app's widget needs once it lands in the buy box. The plans, billing and customer portal belong to the platform and the app; the theme owns where the option sits, what price it shows and what the add-to-cart request carries.

The Evidence

  • Evidence strength: 9 live, Deploi-attributed builds across 3 Shopify storefronts.
  • Split by storefront: Three Ships 6, LUS Brands 2, Nudestix 1.
  • Split by attribution: 5 Deploi-authored, 4 modifications of vendor theme code — disclosed as such on each build page.
  • Evidence quality: five carry code plus client working records. Four are documented from the code; no client-side record of the decision survives.
  • Confidence: 3 strong, 6 moderate.
  • Surfaces: product pages first; also the cart drawer, a subscription landing page and the layout.
  • Status: Live, verified 2026-09-07.
  • Five of the 9 have published build pages.

Nine is a modest base, and it leans on one storefront: depth on Three Ships, where three vendors share one buy box, and one deliberate decision on LUS Brands. The scope note under What This Rests On says which four of the nine are product-page subscription controls and which five are adjacent work.

How It's Actually Built

Everything here follows from one platform fact. A selling plan is a Shopify object — the app defines it, Liquid reads it through product.selling_plan_groups and variant.selling_plan_allocations, and a cart line becomes a subscription only if the add request carries a selling_plan id. The app owns the plan, the billing and the portal. The theme decides what the shopper sees and what the form sends. Three shapes recur.

Placement: getting the widget into the buy box. Shopify app blocks are positioned as top-level section blocks, so an app's subscribe control lands where the block list puts it — never inside the product form, never between the variant picker and the button. On Three Ships, the vendor's product section was extended so that a pre-pass over section.blocks captures the Recharge app block's rendered HTML once, the main block loop skips it, and the form block re-emits that HTML inside itself, into a CSS grid area laid out as variants, quantity, subscription, add to cart. One render, placed where the merchant needs it. On LUS Brands the theme owns the section outright, so placement is a block decision: the buy_buttons block of our own product section mounts the Skio plan picker with the product and selected variant serialized as escaped JSON attributes, and a selector wires the app's price into the theme's own price node. A client working session fixed that position — between the size and formulation selectors — the slot an app block appended to the end of a section cannot reach.

Remediation: what happens after the widget mounts. A subscription app's widget re-renders on variant change and, in Recharge's case, inside a shadow root the theme's stylesheet can't cross. The second shape is observers. In Three Ships' theme script, a MutationObserver on .product__submit__buttons moves the Yotpo subscription add-to-cart control to sit directly after the quantity selector, the slot the buy box was designed around. On product templates, styleRechargeShadow() reads the widget's shadowRoot and appends a <style> with its own id — guarded by a lookup for that id, so the routine that watches for changes finds nothing to do the second time. Each correction checks its own work before acting; convergence is the design.

The theme's own selling-plan control, off the product page. The third shape is the theme rendering a plan choice itself. On Three Ships' subscription landing page, a <bundle-grid-card> element renders one frequency button per entry in variant.selling_plan_allocations, and its two-step button posts id, quantity and selling_plan to the cart add route — the request a stock quick-buy can't make. A <bundle-grid> wrapper keeps no count of its own: after every add it fetches /cart.js and sums only the lines that carry a selling_plan_allocation and belong to the grid's allow-list, then moves a merchandiser-defined tier bar. The cart is the state. The drawer on that storefront is Rebuy's, so the card calls the drawer's own refetch after a successful add and the two agree.

The Buy-vs-Build Position

The corpus's own verdict, in substance: nobody built a subscription engine. Every storefront bought its plans, and on every one the theme still had to place the widget, render the price and carry the plan through the add.

That's a clean split and we'd defend it. Selling plans, billing, dunning and the customer portal are a product category with real vendors in it — Recharge and Skio both appear across these storefronts — and none of these builds tries to be one. What a subscription app can't know is your buy box: where the option should sit commercially, which price node the page already renders, what your own grid card should send. That's theme work, it lands after the subscription fee, and it's the part a quote usually forgets. The decision framework is in Product subscriptions: buy or build?

The Builds

Three Ships — Palo Alto 5.8.0 by Presidio Creative

LUS Brands — Dawn 15.4.1

Two more count toward the evidence above and have no published page yet; the other two are cart-side records — one already covered by a published cart page, one too slight to carry a page of its own.

What This Rests On

  • Selling plansselling_plan_groups, selling_plan_allocations and the selling_plan id on the add request; the platform object the whole capability reads.
  • Liquid section schema and settings — the block that hosts a picker, the pre-pass that captures one, and the tiers a merchandiser edits.
  • Section Rendering API — the drawer and buy-box refreshes a subscription line has to survive.
  • Cart AJAX API/cart/add.js carrying a plan, and /cart.js read back as the only truth about what's in the cart.
  • Theme app extension blocks — the sanctioned mount for a vendor widget, and the positioning rule the placement work exists to get around.
  • Custom elements — the grid card and its wrapper, and the shadow root a vendor widget renders into.
  • Metafields — card copy and buy-box toggles on the host sections.
  • Customer accounts — the authenticated customer object the one segmentation build reads at render time.

What it does not rest on: a subscription engine of ours. No build here defines a selling plan, bills a renewal or serves a customer portal. That's the app's, on every storefront.

And a note on how the nine were drawn. The capability was indexed on the word as much as the mechanism. Five of the nine are adjacent work rather than a subscription control: one resolves loyalty tier and email consent for analytics, two are cart fragments, one is an AJAX add-to-cart with a Section-Rendering drawer refresh, and one is a landing-page opt-in form. Not one of the five is a selling-plan selector, and five of nine is most of this page's evidence. We count them because the graph does, and we'd rather say so than let nine imply nine subscription pickers. The product-page subscription work proper is the other four: the two block systems, the remediation layer and the grid.

What Varies by Storefront

Who owns the product section decides everything.

Three Ships runs Palo Alto 5.8.0 by Presidio Creative, a paid premium theme, with a compiled runtime and the largest installed stack of the three: Recharge for plans, Yotpo for reviews and a subscription control of its own, Rebuy for the cart. The theme doesn't own the buy box's DOM, so placement is done by capture — render the app block once, echo it into the grid slot — and everything after mount is done by observer. Most of the six is what coexistence looks like when three vendors share one form. The one place the theme renders a plan choice itself is a landing page, in a card of its own.

LUS Brands runs Dawn 15.4.1, and the product section is ours. That changes the shape: the plan picker is mounted by a block in a section we control, its position is a block-order decision, and its price is wired into a price node we render. No observer, because nothing arrives after paint that the section didn't put there. Two builds, and the second is cart-side.

Nudestix runs Dawn 6.0.2 and contributes one build to the count, cart-side rather than a product-page control. This storefront carries no product-page subscription work of ours, and the count shouldn't be read as if it did.

Where This Gets Hard

Position is a contract with the platform, and it's the wrong one. App blocks are top-level, and every placement technique here exists to put a widget somewhere the block model won't. Capturing rendered HTML and re-emitting it means recognizing a vendor's widget in its own output — a contract with a third party's markup that nobody signed. Mounting a picker inside your own section is cleaner, and only works if the section is yours.

A correction after mount is a standing commitment. The widget re-renders on variant change; the theme's fix has to run again, and it has to be safe against its own change. That's theme code standing in for a setting the vendor doesn't offer, for as long as the app is installed. The client's own task register carries this work as maintenance rather than as a project, and that is the right ledger.

The cart, not the page, knows whether a line is a subscription. A one-time add and a plan add of the same variant are different requests, and only the cart tells you which one landed. The grid build refuses to keep its own count for that reason — it reads /cart.js after every add. Anything that trusts the page's memory over the cart's is right for exactly one session state.

And two cases where we'd say don't build. If the app's own widget in the app's own position is commercially acceptable, take it; the placement work is what you pay for when position matters, and sometimes it doesn't. And if more than one purchase-option app is installed, choose one before building around either — the builds on this page each assume one source of plans, and the theme work multiplies with the pairs, not the purchases.

Related Capabilities

Answers to Help You Make Smart Decisions

Why can't my subscription widget sit between the variant picker and add to cart?

Because Shopify app blocks are positioned as top-level blocks of a section, not inside the product form, so an app's widget lands wherever the block list puts it. Getting it between the picker and the button means the theme has to either render the app block once and echo its HTML into a slot inside the form, or mount the app's picker itself from a section the theme owns.

Does adding a subscription product from a grid keep the subscription?

Only if the add request carries a selling_plan id. A stock quick-buy sends the variant alone, so Shopify records a one-time purchase whatever plans the product offers. The grid card here renders one button per selling_plan_allocations entry and posts id, quantity and selling_plan together — that's the whole difference between a subscription line and a one-time one.

Do I need to build a subscription engine on Shopify?

No, and none of these nine builds did. Plans, billing, dunning and the customer portal are what a subscription app is for, and every storefront here bought one. What you'll still build is theme-side: where the option sits in your buy box, which price node it updates, what your own cards send, and the corrections a vendor's widget needs to sit properly in a page it has never seen.

Ready to Put the Subscribe Option Where It Sells?

Your subscription app owns the plan. Your theme owns the moment a shopper chooses it. Contact us today and we'll read your buy box and put the option where it belongs.

Builds

Rebuilt 2026 PDP section with merchandiser-composable blocks and per-product ATC color

A custom product section for LUS Brands that declares 26 block types beyond Dawn's set, resolves title and subtitle copy per variant, and paints the add-to-cart button from a product metafield.

LUS Brands

Swell VIP tier and email-subscriber segmentation resolved in Liquid

Two Liquid variables in `theme.liquid`: `loyalty_status`, resolved from the tags the loyalty vendor writes onto the customer record, and `subscriber_status`, resolved from email-marketing consent — both assigned before `content_for_header` and emitted only for signed-in customers.

Three Ships

Configurable PDP block system on the Palo Alto theme (Recharge widget placement, size charts, siblings, quick view)

Merchandiser-orderable buy-box blocks on Three Ships' licensed theme, with the subscription app block captured and re-emitted inside the buy box, plus metafield-driven size charts, a product-family switcher and a sticky add-to-cart bar.

Three Ships

Subscription bundle grid with tiered unlock progress

Two custom elements on the subscription landing page: a card whose two-step button reveals a frequency choice and then posts the variant with that `selling_plan` to the cart, and a wrapper that fetches the live cart after every add and drives a merchandiser-defined tier bar from what it finds there.

Three Ships

DOM and shadow-DOM remediation layer for Yotpo, Rebuy and Recharge widgets

Corrections applied to markup the theme didn't write: `MutationObserver` routines that re-level a review widget's headings, demote headings inside a cart drawer, re-anchor a subscription add-to-cart control, and append a stylesheet inside a subscription widget's shadow root — every one re-entrant, so the fix converges instead of looping.

Three Ships