Buy X Get Y
Overview
The Buy X Get Y function implements the classic promotional pattern: “Buy a certain quantity of items, and get other items discounted or free.” The buy items and get items can be the same products or entirely different products, enabling a wide range of promotional strategies.
- Discount Classes: Product, Shipping
- Billing: Starter plan and above
- Condition Support: Cart-level only —
customerTag,cartSubtotal,cartTotalQuantity,market
Buy X Get Y supports Product and Shipping discount classes. The function’s logic is centered around counting qualifying “buy” items, calculating how many “get” items earn a discount, and allocating that discount starting from the cheapest eligible items.
Key Concepts
| Concept | Description |
|---|---|
buyQuantity | The number of items the customer must purchase to form one “set.” |
getQuantity | The number of items that receive a discount per set. |
maxSets | The maximum number of sets the function will apply. Set to 0 for unlimited. |
buyItemFilter | Defines which products count as “buy” items. Uses filter types like all, collection, or productTag. |
getItemFilter | Defines which products are eligible as “get” items. Same filter types as buy. |
| Cheapest-first allocation | Get items are sorted by price in ascending order. The discount is applied starting from the cheapest qualifying item. This ensures the merchant gives away the least expensive items first. |
| Buy/Get overlap | The same physical items in the cart can serve as both “buy” and “get” items. For example, in a “Buy 2 Get 1 Free” offer where all products qualify, a cart of 3 identical items uses 2 as the buy and 1 (the third) as the get. |
Real-Life Use Cases
1. Buy 2 Get 1 Free (Same Product)
The most common BOGO variant. All products in the store qualify, and the cheapest item in the cart is the one discounted.
- buyQuantity: 2
- getQuantity: 1
- buyItemFilter:
all - getItemFilter:
all - getDiscount: Percentage, 100%
- maxSets: 0 (unlimited)
A cart with 6 items gets 2 free items (2 sets of “Buy 2 Get 1”).
2. Buy 3 Coffees Get 1 Pastry 50% Off
A cross-category promotion where the buy and get items come from different product groups. This drives discovery by encouraging customers to try a new category.
- buyQuantity: 3
- buyItemFilter: Collection “Coffee”
- getQuantity: 1
- getItemFilter: Product tag “pastry”
- getDiscount: Percentage, 50%
- maxSets: 1
3. Buy Laptop Get Case Free
A targeted accessory promotion tied to a high-value purchase. The buy filter targets a specific product or collection, and the get filter targets the accessory.
- buyQuantity: 1
- buyItemFilter: Collection “Laptops”
- getQuantity: 1
- getItemFilter: Collection “Laptop Cases”
- getDiscount: Percentage, 100%
- maxSets: 1
4. Buy 2 Get 1 Free (Max 3 Sets)
Same as use case 1, but capped at 3 sets to limit exposure. A cart with 20 items would still only get 3 free items.
- buyQuantity: 2
- getQuantity: 1
- maxSets: 3
Algorithm
The Buy X Get Y function follows a precise allocation algorithm:
Step 1: Filter Buy Lines
Evaluate each cart line against buyItemFilter. Sum the quantities of all matching lines to get the total buy quantity.
Step 2: Calculate Sets
sets = floor(total_buy_qty / buyQuantity)
If maxSets > 0, cap the result:
sets = min(sets, maxSets)
Step 3: Filter Get Lines
Evaluate each cart line against getItemFilter. Collect all matching lines.
Step 4: Sort by Price Ascending
Sort the eligible get lines by their unit price in ascending order. This is the cheapest-first allocation strategy.
Step 5: Allocate Discounts
Calculate the total quantity to discount:
total_to_discount = sets * getQuantity
Walk through the sorted get lines from cheapest to most expensive. For each line, allocate up to the line’s available quantity (or the remaining total_to_discount, whichever is smaller). Subtract the allocated amount from total_to_discount and move to the next line. Stop when total_to_discount reaches 0.
Walkthrough Example
Offer: Buy 2 Get 1 Free, all products, unlimited sets.
Cart contents:
| Item | Price | Quantity |
|---|---|---|
| Socks | $5 | 2 |
| T-Shirt | $20 | 3 |
| Jacket | $80 | 1 |
Step 1: All items match the buy filter. Total buy quantity = 2 + 3 + 1 = 6.
Step 2: Sets = floor(6 / 2) = 3. maxSets = 0 (unlimited), so sets remain 3.
Step 3: All items also match the get filter.
Step 4: Sort by price ascending: Socks ($5), T-Shirt ($20), Jacket ($80).
Step 5: Total to discount = 3 * 1 = 3.
- Socks: allocate min(2, 3) = 2 units discounted. Remaining = 1.
- T-Shirt: allocate min(3, 1) = 1 unit discounted. Remaining = 0. Stop.
Result: 2 Socks and 1 T-Shirt receive the 100% discount (free). The customer saves $30 ($5 + $5 + $20).
Configuration Example: Buy 2 Get 1 Free
{
"version": "1.0",
"strategy": "first",
"ruleGroups": [
{
"id": "rg_001",
"name": "Buy 2 Get 1 Free",
"enabled": true,
"conditionLogic": "and",
"conditions": [],
"buyQuantity": 2,
"getQuantity": 1,
"maxSets": 0,
"buyItemFilter": { "filterType": "all" },
"getItemFilter": { "filterType": "all" },
"getDiscount": {
"type": "percentage",
"value": 100,
"message": "Buy 2 Get 1 FREE"
}
}
],
"rejectionRules": []
}
Key Fields
| Field | Purpose |
|---|---|
buyQuantity | Number of items required to form one buy set. |
getQuantity | Number of items discounted per set. |
maxSets | Maximum number of sets allowed. 0 means unlimited. |
buyItemFilter | Filter object determining which cart lines count as buy items. |
getItemFilter | Filter object determining which cart lines are eligible for the get discount. |
getDiscount | The discount applied to get items. Supports percentage and fixedAmount types. |
filterType | Filter mode: "all" (every product qualifies), "collection" (match by collection ID), or "productTag" (match by product tag). |
Cross-Category Example
Buy 3 items from the Coffee collection, get 1 pastry-tagged item at 50% off, limited to 1 set per cart.
{
"version": "1.0",
"strategy": "first",
"collectionIds": ["gid://shopify/Collection/coffee"],
"productTags": ["pastry"],
"ruleGroups": [
{
"id": "rg_001",
"name": "Buy 3 Coffees Get 1 Pastry 50% Off",
"enabled": true,
"conditionLogic": "and",
"conditions": [],
"buyQuantity": 3,
"getQuantity": 1,
"maxSets": 1,
"buyItemFilter": {
"filterType": "collection",
"collectionIds": ["gid://shopify/Collection/coffee"]
},
"getItemFilter": {
"filterType": "productTag",
"tags": ["pastry"]
},
"getDiscount": {
"type": "percentage",
"value": 50,
"message": "Buy 3 Coffees, Get a Pastry 50% OFF"
}
}
],
"rejectionRules": []
}
In this configuration, the collectionIds and productTags arrays at the top level tell the Shopify input query which collections and tags the function needs access to. The buyItemFilter and getItemFilter inside the rule group define the actual matching logic.
Conditional Buy X Get Y
You can add cart-level conditions to restrict who qualifies for the offer. For example, to limit a BOGO deal to customers tagged “member”:
{
"conditions": [
{
"type": "customerTag",
"operator": "hasAny",
"tags": ["member"]
}
]
}
Add this conditions array to the rule group alongside the buy/get configuration. Only customers with the “member” tag will see the discount applied.
Next Steps
- Conditional Discount — Simple if-then discount rules.
- Tiered Discount — Escalating discounts based on quantity or spending thresholds.
- Bundle Discount — Require specific product combinations for a discount.