Bundle Discount
Overview
The Bundle Discount function implements the pattern: “Buy these specific items together and get a discount.” Unlike Buy X Get Y, which focuses on quantity-based promotions, the Bundle Discount requires all designated components to be present in the cart before any discount is applied. This makes it ideal for curated product combinations, kits, and outfit-style promotions.
- Discount Classes: Product, Shipping
- Billing: Enterprise plan only
- Condition Support: Cart-level only —
customerTag,cartSubtotal,cartTotalQuantity,market
The Bundle Discount is the most specialized function type. It is designed for merchants who want to incentivize customers to purchase a complete set of complementary products rather than individual items.
Key Concepts
| Concept | Description |
|---|---|
bundleItems | An array of component definitions. Each component specifies a product filter and a required quantity. All components must be present for a bundle to be complete. |
bundleDiscount | The discount applied to all items that form complete bundles. |
maxBundles | The maximum number of complete bundles the function will discount. Set to 0 for unlimited. |
| Component bottleneck | The number of complete bundles is determined by the component with the fewest available sets. If a bundle requires 1 Top, 1 Bottom, and 1 Accessory, and the cart has 3 Tops, 1 Bottom, and 5 Accessories, only 1 complete bundle can be formed (bottlenecked by Bottoms). |
| Quantity merging | If a single cart line matches multiple bundle components, the quantities are merged. The function ensures a line’s allocated quantity never exceeds its actual cart quantity. |
Real-Life Use Cases
1. Complete Outfit 25% Off
Encourage customers to buy a coordinated outfit by discounting the combination. Each bundle requires one item from the Tops collection, one from Bottoms, and one Accessory-tagged item.
- Components:
- 1x from collection “Tops”
- 1x from collection “Bottoms”
- 1x with product tag “accessory”
- Discount: Percentage, 25%
- maxBundles: 0 (unlimited)
2. Starter Kit Flat Price Discount
Offer a fixed dollar amount off when a customer purchases all items in a starter kit. This is useful when you want a predictable discount amount rather than a percentage.
- Components:
- 1x with product tag “starter-cleanser”
- 1x with product tag “starter-toner”
- 1x with product tag “starter-moisturizer”
- Discount: Fixed amount, $15 off
- maxBundles: 1
3. Skincare Routine 20% Off
A simpler bundle requiring 3 items from the same collection. Customers who buy any 3 products from the Skincare Routine collection receive 20% off those items.
- Components:
- 3x from collection “Skincare Routine”
- Discount: Percentage, 20%
- maxBundles: 0 (unlimited)
Note: A single-component bundle with a required quantity of 3 is functionally different from a Buy X Get Y with buyQuantity: 3. The bundle discounts all 3 items, whereas Buy X Get Y only discounts the “get” items.
Algorithm
The Bundle Discount function uses a multi-step allocation algorithm to determine how many complete bundles can be formed and which specific cart lines receive the discount.
Step 1: Calculate Component Sets
For each component in the bundleItems array:
- Find all cart lines matching the component’s filter.
- Sum their quantities.
- Calculate the number of sets this component can contribute:
sets_for_component = floor(total_matching_qty / requiredQuantity)
Step 2: Determine Complete Bundles
The number of complete bundles is the minimum across all components, optionally capped by maxBundles:
complete_bundles = min(sets_for_component_1, sets_for_component_2, ..., sets_for_component_n)
if maxBundles > 0:
complete_bundles = min(complete_bundles, maxBundles)
Step 3: Allocate Quantities Per Component
For each component, calculate the total quantity needed:
needed = complete_bundles * requiredQuantity
Walk through the matching lines and allocate quantities up to each line’s available amount until needed is fulfilled.
Step 4: Merge by Line ID
If a cart line matches multiple components (for example, a product that is in both the “Tops” collection and tagged “accessory”), the allocated quantities are merged. The merged quantity is capped at the line’s actual cart quantity to prevent over-allocation.
Step 5: Build Discount Output
Generate the ProductDiscountsAdd output, applying the bundleDiscount to each allocated line and quantity.
Walkthrough Example
Bundle definition: Complete Outfit — 1 Top + 1 Bottom + 1 Accessory, 25% off, unlimited bundles.
Cart contents:
| Item | Collection / Tag | Quantity | Unit Price |
|---|---|---|---|
| T-Shirt | Tops | 2 | $25 |
| Jeans | Bottoms | 1 | $60 |
| Belt | accessory (tag) | 3 | $15 |
Calculation
Step 1: Component sets
| Component | Matching Qty | Required Qty | Sets |
|---|---|---|---|
| Tops (collection) | 2 | 1 | 2 |
| Bottoms (collection) | 1 | 1 | 1 |
| Accessory (tag) | 3 | 1 | 3 |
Step 2: Complete bundles
complete_bundles = min(2, 1, 3) = 1
Bottoms is the bottleneck. Only 1 complete bundle can be formed.
Step 3: Allocate quantities
- Tops: need 1. Allocate 1 from T-Shirt (has 2 available).
- Bottoms: need 1. Allocate 1 from Jeans (has 1 available).
- Accessory: need 1. Allocate 1 from Belt (has 3 available).
Step 4: No merging needed (no line matches multiple components).
Step 5: Apply 25% discount
| Item | Discounted Qty | Discount |
|---|---|---|
| T-Shirt | 1 of 2 | 25% off ($6.25 saved) |
| Jeans | 1 of 1 | 25% off ($15.00 saved) |
| Belt | 1 of 3 | 25% off ($3.75 saved) |
Total savings: $25.00
The remaining items (1 T-Shirt and 2 Belts) are at full price because they are not part of a complete bundle.
Configuration Example
{
"version": "1.0",
"strategy": "first",
"collectionIds": [
"gid://shopify/Collection/tops",
"gid://shopify/Collection/bottoms"
],
"productTags": ["accessory"],
"ruleGroups": [
{
"id": "rg_001",
"name": "Complete Outfit Bundle",
"enabled": true,
"conditionLogic": "and",
"conditions": [],
"bundleItems": [
{
"filter": {
"filterType": "collection",
"collectionIds": ["gid://shopify/Collection/tops"]
},
"requiredQuantity": 1
},
{
"filter": {
"filterType": "collection",
"collectionIds": ["gid://shopify/Collection/bottoms"]
},
"requiredQuantity": 1
},
{
"filter": {
"filterType": "productTag",
"tags": ["accessory"]
},
"requiredQuantity": 1
}
],
"bundleDiscount": {
"type": "percentage",
"value": 25,
"message": "Complete Outfit 25% OFF"
},
"maxBundles": 0
}
],
"rejectionRules": []
}
Key Fields
| Field | Purpose |
|---|---|
bundleItems | Array of component objects. Each component has a filter and a requiredQuantity. |
filter.filterType | How to match products: "collection" (by collection ID), "productTag" (by tag), or "all" (every product). |
filter.collectionIds | Array of Shopify collection GIDs. Used when filterType is "collection". |
filter.tags | Array of product tag strings. Used when filterType is "productTag". |
requiredQuantity | How many items of this component are needed per bundle. |
bundleDiscount | The discount applied to all items that form complete bundles. Supports percentage and fixedAmount. |
maxBundles | Maximum number of complete bundles to discount. 0 means unlimited. |
collectionIds (top-level) | Declares which collection IDs the function needs in its Shopify input query. Must include all collection IDs referenced in bundle item filters. |
productTags (top-level) | Declares which product tags the function needs in its input query. Must include all tags referenced in bundle item filters. |
Fixed Amount Bundle Example
A starter kit with a flat $15 discount, limited to 1 bundle per cart.
{
"version": "1.0",
"strategy": "first",
"productTags": ["starter-cleanser", "starter-toner", "starter-moisturizer"],
"ruleGroups": [
{
"id": "rg_001",
"name": "Skincare Starter Kit",
"enabled": true,
"conditionLogic": "and",
"conditions": [],
"bundleItems": [
{
"filter": { "filterType": "productTag", "tags": ["starter-cleanser"] },
"requiredQuantity": 1
},
{
"filter": { "filterType": "productTag", "tags": ["starter-toner"] },
"requiredQuantity": 1
},
{
"filter": { "filterType": "productTag", "tags": ["starter-moisturizer"] },
"requiredQuantity": 1
}
],
"bundleDiscount": {
"type": "fixedAmount",
"value": 15,
"message": "Starter Kit - Save $15"
},
"maxBundles": 1
}
],
"rejectionRules": []
}
With maxBundles set to 1, even if the customer adds multiples of each component, only 1 bundle receives the $15 discount.
Adding Conditions
You can add cart-level conditions to restrict bundle eligibility. For example, to offer the bundle discount only in a specific market:
{
"conditions": [
{
"type": "market",
"operator": "is",
"value": "US"
}
]
}
This ensures the bundle discount only applies to customers shopping in the US market. Customers in other markets see full prices even if they have all bundle components in their cart.
Next Steps
- Conditional Discount — Simple if-then discount rules.
- Tiered Discount — Escalating discounts based on quantity or spending thresholds.
- Buy X Get Y — Create buy-one-get-one and promotional bundle offers.