Skip to content

Subscriptions and selling plans

Signal renders full subscription support on the product page: a purchase-type toggle, a frequency choice, prices that match what checkout will actually charge, and a savings badge derived from real numbers rather than read out of a plan name.

The control is rendered by the Buy buttons block and self-hides completely on products with no selling plans — so a catalogue with one subscription product doesn’t carry the UI on the other four hundred.

Shopify models subscriptions as selling plan groups containing selling plans, and only an app can create them. The theme renders what the app has set up; it cannot create a plan.

  1. Install a subscription app. Shopify’s own Shopify Subscriptions app is free and the simplest starting point. Third-party apps (Recharge, Loop, Seal) work identically as far as the theme is concerned.

  2. Create a selling plan group. In Shopify Subscriptions this is a subscription plan: give it a name (“Resupply”), add one or more delivery frequencies, and set the discount on each — a percentage, a fixed amount off, or a flat replacement price.

  3. Assign products to it. Either from the plan in the app, or from the product’s own page in admin. A product can belong to more than one group.

  4. Check the product page. The purchase-type toggle appears under the variant picker, inside the buy form. No theme setting needs switching on.

Two labels, both on the Buy buttons block under a Subscriptions header:

SettingTypeDefaultWhat it does
Delivery labelTextDeliveryThe heading above the control. “Delivery”, “Resupply”, “How often”
One-time labelTextOne timeThe label on the one-time purchase option

Everything else — the frequency names, the discounts, the groups — comes from the app. There is nothing in the theme to keep in sync, which is the point.

Two options side by side: One time (selected by default) and Subscribe. When a subscription discount exists, the Subscribe option carries a badge reading Save up to 15% — the best saving available across every plan on the product.

The frequency choice stays hidden until Subscribe is picked. That’s the whole reason the toggle exists. An earlier build flattened groups and plans into one row of pills, so a product in two groups with seven plans became eight equal-width boxes each labelled with Shopify’s auto-generated sentence (“Deliver every 2 months, 10% off”). The group names vanished and the row wrapped into a slab of text. Purchase type is one decision; frequency is a different one; they’re now presented as two.

Frequency: rows up to four, a select beyond

Section titled “Frequency: rows up to four, a select beyond”

Each frequency is a row with its name, its price under that plan, and a savings badge when there is one:

Every month $204.00 15% off
Every 2 months $216.00 10% off
Every 3 months $228.00 5% off

When the product belongs to more than one group and the groups are named, each group gets a small heading above its rows — so “Resupply” and “Seasonal” stay distinguishable.

The threshold is the total number of plans across all groups, so a product in three groups with two plans each (six total) gets the select.

Each row or option is labelled with the plan’s first option value when it has one — “Every month” — and falls back to the plan’s full name otherwise. Shopify’s generated plan names read like sentences (“Deliver every 1 month, 15% off”); using the option value keeps the label short and lets the price and the badge carry the rest.

Prices come from the allocation, not the variant

Section titled “Prices come from the allocation, not the variant”

This is the part worth understanding, because it’s where subscription themes usually go wrong.

A plan’s price adjustment can be a percentage, a fixed amount off, or an outright replacement price. You cannot reliably reconstruct the final price from the plan definition. What you can read is variant.selling_plan_allocations — Shopify’s own calculation of what each plan costs for each variant.

Signal emits that as a map inside the control:

{
"40123456789": { "501": 20400, "502": 21600 },
"40123456790": { "501": 20400, "502": 21600 }
}

Variant id → plan id → price in cents. Every price the page shows under a subscription is read from this map:

  • the price block figure
  • the struck-through figure beside it
  • the add-to-bag button label
  • the sticky bar price
  • each frequency row’s own price and savings badge

Before this, the page always rendered the one-time price, which meant a shopper on a 10% plan was quoted a figure the checkout did not charge. Now the number on screen is the number billed.

Plans can apply to some variants and not others, and the frequency labels are rendered for the first variant. So when the shopper changes variant, Signal re-labels every frequency row and every <option> from the same map. Where a variant has no allocation for a plan, the one-time price is used as the fallback rather than a stale figure from another variant.

Derived, never parsed:

percent off = round((variant price − allocation price) × 100 / variant price)

Comparing the two prices is the one reading that’s true for all three adjustment types. A badge reading 0% is hidden rather than rendered, and the Save up to badge on the Subscribe toggle is the maximum of these across every plan.

The strike-through follows what’s displayed

Section titled “The strike-through follows what’s displayed”

A subscription discount is itself worth striking through. The compare-at element compares against whatever price is currently shown, not against the variant price — so on a subscription the one-time price appears struck through beside the discounted one, and on a sale item under a plan the higher of compare-at and one-time is used.

When a product has requires_selling_plan set — Shopify’s “this product can only be sold as a subscription” flag — the control changes shape:

  • The One time option is not rendered at all. Offering it would let a shopper choose something that fails at add-to-cart.
  • The frequency choice is visible immediately, not hidden behind a toggle.
  • The first plan of the first group is pre-selected, so the form is valid from the first paint.

The visible controls are a UI over one hidden input:

<input type="hidden" name="selling_plan" value="" data-sub-input>

The chosen plan is always submitted with the order, whether the shopper adds to bag normally or through a native form post. “One time” is expressed as an empty value rather than a sentinel plan id, which is what lets the frequency rows and the select share one target.

Signal’s quick-add path (the [data-add] buttons on cards and in the drawer) reads the same input and only attaches selling_plan to the request when the value is non-empty.

The plan name is printed under the variant title on every subscription line, in all four places a line item appears:

WhereWhat it shows
Cart drawerPlan name under the variant, server-rendered and in the client-rendered fallback
Cart pagePlan name under the variant
Customer order pagePlan name under the line item
CheckoutShopify’s own subscription summary

All of them read item.selling_plan_allocation.selling_plan.name — so a shopper reviewing their bag can see which subscription they picked, not just that they picked one.