EzyStudio ADF

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

ConceptDescription
bundleItemsAn 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.
bundleDiscountThe discount applied to all items that form complete bundles.
maxBundlesThe maximum number of complete bundles the function will discount. Set to 0 for unlimited.
Component bottleneckThe 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 mergingIf 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:

  1. Find all cart lines matching the component’s filter.
  2. Sum their quantities.
  3. 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:

ItemCollection / TagQuantityUnit Price
T-ShirtTops2$25
JeansBottoms1$60
Beltaccessory (tag)3$15

Calculation

Step 1: Component sets

ComponentMatching QtyRequired QtySets
Tops (collection)212
Bottoms (collection)111
Accessory (tag)313

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

ItemDiscounted QtyDiscount
T-Shirt1 of 225% off ($6.25 saved)
Jeans1 of 125% off ($15.00 saved)
Belt1 of 325% 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

FieldPurpose
bundleItemsArray of component objects. Each component has a filter and a requiredQuantity.
filter.filterTypeHow to match products: "collection" (by collection ID), "productTag" (by tag), or "all" (every product).
filter.collectionIdsArray of Shopify collection GIDs. Used when filterType is "collection".
filter.tagsArray of product tag strings. Used when filterType is "productTag".
requiredQuantityHow many items of this component are needed per bundle.
bundleDiscountThe discount applied to all items that form complete bundles. Supports percentage and fixedAmount.
maxBundlesMaximum 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