Rule types
The 7 discount rule types (Product, Order, Buy X Get Y, Buy X Get X, Volume, Shipping, Bundle) and the exact config each one accepts, including per-unit vs set pricing on volume tiers, with JSON examples for the API/SDK.
Every discount rule has a Type that controls what discount it applies and how. This page is the reference for all 7 types and the fields each one accepts. For the step-by-step of creating a rule, see Discount rules.
Changing the Type of a saved rule switches the rule off. The Type cell in the rules table is editable, so click it and pick another type. But the discount settings you saved belong to the old type, so Brainerce disables the rule the moment you change it and tells you so. To bring it back: reopen the rule, fill in the new type's discount settings, then set Status back to Active. If the promotion is live right now, create a new rule instead, because changing the type takes the running promotion down until you finish.
The 7 rule types
| Type | What it does | Classic example |
|---|---|---|
| Product Discount | A % or a fixed amount off the filtered products. | "25% off all Apple Watches" |
| Order Discount | A % or a fixed amount off the whole order subtotal. | "$50 off orders over $500" |
| Buy X Get Y | Buy N units of A, get M units of B free or discounted. B can be a different product set than A. | "Buy 2 shirts, get a free tie" |
| Buy X Get X | Buy N units of the same product, get extra units of the same product discounted. | "Buy 2 t-shirts, get the 3rd at 50%" |
| Volume Discount | Quantity pricing. Each tier is a %, a price per unit, or a price for a set of N units. | "3+: 10% off. 10+: $3.99 each. 3 for $499." |
| Shipping Discount | Reduce or zero out shipping cost when the order qualifies. Filters limit it to carts holding a matching item. | "Free shipping over $100" |
| Bundle / Set | Discount when a specific set of products is bought together. Define components (e.g. "1 from cat A + 1 from cat B"). | "MacBook + AirPods bundle for $1,299" |
The Discount Config dialog in the rule form is type-aware: it asks only for the fields the type you picked needs. The rest of this page lists those fields, type by type, with JSON examples for the API/SDK.
Field names below are the API/SDK names. In the dashboard you see friendly labels instead, so don't go hunting for a box marked
getDiscountPercent. The mapping for the ones you'll actually type into:
In the dashboard In the API Discount Percentage valuewithmethod: 'percentage'Discount Amount valuewithmethod: 'fixed_amount'Maximum Discount maximumDiscountBuy Qty buyQuantityGet Qty getQuantityGet Discount % getDiscountPercentGet Items Filter getFiltersMin Qty minQuantityDiscount % / Price per unit / Set price (per tier) valuewithmethodIf you are clicking through the dashboard rather than calling the API, the step-by-step in Discount rules is the page you want.
Product Discount / Order Discount
Same config shape for both. What the discount comes off is decided by the Type you pick, and the Method only says how the number is read:
- Product Discount is taken off the filtered items, for both Methods. A Percentage is a percentage of those items; a Fixed Amount is capped at what those items are worth. $50 off filtered to Electronics, in a cart holding a $60 phone case and a $500 sofa, takes $50 off the phone case and leaves the sofa alone. If the filtered items only came to $30, the shopper saves $30, and the discount never spills onto the rest of the cart.
- Order Discount is taken off the whole cart subtotal, for both Methods, capped at that subtotal. Here filters decide only whether the rule fires, not what it comes off: "20% off the order when they buy any electronics" is an Order Discount with an Electronics filter, and it discounts the whole order.
With no filters set the two types behave identically, because everything is "the filtered items". Pick Product Discount when the saving belongs to particular products, Order Discount when a qualifying cart earns a saving on the total.
This changed. The Method used to decide the base instead of the Type: a Fixed Amount always came off the whole cart (so a filtered "$50 off Electronics" took $50 off a cart holding only a sofa), and a Percentage always stayed on the filtered items (so a filtered Order Discount under-discounted). Rules with no filters are unaffected; they computed the same before and compute the same now. If you have a filtered rule, check it: a filtered Product Discount + Fixed Amount now pays out less (capped at the filtered items), and a filtered Order Discount + Percentage now pays out more (the whole subtotal). Switch the Type if the old payout was the one you wanted.
| Field | Type | Required | What it does |
|---|---|---|---|
method | 'percentage' | 'fixed_amount' | Yes | How the value is interpreted. |
value | Number | Yes | The percentage (20 = 20%) or fixed amount (50 = $50). |
Maximum Discount maximumDiscount | Number | No | Cap on the total discount. Useful for big percentages on big carts (avoid giving $300 off). |
// Product Discount: 20% off filtered items, capped at $100
{ "method": "percentage", "value": 20, "maximumDiscount": 100 }
// Order Discount: $50 flat off the cart
{ "method": "fixed_amount", "value": 50 }Buy X Get Y: different product as the Get
The "BOGO" pattern, where the Get can be different products than the Buy. In the dashboard these are the Buy Qty, Get Qty and Get Discount % boxes in the Discount Config dialog, and the code names in the table are the API/SDK equivalents.
| Field | Type | Required | What it does |
|---|---|---|---|
Buy Qty buyQuantity | Integer | Yes | How many qualifying items the customer must have in cart. |
Get Qty getQuantity | Integer | Yes | How many free/discounted items they get. |
Get Discount % getDiscountPercent | Number | Yes | Percent off the Get items. 100 = free, 50 = half-price. |
Get Items Filter getFilters | Filter object | No | If set: defines what counts as a "Get" item (categories/products/tags/SKUs). If omitted: same as main Filters. |
recursive | Boolean | No | false (default): rule fires once. true: repeats. Buy 4 → get 2 (with recursive). Buy 4 → get 1 (without). |
// "Buy 2 shirts, get a free tie"
// Main Filters = Category "Shirts"
// Discount Config:
{
"buyQuantity": 2,
"getQuantity": 1,
"getDiscountPercent": 100,
"getFilters": { "categories": ["cat_ties_id"] },
"recursive": false
}
// "Every 2 shirts in cart → 1 free tie" (recursive)
// Same filters but recursive: true → buy 4 → 2 free ties; buy 6 → 3 free ties.
categories,products,tagsandbrandstake IDs, never names or slugs.["cat_ties_id"]above stands for a real category id you fetched fromlistCategories; usegetProducts/listTags/listBrandsfor the others. A name such as["ties"]is rejected at save with a 400 telling you to use ids.skusandattributesare the exception: they are free text and are not checked, so a typo there saves happily and then matches nothing. Clicking through the dashboard always emits ids, so this only bites API and AI-agent callers.
Buy X Get X: same-product BOGO
Same product fills both roles: buy N of X, get M more of X discounted.
| Field | Type | Required | What it does |
|---|---|---|---|
Buy Qty buyQuantity | Integer | Yes | Buy this many to qualify. |
Get Qty getQuantity | Integer | Yes | Discount this many additional units of the same product. |
Get Discount % getDiscountPercent | Number | Yes | Percent off the extras. |
recursive | Boolean | No | Repeating, like BXGY. |
// "Buy 2, get the 3rd at 50%"
{ "buyQuantity": 2, "getQuantity": 1, "getDiscountPercent": 50, "recursive": false }Volume Discount: percentage, price per unit, or a price for a set
Each tier is a percentage off, a price for one unit, or a price for a set of N units. Mix freely.
| Field | Type | Required | What it does |
|---|---|---|---|
tiers | Array of tier objects | Yes | At least one tier. Each: { minQuantity, method, value }. |
tiers[].minQuantity | Integer | Yes | For percentage and fixed_price, the quantity that unlocks the tier. For fixed_price_set, the SET SIZE. |
tiers[].method | 'percentage' | 'fixed_price' | 'fixed_price_set' | Yes | See the three rows below the table. |
tiers[].value | Number | Yes | Percent (15 = 15% off), price for ONE unit (3.99), or the total for one whole set (499). |
percentagetakes that percent off every matching unit, onceminQuantityis reached.fixed_priceis a price per unit.10+ → 3.99means every unit costs $3.99 from the tenth onward.fixed_price_setis a price for the whole set.3 → 499means three units cost $499 together. Sets repeat across the cart, and units left over past the last full set stay at full price.
"3 for $499" is fixed_price_set. Writing it as fixed_price sets a $499 price for a single unit, which on a cheaper product is above its own price and so discounts nothing at all: the rule saves, shows as active, and never fires. This is the single most common way a volume rule ends up doing nothing.
// "3+: 10% off. 5+: 15% off. 10+: $3.99 each (bulk)"
{
"tiers": [
{ "minQuantity": 3, "method": "percentage", "value": 10 },
{ "minQuantity": 5, "method": "percentage", "value": 15 },
{ "minQuantity": 10, "method": "fixed_price", "value": 3.99 }
]
}// "3 soaps for $499 together" — repeats, remainder at full price
{
"tiers": [{ "minQuantity": 3, "method": "fixed_price_set", "value": 499 }]
}With that tier on a $199 soap: 3 units cost $499, 4 cost $698 ($499 + one at full price), 6 cost $998, and 2 cost $398 because no full set was reached.
Before you set a fixed_price tier: it re-prices everything in the filter
Two things about volume tiers regularly cost merchants real money:
- The quantity counts everything the filter matches, added together, not per product. A
10+tier filtered to "Charging Accessories" unlocks on 4 cables plus 6 adapters. There is no per-product threshold and no setting to make one. - Once a tier unlocks, every filtered unit is re-priced, including the expensive ones. A
$3.99 eachtier over a whole category will drop a $200 charging dock in that category to $3.99 alongside the $5 cables. Scope afixed_pricetier to a narrow product or SKU filter, never to a broad category. A percentage tier is safe on a broad filter because it scales with each item's own price.
One guard is built in: a unit already priced at or below the tier price keeps its own lower price, so a tier can never raise what someone pays. That also means "each unit costs exactly $value" is a ceiling, not a flat re-price.
A fixed_price_set tier is guarded the same way: if the set price is at or above what those units already cost together, the set simply does not apply. And because a set price covers a group rather than a unit, it is safe to use where a per-unit tier is not, because it never re-prices anything outside a full set.
Bundle: multi-component sets
Define what makes a complete bundle (the components), and the bundle price.
| Field | Type | Required | What it does |
|---|---|---|---|
bundlePrice | Number | Yes | The total bundle price (absolute, replaces sum of components' individual prices). |
components | Array of component objects | Yes | The product slots that make a bundle. Each: { filters, quantity }. ALL slots must be satisfied to form one bundle. |
maxBundlesPerCart | Integer | null | No | How many bundles can apply per cart. null or omitted = 1 (single bundle). Use a large value (e.g. 999) for "unlimited bundles per cart". |
// "MacBook + AirPods bundle for $1,299" (limit 1 per cart)
{
"bundlePrice": 1299,
"components": [
{ "filters": { "products": ["prod_macbook_air"] }, "quantity": 1 },
{ "filters": { "products": ["prod_airpods_pro"] }, "quantity": 1 }
],
"maxBundlesPerCart": 1
}
// "1 from cat A + 1 from cat B for $50" (unlimited bundles)
{
"bundlePrice": 50,
"components": [
{ "filters": { "categories": ["cat_a"] }, "quantity": 1 },
{ "filters": { "categories": ["cat_b"] }, "quantity": 1 }
],
"maxBundlesPerCart": 999
}
cat_a,prod_macbook_airabove stand for real IDs. Component filters take the same values as rule filters:categories,products,tagsandbrandsmust be IDs fetched fromlistCategories/getProducts/listTags/listBrands, and a name or slug is rejected with a 400.skusandattributesare free text and are never checked, so a mistyped SKU in a component means that slot can never be filled and the bundle never forms.
Shipping Discount: free shipping and part-paid delivery
| Field | Type | Required | What it does |
|---|---|---|---|
method | 'free' | 'percentage' | 'fixed_amount' | Yes | free = zeroes shipping. percentage = N% off shipping. fixed_amount = $N off shipping. |
value | Number | If method ≠ 'free' | The amount or percentage. |
// Free shipping
{ "method": "free" }
// 50% off shipping
{ "method": "percentage", "value": 50 }
// $10 off shipping
{ "method": "fixed_amount", "value": 10 }Limiting free shipping to certain products: Filters or a Condition
Filters work on a Shipping Discount. They decide which carts qualify: a rule meaning "free shipping when the cart contains a mattress", built with Filters = Mattresses, discounts shipping only on carts that actually contain one. A cart without a mattress pays normal delivery. Leave Filters empty and the rule covers every order whose conditions pass.
Filters ask "is a matching item in the cart?", meaning presence at any price. When the gate is an amount spent rather than presence, use a Condition instead:
- Subtotal by Category
>=an amount gives free shipping once they have enough of that category in the basket. - Subtotal by Products
>=an amount does the same, for a specific product list.
See Conditions for both. Filters and conditions combine: a rule with Filters = Mattresses and a Cart Subtotal condition needs both to pass.
This changed. Filters on a Shipping Discount used to be saved, shown in the rules table, and then ignored, so a mattress-filtered rule shipped every order free. They are honoured now. If you built such a rule under the old behaviour and worked around it, re-check it: a rule you left filtered "harmlessly" has become genuinely restricted, and one you gated with a condition instead is unaffected.
Anything less than fully free shows as $0.00 in the cart
Only Free (and Percentage set to 100) can be worked out before the shopper chooses a delivery method, so only those show in the cart.
Any percentage under 100, and any fixed amount, has nothing to be a percentage of until a delivery rate is picked. Those rules still appear in the cart's discount list, as $0.00. The real saving is taken off at the delivery step of checkout, and the shopper does pay less. Write your banner and cart-nudge text so it doesn't promise a saving the cart is about to show as zero: "50% off delivery, applied when you choose a delivery method" reads honestly, "Save $10 now" does not.
Only one Shipping Discount rule is applied per order. If two shipping rules both match, checkout honours one of them and the other is silently dropped; they do not add up. Build one shipping rule per outcome rather than layering "10% off delivery" on top of "$5 off delivery".
What's next?
- Discount rules are the step-by-step of building a rule.
- Conditions control when a rule fires (cart value, customer segment, location, time).
- Stacking & priority explains how multiple rules and coupons interact.
- Promotion recipes are ready-made patterns for every type above.
Discount rules
Automatic discounts that apply without a code, what they are, and a step-by-step walkthrough of building one (filters, conditions, discount config, display, stacking).
Conditions
The 15 discount-rule condition types across cart, customer, shipping, and time, plus AND/OR match logic and the timezone gotcha for time-based rules.