Stackability & Priority
For grocery and retail operators running multiple simultaneous promotions, controlling which discounts combine β and which compete β is critical for two reasons: margin protection (you decide the maximum benefit a customer can accumulate) and customer expectation management (a customer who gets a club price and a category discount on the same item should always get the better of the two, not an unexpected combination).
A practical pain point for retailers is coordinating commercial actions across departments. Buying, marketing, and category management teams often define promotions independently β and without a clear rule, those promotions can overlap unexpectedly on the same product at checkout. The Highest Discount setting solves this: any number of teams can define competing promotions, and the engine guarantees that only the single best one applies. This removes the need to manually audit every combination before launch.
Loyal Guru's stackability system gives you two independent levers:
- Campaign categories β define which groups of campaigns are evaluated together and in what order. Categories with a lower priority number run first.
- Stackability type β within a category, each campaign is either Highest Discount (only the best-value campaign in the group wins) or Stackable (campaigns accumulate in priority order).
Level 1 β Campaign categories define evaluation orderβ
Every discount campaign is assigned to a campaign category. Categories are managed in the Campaign categories tab inside each module:
- Loyalty and Promotional engine β Discounts β tab: Campaign categories
- Loyalty and Promotional engine β Points & Wallet β tab: Campaign categories
To create a category, click New campaign category and fill in two fields:
| Field | Description |
|---|---|
| Name | Label for the category (e.g. "Club prices", "Leaflet offers"). |
| Priority | Integer β₯ 1. Lower number = evaluated first. |
Each discount or points campaign then references the category it belongs to.
The evaluation order is:
- All campaigns in the lowest-priority-number category are evaluated together as a group.
- Once that group is resolved and applied, the lines it affected are cleared before the next category starts.
- The next category evaluates on the remaining (unaffected) cart lines.
- This continues until all category tiers have been processed.
This means campaigns in different categories never compete with each other β they run sequentially. A campaign in Category A (priority 1) is resolved before Category B (priority 2) even begins.
Example: A retailer has two categories β "Club prices" (priority 1) and "Leaflet offers" (priority 2). Club prices evaluate first. Once resolved, the engine clears those lines and runs Leaflet offers on whatever remains.
Level 2 β Within a category: Stackable vs Highest Discountβ
Within the same category, each campaign is configured with one of two stackability types:
| Type | Owner App option | What it does |
|---|---|---|
| Highest Discount | Highest discount | Among all campaigns of this type in the category, only the one producing the highest total cart discount is applied. Other campaigns of this type compete but do not stack. |
| Stackable | Stackable | Campaigns apply one after another in priority order (lower priority number first). Each campaign can affect the same lines β they accumulate. |
Highest Discount β how it evaluatesβ
All Highest Discount campaigns in the same category compete together, regardless of their effect type. A percentage campaign, a BOGO bundle, a ratio discount, and a combo price can all be in the same Highest Discount group and compete against each other.
The engine computes a ranking score (RankingDiscount) for each candidate campaign:
- It simulates how the campaign would apply to the current cart.
- It accumulates the total monetary discount across all possible applications, up to the campaign's Limit of effects setting (see below).
- The campaign with the highest total discount across the cart wins.
- The winning campaign is applied and its lines are marked as used.
- The engine then checks if any remaining Highest Discount campaign can apply to the still-unaffected lines, picks the best again, and repeats.
Tiebreaker order: highest total discount β fewest lines affected β oldest campaign (lower ID).
How effect limits affect the rankingβ
The Limit of effects field (set in the campaign's Effect configuration) directly influences the ranking score:
- A campaign with no limit contributes all possible applications to its total ranking discount.
- A campaign with Limit of effects: N contributes at most N applications to its ranking score β even if more applications would be possible on the cart.
This means a campaign that can apply multiple times without a limit can outrank a "better" offer that is capped at one application.
Example: Cart has 6 Γ Coca-Cola (β¬2.00 each).
| Campaign | Effect | Limit of effects | Applications on this cart | Ranking score |
|---|---|---|---|---|
| A β 2Γ1 BOGO | 50% off every 2nd unit | 1 (capped) | Only 1 group counts | ββ¬2.00 |
| B β 3Γ2 BOGO | 33% off every 3rd unit | No limit | 2 groups (6 units Γ· 3) | ββ¬4.00 |
Campaign B wins (ββ¬4.00 > ββ¬2.00) even though the 2Γ1 gives a larger discount per application. The 3Γ2 applies twice and its cumulative saving is higher.
Stackable β how it evaluatesβ
When multiple Stackable campaigns exist in the same category:
- Campaigns are sorted by their priority number (ascending β lower number applies first).
- Each campaign applies in sequence. Lines are not marked as used β subsequent campaigns can apply to the same lines.
- The result is cumulative: a customer can receive multiple stackable discounts on the same item.
Worked examplesβ
Example 1 β Two categories, mixed stackability typesβ
Cart: Olive Oil (β¬5.99), Pasta Γ 2 (β¬1.29 each), Water (β¬0.89)
| Campaign | Category | Type | Condition | Benefit |
|---|---|---|---|---|
| Club Price β Olive Oil | Club prices (priority 1) | Highest Discount | Olive Oil | 20% off β ββ¬1.20 |
| BOGO β Pasta | Club prices (priority 1) | Highest Discount | Pasta Γ 2 | 2nd unit free β ββ¬1.29 |
| Member discount | Club prices (priority 1) | Stackable | All products | 5% off |
| Leaflet β Olive Oil | Leaflet offers (priority 2) | Highest Discount | Olive Oil | 15% off β ββ¬0.90 |
Step 1 β Category "Club prices" (priority 1):
Highest Discount group: BOGO Pasta (ββ¬1.29) vs Club Price Olive Oil (ββ¬1.20). BOGO wins round 1; Pasta lines marked used. Club Price applies in round 2 on Olive Oil.
Stackable group: 5% member discount applies across all lines regardless of used status.
Step 2 β Lines cleared. Category "Leaflet offers" (priority 2):
Leaflet 15% on Olive Oil evaluates (Olive Oil is cleared). Applies β ββ¬0.90.
Final result:
| Product | Applied discounts | Final price |
|---|---|---|
| Olive Oil (β¬5.99) | Club Price β20% + 5% member + Leaflet β15% | β¬3.59 |
| Pasta Γ 2 (β¬2.58) | BOGO free + 5% member | β¬1.16 |
| Water (β¬0.89) | 5% member | β¬0.85 |
Example 2 β BOGOs on different products compete with a broad discountβ
Cart: 3 Γ Coca-Cola (β¬1.29 each) + 3 Γ Fanta (β¬1.29 each)
Active campaigns β all in "Club prices" (priority 1), all Highest Discount:
| Campaign | Condition | Effect | Ranking score |
|---|---|---|---|
| A β 3Γ2 on Coca-Cola | Coca-Cola Γ 3 | 1 unit free | ββ¬1.29 |
| B β 3Γ2 on Fanta | Fanta Γ 3 | 1 unit free | ββ¬1.29 |
| C β 20% off soft drinks | All 6 lines | 20% off each | ββ¬1.55 |
Round 1: C wins (ββ¬1.55 beats ββ¬1.29). All 6 lines β Coca-Cola and Fanta β marked as used.
Round 2: A and B have no available lines. Neither can apply.
Result: Only Campaign C applies. A broad percentage discount on the whole cart beats two separate product-level BOGOs.
What if C did not exist? A and B tie (both ββ¬1.29). Tiebreaker: fewest lines affected (both 3) β oldest campaign ID wins round 1. The other applies in round 2 β Coca-Cola lines are marked used after A wins, but Fanta lines are still free for B.
Example 3 β Effect limits change the winnerβ
Cart: 6 Γ Coca-Cola (β¬2.00 each)
Active campaigns β same category, both Highest Discount:
| Campaign | Effect | Limit of effects | Applications on this cart | Ranking score |
|---|---|---|---|---|
| A β 2Γ1 BOGO | Buy 1, get 1 free | 1 (capped) | Only 1 group counts | ββ¬2.00 |
| B β 3Γ2 BOGO | Buy 2, get 1 free | No limit | 2 groups (6 Γ· 3) | ββ¬4.00 |
Campaign B wins despite a smaller saving per application, because it applies twice and its cumulative saving (β¬4) exceeds A's capped saving (β¬2).
Reverse the limits: Campaign A with no limit β 3 groups β saves 3 Γ β¬2 = β¬6. Campaign B β 2 groups β β¬4. Campaign A wins.
The winner depends on how many times each campaign can apply on the specific cart, not just which offer is better per item.
SKU grouping vs line item groupingβ
When a Highest Discount campaign wins on a cart line, the engine marks those lines as "used" to prevent other Highest Discount campaigns from applying to them. A company-level setting controls how broadly this "used" marking extends:
| Mode | Setting | Behavior |
|---|---|---|
| Per line item | products_by_sku: false (default) | Only the specific cart lines consumed by the winning campaign are marked as used. Other lines with the same product remain available for the next Highest Discount iteration. |
| Per SKU | products_by_sku: true | After a winning campaign touches any line of a product, all cart lines with the same product ID or code are marked as used β blocking further Highest Discount competition for that product anywhere in the cart. |
The difference becomes visible when the same product appears on multiple cart lines and two Highest Discount campaigns partially overlap on it. In per-line-item mode, an unaffected line remains available for a second iteration. In per-SKU mode, no campaign can touch that product again once any of its lines are used.
products_by_sku is a company-level configuration set during onboarding or via a Loyal Guru support request. It is not configurable from the Owner App.
Configurationβ
Campaign stackability typeβ
Set per campaign in Step 3: Effect β Stackability of the creation flow:
| Setting | Description |
|---|---|
| Highest Discount | This campaign competes with other Highest Discount campaigns in the same category. Only the best-value campaign applies per cart section. |
| Stackable | This campaign stacks with others. Applies in order of Priority (integer β lower number applies first). Required when type = Stackable. |
Compatibility β effect typesβ
Not all effect types are compatible with both stackability options:
| Effect type | Highest Discount | Stackable | Notes |
|---|---|---|---|
| Percentage | Yes | Yes | Free choice |
| Ratio | Yes | Yes | Free choice |
| Direct Value | Yes | Yes | Free choice |
| Combo Price | Forced | No | Always Highest Discount β cannot be changed |
| Strikethrough | Forced | No | Always Highest Discount β cannot be changed |
Bundle (rule_group) | Forced | No | Always Highest Discount when bundle is enabled |
Compatibility β event typesβ
Some event types do not support Highest Discount at all:
| Event type | Highest Discount | Stackable |
|---|---|---|
| Cart | Yes | Yes |
| Custom Event | No | Only option |
| Trigger | No | Only option |
| Direct Assignment | No | Only option |
For Custom Event, Trigger, and Direct Assignment campaigns, the stackability type is always Stackable and the Priority field controls the application order.
See also: Limits to cap total redemptions independently of stackability logic.