How to build a subscription integration with Simple Bundles app?
Subscription apps (Recharge, Loop, Seal, Appstle, Skio, Ordergroove and others) can integrate with Simple Bundles by reading two things Shopify already stores: variant metafields, which hold each bundle's configuration, and line item properties, which hold the customer's selection. If your app carries the bundle line's properties from the first order onto every renewal, Simple Bundles breaks each renewal down into component SKUs automatically. If your customer portal can also rewrite those properties, subscribers can change their bundle mix for future deliveries without the merchant touching a single order.
This guide is for subscription app developers, agencies, and technical merchants. Merchants setting up subscriptions for the first time should start with How to create bundles with a subscription plan like Recharge, Seal, and Appstle.
In this article
- How does Simple Bundles process subscription renewals?
- Where does Simple Bundles store bundle data?
- How is the customer's selection encoded?
- How do I build "edit my bundle" in a customer portal?
- How do I enforce pack rules?
- Can I use the Simple Bundles API instead of metafields?
- What's different on Simple Bundles 1.0?
- FAQ
How does Simple Bundles process subscription renewals?
On the first purchase, the storefront bundle widget writes the customer's selection onto the bundle line as line item properties. At checkout, the Simple Bundles cart transform expands one-time bundle purchases into component lines.
Shopify's cart transform does not run on selling plan (subscription) lines, and renewal orders never pass through a cart. A renewal order arrives with the bundle parent line intact at quantity 1, plus whatever line item properties your app copied onto it. When Simple Bundles receives the orders/create webhook, it:
- Recognizes the line as a bundle by its variant ID. The variant has to be a bundle in the merchant's Simple Bundles catalog; properties alone don't make a line a bundle.
- Reads the bundle's stored configuration and the line's properties to work out the customer's selection.
- Adds the component lines to the order with a Shopify order edit. Each component gets a 100% line discount (description
Simple Bundles: <bundle title> | ID: <line id>), so the order total doesn't change. - Tags the order
Simple Bundles 2.0 - Bundle Orderand, if the merchant has order metafields turned on, writessimple_bundles.bundled_line_items.
The parent line stays on the order for pricing and accounting. Fulfillment apps and 3PLs pick the component SKUs.
If Shopify refuses the order edit (it sometimes rejects edits "related to a recurring selling plan"), Simple Bundles skips that line instead of failing the order. Merchants can turn on the associated order fallback, which creates a duplicate order containing the components, or re-run processing with the Shopify Flow action Process Bundle Order. See Handling orders that cannot be edited by Shopify.
Where does Simple Bundles store bundle data?
Bundle variant metafields (the bundle's configuration)
Any app with the read_products scope can read these:
| Metafield | Contents |
|---|---|
simple_bundles.bundled_variants |
The component list. Each component has variant_id , product_id , sku , variant_title , product_title , price , quantity_in_bundle , inventory_item_id , plus variant_options (infinite options bundles) and selection_token (build-your-own bundles). This is the public source of truth for a bundle's contents. |
simple-bundles.byob_validations |
Pack rules for build-your-own bundles, stored on each pack variant. Note the hyphen in the namespace. |
simple_bundles.variant_options_v2 |
Option groups for infinite options bundles, for example [[{"optionName": "Flavor 1", "optionValues": "Berry,Citrus"}]] . |
To check whether a subscription line is a bundle, look for a simple_bundles.bundled_variants metafield on its variant.
Metafields in the $app:simple_bundles namespace are owned by Simple Bundles and power the cart transform. Shopify doesn't let other apps read them, so don't build against them.
Line item properties (the customer's selection)
These sit on the bundle parent line and should be carried from cart to order to subscription contract line:
| Property | Used for |
|---|---|
Visible option properties, for example Flavor 1: Berry |
Drive the breakdown for infinite options bundles. Matched case-insensitively against each component's variant_options . |
_bundle_selection |
Drives the breakdown for build-your-own bundles and is the cart transform's input at checkout. |
Component lines expanded at checkout may also carry _sb_bundle_title , _sb_bundle_group and _sb_bundle_variant_id_qty when the merchant turns on cart transform bundle line item properties. Simple Bundles regenerates these, so they don't belong on a subscription contract line.
What's on the order after breakdown?
- The order tag
Simple Bundles 2.0 - Bundle Order(andSimple Bundles - Associated Orderwhen the fallback is used). - Component lines at a 100% discount.
- Optionally, the
simple_bundles.bundled_line_itemsorder metafield: one JSON row per component, mapping the parent line (bundle_line_item_id,bundle_line_item_sku,bundle_line_item_propertiesand so on) to its component lines. See Enabling bundle details in Shopify order metafields.
How is the customer's selection encoded?
Build-your-own bundles
Every component has a stable selection_token , published in simple_bundles.bundled_variants and in the API. You don't need to compute it yourself.
_bundle_selection lists one token per slot, with duplicates repeated, joined by space, <> , space. A customer who picks 2 Berry Boxes (token a1b2c3d4 ) and 1 Citrus Box (token 9f8e7d6c ) has:
_bundle_selection = "a1b2c3d4 <> a1b2c3d4 <> 9f8e7d6c"
On breakdown, each component's quantity is quantity_in_bundle × parent quantity × token count . Unknown tokens are dropped silently, so always build tokens from current metafield or API data.
Infinite options bundles
The selection is stored twice, and both copies need to stay in sync:
- One visible property per option group, such as
Flavor 1: BerryandFlavor 2: Citrus. These are what the renewal breakdown matches against. _bundle_selection, with the chosen values joined by<>between option groups (and++between values inside a multi-select group), for exampleBerry <> Citrus.
Fixed bundles
Fixed bundles have no selection. Components come from the bundle's configuration and properties are ignored, so the only change a portal can make is swapping to a different bundle variant.
How do I build "edit my bundle" in a customer portal?
The selection on a subscription only stays fixed because nothing updates the contract line's properties after the first purchase. A portal that rewrites them changes every future renewal.
- Detect bundles: the line's variant has a
simple_bundles.bundled_variantsmetafield. - Render the picker. For BYOB, list the components from
bundled_variantsand read the pack size and section limits fromsimple-bundles.byob_validations. For infinite options, show one selector per option group fromsimple_bundles.variant_options_v2. Pre-fill from the line's current properties. - Validate the new selection against the pack rules (next section). Simple Bundles doesn't validate selections on renewal, so your portal needs to.
- Write the new selection to the subscription contract line's properties (Recharge line item
properties, Loop line attributes, or your platform's equivalent). For BYOB, rebuild_bundle_selection. For infinite options, rewrite both the visible properties and_bundle_selection. - Keep the parent line at quantity 1. A bigger box is a different pack variant (for example 3-Pack instead of 2-Pack), so swap the variant and update the properties instead of raising the quantity.
That's all. The next renewal carries the new properties and Simple Bundles breaks it down into the new mix. There's nothing to call on the Simple Bundles side.
Renewal pricing comes from the subscription contract line, not from the selection. Changing the mix inside one BYOB pack variant doesn't change the price. If the customer changes pack size, or the bundle uses custom per-selection pricing, update the line's variant or price following your platform's rules.
How do I enforce pack rules?
Build-your-own pack variants store their rules as JSON in simple-bundles.byob_validations . Each pack size has its own rules:
{
"rules": [
{ "type": "itemCount", "count": 3 },
{ "type": "perItemMax", "max": 2 },
{ "type": "groupRange", "section": "Snacks", "min": 2, "max": 4,
"message": "Pick 2-4 snacks" },
{ "type": "itemMax", "productId": "10240566853952", "max": 1 }
]
}
| Rule | Meaning |
|---|---|
itemCount |
Total picks required. Defaults to the pack's own item count when missing. |
perItemMax |
Maximum picks of any one variant. 1 means no duplicates. |
groupRange |
Minimum and maximum picks within one section, by section name. |
itemMax |
Cap for one specific variantId or productId (numeric IDs). |
message |
Optional shopper-facing error text. |
A merchant's "at least 2 boxes" minimum is the itemCount of their smallest pack variant. Offering a bigger box means offering the next pack variant. For more on how BYOB works on the storefront, see How to create a BYOB experience.
Can I use the Simple Bundles API instead of metafields?
Yes, for reading bundle configuration server-side. The API needs Simple Bundles 2.0 on a paid plan, and the merchant creates a token under Settings → API tokens.
- Base URL
https://api.simplebundles.io, auth headerAuthorization: Bearer <token>, 60 requests per minute. GET /api/v1/bundleslists bundles, withtype,statusandupdated_at_min/updated_at_maxfilters.GET /api/v1/bundles/{shopify_variant_id}returns one bundle, includingselectors(infinite options) andbundled_variantswithselection_token(BYOB).
There's no endpoint that validates a selection or expands it into components, so read the configuration and validate in your app. Full reference: How to use Simple Bundles API.
What's different on Simple Bundles 1.0?
Renewals work the same way on both versions: Simple Bundles breaks the parent line down with an order edit after the order is created. On 1.0 the first purchase is also broken down after checkout, because there's no cart transform. The other differences:
- The order tag is
Simple Bundles - Bundle Order. - Infinite options groups live in
simple_bundles.variant_options(a flat list) instead ofvariant_options_v2. - There are no build-your-own bundles, no
byob_validationsmetafield, and no public API. Read configuration fromsimple_bundles.bundled_variants.
FAQ
Can subscribers change their bundle today without a portal integration?
Only if the merchant edits each renewal order by hand, or edits the subscription line's properties in the subscription app's admin. The portal integration in this guide automates that second option.
Would switching from Recharge to Loop (or another app) fix renewals?
No. Every subscription app uses the same mechanism: a contract line that carries line item properties onto each renewal order. What matters is that the properties survive onto every renewal, which mainstream subscription apps do by default.
Can I add component lines with the selling plan at the cart instead?
Yes, some merchants expand bundles into components at add-to-cart so each component carries the selling plan. See Breaking down bundles at the cart level when using Loop and other subscription apps. With that setup the subscription contract holds components instead of a bundle parent, and the editing approach in this guide doesn't apply.
Does Simple Bundles validate the selection on renewal?
No. Your portal needs to enforce pack rules before saving. Tokens or option values that don't match the bundle are dropped at breakdown.
Will this change once Shopify supports cart transform on subscriptions?
Shopify is working on native subscription support for cart transform bundles, which should let component lines flow through subscription contracts directly. Until then, the property-based integration in this guide is the supported path.
Building an integration and have questions? Email us at hello@simplebundles.io.