Selling Plans
Selling plans expose subscription / recurring-purchase data on a product in a standard, theme-friendly shape. Themes that render a “Subscribe & save” selector or display per-delivery pricing pick up the data automatically on LaunchMyStore — no theme changes required versus a Liquid-compatible theme. The data is hydrated from the backend at render time onto theproduct.selling_plan_groups and variant.selling_plan_allocations
drops. The selected plan id flows through the cart, checkout, and order
placement to be persisted on the resulting order line.
How it flows end-to-end
Schema
product.selling_plan_groups is an array of groups. Each group has one
or more selling plans. Each plan describes a recurring schedule and
checkout charge.
variant.selling_plan_allocations carries the
per-variant resolved pricing for each plan in the groups above:
selling_plan_groups (definition) and
selling_plan_allocations (per-variant pricing) follows the canonical
Liquid selling-plan model — group-level definitions plus per-variant
resolved pricing — so theme code that reads either works as expected.
Delivery policies (deliver daily, bill weekly)
A plan can deliver more often than it bills — the classic prepaid pattern: a bakery box delivered every morning, charged once a week. This is modelled with two policies on the plan:billing_policy— how often the customer is charged (e.g. every 7 days).delivery_policy— how often goods are delivered (e.g. every 1 day).
deliveries_per_cycle.
price is the amount
charged per billing cycle (per-delivery price × deliveries per
cycle, after any plan discount), while per_delivery_price stays the
single-delivery amount:
selling_plan_allocation.per_delivery_price divides it back down for
display. Recurring billing (Stripe) charges the same per-cycle amount on
the billing cadence.
Theme usage
The canonical pattern: gate the selector onproduct.selling_plan_groups.size > 0, then offer the merchant’s plans
alongside the one-time purchase option.
Subscribe & save selector
<input name="selling_plan"> radio value flows into the cart line
as selected_selling_plan_id — the Liquid form serializer reads it
automatically when the form is submitted.
Per-delivery price for the selected variant
variant.selling_plan_allocations is the structured way to read the
price applied when a customer subscribes to a particular plan on that
variant.
Showing total checkout charge
Some plans charge less than the full price at checkout (deferred billing). Readallocation.checkout_charge_amount for what gets
charged today and allocation.remaining_balance_charge_amount for what
will be billed on subsequent renewals.
Detecting subscription-only products
A product that requires a selling plan (no one-time purchase option) exposesrequires_selling_plan: true. The MVP does not yet flip
this flag — every subscription-enabled product currently allows both
one-time and recurring purchase.
Cart-line context
In cart and order templates, a line that has a selected selling plan carries the plan reference atline.selling_plan_allocation:
Dynamic price calculation
There are two ways a plan can affect price, and themes should be prepared for both:1. Allocation override
The simplest case: the merchant sets a fixed per-delivery price for the plan that’s lower than the variant price. The platform pre-computes this and surfaces it asallocation.per_delivery_price. No theme
math is required — render the allocation price directly.
2. Per-billing-cycle adjustments
Some plans charge a different amount on the first delivery vs. subsequent renewals (e.g. “first month free”, “$10 off your second delivery”). These are surfaced asallocation.price_adjustments[]:
Checkout integration
When the customer adds the product to cart with a selling plan selected, the cart API persists theselected_selling_plan_id on the
line. From there the checkout receives the plan id alongside the
variant and quantity, and order placement writes the plan id onto the
resulting order line.
The checkout UI surfaces the plan name beside the line item the same as
any Liquid-rendered checkout — no theme-side wiring needed.
Order-placement persistence
Once the order is placed, the platform records:- The selling plan group definition (name, position, the products it is offered on).
- Each selling plan (name, billing and delivery cadence, recurring
flag, trial days, checkout charge, category —
subscription,prepaid, ortry_before_you_buy). - Any per-cycle price adjustments.
- The selected plan on each order line — the authoritative record of which plan was chosen, used to find renewals due.
Field reference
Selling plan group (product.selling_plan_groups[])
| Field | Type | Description |
|---|---|---|
id | string | Unique group id. Format: spg-{productId} for MVP. |
name | string | Group label (e.g. "Subscribe and save"). |
app_id | string | null | App that registered the group, or null for merchant-set. Always null in MVP. |
options | array | Array of option definitions — name, position, and possible values. |
selling_plans | array | Array of plans in this group. |
selling_plan_selected | bool | true if a plan in this group is the active selection. |
Selling plan (group.selling_plans[])
| Field | Type | Description |
|---|---|---|
id | string | Unique plan id. |
name | string | Plan label. |
description | string | Optional plan description (used for trial info, etc.). |
recurring_deliveries | bool | true if this plan delivers on a schedule. |
selected | bool | true if this plan is the active selection. |
billing_policy | object | How often the customer is charged ({ interval, interval_count }). |
delivery_policy | object | How often goods are delivered ({ interval, interval_count }). Same as billing_policy unless the merchant set a delivery frequency. |
deliveries_per_cycle | number | Deliveries covered by one charge (billing ÷ delivery). 1 for ordinary subscriptions. |
options | array | Array of option values selected for this plan. |
price_adjustments | array | Discount adjustments applied to recurring deliveries. |
checkout_charge | object | How much is charged at checkout ({ value, value_type }). |
Variant allocation (variant.selling_plan_allocations[])
| Field | Type | Description |
|---|---|---|
selling_plan | object | Reference to the plan this allocation belongs to ({ id, name }). |
selling_plan_group_id | string | Parent group id. |
price | string | Amount charged per billing cycle for this variant (presentment currency). Equals one delivery for ordinary plans; per-delivery × deliveries_per_cycle for prepaid delivery-policy plans. |
compare_at_price | string | One-time price for the same number of deliveries — for the strikethrough. |
per_delivery_price | string | Price of a single delivery. Same as price unless the plan has a delivery policy. |
checkout_charge_amount | string | Amount charged at checkout (subset of price if deferred). |
remaining_balance_charge_amount | number | Balance charged on the next renewal. 0 in MVP. |
price_adjustments | array | Plan-specific adjustments ({ order_count, adjustment_value: { adjustment_percentage | adjustment_amount } }). |
Cart-line context
| Field | Type | Description |
|---|---|---|
line.selling_plan_allocation | object | null | Present on lines whose customer picked a plan at PDP. Carries the same shape as variant.selling_plan_allocations[]. |
Renewals (MVP behaviour)
A renewal-discovery job runs daily. For each subscription order line whose computed next billing date is on/before today and which does not yet have a child renewal order, the job records the renewal that would happen — it does not yet create a child order or capture payment. What the job currently does:- Finds recent order lines placed with a recurring selling plan whose parent order is not cancelled / abandoned / refunded.
- Computes each line’s next billing date from the plan’s billing cadence.
- Flags every line due on/before today that has not yet been renewed.
- Charge the customer’s saved payment method.
- Create a child order for the renewal.
- Decrement inventory.
- Send a renewal email.
- Advance the line’s next billing date.
What works today
- Themes render the selector and customers can place orders with a
selling_plan_idattached. - The recurring schedule is recorded on the order line.
- The plan is visible on the order detail page (admin), the customer
account order history, and via the storefront
orderdrop. - Subscription-management UI in the admin (cancel, skip, update cadence) reads the plan recorded on each order line.
What doesn’t work yet
- Subsequent deliveries are not automatically charged or shipped.
- No webhook fires on renewal due-date.
- Failed-payment retry / dunning is not wired.
See also
- Aqua Objects — full reference for
product,variant, and other globals. - Aqua Filters —
money,default, and other filters used in the examples above. - Metafields in Aqua — for storing app-specific data alongside selling plans (e.g. anchor day, prepaid count).
- Order drop — for reading the selected plan on past orders in the customer account.