The merchant creates a sectioned bundle in the Appstle admin: in Bundles & Discounts, click Create offer, then choose Sectioned bundle. There is no API to create one.
How pricing and limits work
A sectioned rule’sdiscountType sets how the box is discounted:
Each tier’s
discountBasedOn says what its value counts:
A tier’s own
discountType is PERCENTAGE (that percentage off each counted line) or FIXED_AMOUNT (that amount off the box, spread across its counted lines).
This guide’s examples use a TIERED_DISCOUNT rule with QUANTITY tiers: 2 units save 5%, 3 save 10% and 4 save 15%.
Each section has minProductCount and maxProductCount. The Function applies them like this:
- Minimum. A box gets no discount until every section has at least its
minProductCountunits. - Maximum. Units above a section’s
maxProductCountdon’t count towards the tier and don’t get a discount. - Membership. A cart line belongs to the first section whose
variantslist contains the line’s variant. The Function never looks at which section your UI showed the product in. A line whose variant is in no section doesn’t count and isn’t discounted, even with the box attributes.
Step 1 — Read the rule from the metafield
Read the rule from the shop metafield, exactly as the headless overview shows (Liquid on a theme, the Shopify Admin API on a headless stack), and keep the rule whosebundleType is SECTIONED_BUNDLE and status is ACTIVE:
sections, tieredDiscount and settings are JSON-encoded strings inside the rule, so parse them before use. This is the live rule for the Build your set bundle, trimmed to the fields this guide uses:
JSON.parse, the first section looks like this (one variant shown):
Step 2 — Render the sections
Build one picker per section fromsections[].variants, and enforce the limits as the shopper picks:
- Stop adding to a section when its units reach
maxProductCount. - Keep the Add to cart button disabled until every section has at least
minProductCountunits. - Show the tier the box has reached and the next one, from
tieredDiscount. A client-side preview is a number you compute from the same fields; the Function decides the real discount, in the cart and at checkout.
Step 3 — Add the box to the cart
Add each picked variant as its own cart line, with these line properties (attributes in Shopify’s Storefront API):
If a variant in
sections[].variants has isMandatory: true, the box gets no discount unless that variant is in it. The widget also sends _appstle_bundles_added_to_cart_at and _appstle_bundle_mandatory_products. You don’t need either._ stay hidden from the shopper in standard Shopify cart and checkout UIs.
cartCreate first, then pass its id as $cartId and the box’s UUID as $instanceId. Send one entry in lines for each pick, each with the same five attributes, and treat the add as done only when userErrors is empty. The call needs a Storefront API access token: see Shopify’s Storefront API documentation. The overview has another cartLinesAdd example.
Step 4 — Sell the box on a subscription
A line is a subscription line when it has a selling plan:selling_plan in the Ajax Cart API, sellingPlanId in Shopify’s Storefront API. When the order is placed, Appstle Subscriptions creates the contract. The bundle rule doesn’t replace the selling plan.
The rule’s appliesOn decides which lines get the box discount:
In a live test with a
SUBSCRIPTION rule, a box of two subscription lines (3 units) and one one-time line (1 unit) discounted only the subscription lines. The one-time line stayed at full price, but its unit still counted, so the box reached the 15% tier.
- Offer only the purchase types
appliesOnallows, and only one-time whenhideSubscriptionPurchaseistrue. - Take the plans from the products’ own selling plans: from
selling_plan_allocations, or fromselling_plan_groupsin/products/<handle>.js. A choice that covers several products can offer only the plans attached to all of them. If the rule’ssettingshasincludedSubscriptionPlansorexcludeSubscriptionPlans(comma-separated selling plan IDs), offer only the included plans and none of the excluded ones. - Ask for one plan for the whole box when
sellingPlanTypeisBUNDLE_LEVEL, one per product when it’sPRODUCT_LEVEL, and one per section whensettingshasenableSectionPurchaseOptions: true. - Add every line of the box with the plan the shopper chose for it, and record the plan in the line properties:
/cart/add.js returns 422:
Step 5 — The discount applies automatically
Once the lines are in the cart, the Function discounts every eligible line. Confirm it on/cart.js: each discounted line carries a discount allocation titled with the rule’s discountName, or with its name when discountName is empty (Build your set here).
This is the real cart for a box of 2 Colombia and 1 Dark Roast (section Single origins, 3 of 3) and 1 Decaf (section Extras). Four units reach the 15% tier, and each line’s discount allocation is titled Build your set. Prices are in dollars here, and /cart.js returns cents:
The cart total goes from 76.00 to 64.60.
These are the cases to design for. Each one was run on the demo store:
A box also stays undiscounted when:
- The rule isn’t
ACTIVE. - A section is under its
minProductCount, or a variant markedisMandatoryisn’t in the box. - The rule is limited by customer tags (
allowedTags,restrictTags) or by uses per customer (limitToUsePerCustomer), and the cart’s buyer isn’t identified or doesn’t qualify. - The buyer is purchasing for a Shopify B2B company and the rule’s
settingshasexcludeB2BCustomers: true.
discountAllowedTags applies only to a signed-in customer who has one of those tags.After checkout: the subscription
When the order has a subscription line, Appstle Subscriptions creates the contract, and the box discount comes with it. Every line keeps your attributes. Each contract line carries the properties you sent ascustomAttributes. This is a line from the live contract, read with the Subscriptions Admin API (GET /api/external/v2/subscription-contracts/contract-external/{contractId}), trimmed:
BUILD_A_BOX_DISCOUNT_<uniqueRef>. This rule doesn’t limit how many orders get the discount, so recurringCycleLimit is null and the discount applies to every renewal. When the merchant limits the discount to a number of orders, the first order counts as one of them:
When the subscriber edits the box
When a box is edited after checkout, in the customer portal or by your own code calling the Subscriptions Storefront API, Appstle Subscriptions re-prices it in the same request. It reads the rule from Appstle Bundles, counts the box’s units, picks the tier and replaces the discount with one namedSECTIONED_DISCOUNT_<uniqueRef>. The new discount is on the contract when the edit call returns.
Re-pricing treats every line of the same bundle on a contract as one box, whatever its _appstle_bundle_instance_id, so two boxes bought in one order are priced together after an edit. If the merchant has turned the offer off, an edit leaves the contract’s box discount as it is.
These are real results on the demo contract, one edit after another:
Section limits on a subscription
- Appstle’s customer portal checks each section’s minimum and maximum when a subscriber adds items to the box.
- The portal API doesn’t. In live tests, adding a line that took a section past its maximum returned
200, and so did setting a quantity that left a section under its minimum. - When Appstle Subscriptions re-prices, it counts the units on every contract line whose variant is in one of the rule’s sections. It doesn’t trim a section to its maximum, and it can keep a tier when a section is under its minimum: in a live test, a box with 1 unit in a section with a minimum of 2 kept the 5% tier.
minProductCount and maxProductCount there, the same way as in Step 2, before you call the API.
To add items to a box from your own subscriber portal, call the Subscriptions Storefront API on your storefront domain, as the signed-in customer:
The endpoint adds each variant as its own subscription line, then re-prices the box. It returns
200 with a JSON array that holds the contract after each added variant, so the last element is the contract’s current state. See Add a line item to a specific section of a contract in the API reference.
For a signed-in customer, the call refuses the add with a 400 when any of these checks fail. The body is application/problem+json, and its title holds the message:
Show the gift message to the customer without the
UserGeneratedError: prefix. Permission denied, Subscription cannot be modified means the store doesn’t let this customer add to the box, so hide adding on that subscription. The variants are added one at a time, so a call that fails part-way, such as when the subscription reaches its product limit (50 by default), can leave the earlier variants on the subscription: read the contract again before you retry.
A call without a signed-in customer answers 401 (see Authentication), and a call over a rate limit answers 429 with a Retry-After header: wait that many seconds before you retry.
Any product added to a subscription that holds a sectioned box, through this endpoint or subscription-contracts-add-line-item, copies the box’s attributes from the contract’s box lines, with _appstle-bundles-variants and _appstle_bundles_subscription_selling_plan_id set to the new line’s variant and plan. A product that isn’t in one of the rule’s sections gets the attributes too, but it doesn’t count towards the box. Lines added after checkout store those two values as Shopify IDs (gid://shopify/ProductVariant/..., gid://shopify/SellingPlan/...), where checkout lines store plain numbers, so normalize them when you read them.
Checklist
1
The offer is live in the Appstle admin
Only live offers (
status is ACTIVE) have an automatic discount.2
Your UI enforces every section's limits
The Function trims what exceeds a maximum and withholds the discount below a minimum, but your shoppers need to know why.
3
Every line carries the box attributes
The same
_appstle-bb-id and _appstle_bundle_instance_id on each line of a box, plus _appstle_bundles_type set to SECTIONED_BUNDLE.4
Subscription lines use a selling plan attached to the variant
Each line has its own
selling_plan, and the plan’s ID is also in _appstle_bundles_subscription_selling_plan_id.5
Purchase types match appliesOn
Put the lines on a selling plan for a
SUBSCRIPTION rule, and none for ONE_TIME.6
Verify the discount in the cart
Check
/cart.js for the discount allocation on each line.7
Appstle Bundles is connected to Appstle Subscriptions
Needed to re-price a subscription’s box discount after an edit.
Next steps
Build your own frontend
How the metafield, attributes and discount Function fit together.
Combo bundle
Sell a fixed set of products with a per-product discount.
Build your own subscription frontend
Build the customer portal that edits subscriptions.
JavaScript events and API
The globals, methods and events your theme code can use.