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.
| 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). Must be above 0, and a percentage at most 100 (0.5 is fine). |
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 }A Product Discount or Order Discount with a value of 0, a negative value, or a percentage over 100 is refused and the rule is not saved. In the dashboard, API and the AI assistant alike, the rule is refused with a message that names the value to fix. A rule that takes nothing off, or more than the whole price, is never saved. For a fixed amount, any amount above 0 is accepted. This check applies to these two types only; a Shipping Discount, Volume Discount tier or Buy X Get Y percentage is not covered by it.
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. |
Apply recursively 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.Which units get the discount: always the cheapest. The discounted units are never picked by the shopper or by the order they were added.
- Without a Get Items Filter, every unit that matches the main Filters is lined up from cheapest to dearest and cut into groups of Buy Qty plus Get Qty. In each full group the cheapest Get Qty units take the percentage. With Buy 2 Get 1 at 100%, a cart of three shirts at 20, 25 and 40 makes the 20 shirt free.
- With a Get Items Filter, the Get units are looked for among everything in the cart that matches it. A unit that matches both the main Filters and the Get Items Filter is counted as a Buy unit first, and only the units left over can be a Get unit. The cheapest of those get the percentage.
- A rule fires once per cart unless recursive is on. With it on, it repeats for every full group.
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. |
Apply recursively recursive | Boolean | No | Repeating, like BXGY. |
// "Buy 2, get the 3rd at 50%"
{ "buyQuantity": 2, "getQuantity": 1, "getDiscountPercent": 50, "recursive": false }Each product is counted on its own. Two different products never complete each other's set: 2 blue mugs and 1 red mug of a different product is not "buy 2, get 1". Within one product, the cheapest units take the discount, which only matters when its variants have different prices. A rule fires once per product unless recursive is on.
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.
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.