Skip to main content

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:

FieldDescription
NameLabel for the category (e.g. "Club prices", "Leaflet offers").
PriorityInteger β‰₯ 1. Lower number = evaluated first.

Each discount or points campaign then references the category it belongs to.

The evaluation order is:

  1. All campaigns in the lowest-priority-number category are evaluated together as a group.
  2. Once that group is resolved and applied, the lines it affected are cleared before the next category starts.
  3. The next category evaluates on the remaining (unaffected) cart lines.
  4. 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.

info

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:

TypeOwner App optionWhat it does
Highest DiscountHighest discountAmong 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.
StackableStackableCampaigns 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:

  1. It simulates how the campaign would apply to the current cart.
  2. It accumulates the total monetary discount across all possible applications, up to the campaign's Limit of effects setting (see below).
  3. The campaign with the highest total discount across the cart wins.
  4. The winning campaign is applied and its lines are marked as used.
  5. 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).

CampaignEffectLimit of effectsApplications on this cartRanking score
A β€” 2Γ—1 BOGO50% off every 2nd unit1 (capped)Only 1 group countsβˆ’β‚¬2.00
B β€” 3Γ—2 BOGO33% off every 3rd unitNo limit2 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:

  1. Campaigns are sorted by their priority number (ascending β€” lower number applies first).
  2. Each campaign applies in sequence. Lines are not marked as used β€” subsequent campaigns can apply to the same lines.
  3. 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)

CampaignCategoryTypeConditionBenefit
Club Price β€” Olive OilClub prices (priority 1)Highest DiscountOlive Oil20% off β†’ βˆ’β‚¬1.20
BOGO β€” PastaClub prices (priority 1)Highest DiscountPasta Γ— 22nd unit free β†’ βˆ’β‚¬1.29
Member discountClub prices (priority 1)StackableAll products5% off
Leaflet β€” Olive OilLeaflet offers (priority 2)Highest DiscountOlive Oil15% 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:

ProductApplied discountsFinal 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:

CampaignConditionEffectRanking score
A β€” 3Γ—2 on Coca-ColaCoca-Cola Γ— 31 unit freeβˆ’β‚¬1.29
B β€” 3Γ—2 on FantaFanta Γ— 31 unit freeβˆ’β‚¬1.29
C β€” 20% off soft drinksAll 6 lines20% 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:

CampaignEffectLimit of effectsApplications on this cartRanking score
A β€” 2Γ—1 BOGOBuy 1, get 1 free1 (capped)Only 1 group countsβˆ’β‚¬2.00
B β€” 3Γ—2 BOGOBuy 2, get 1 freeNo limit2 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:

ModeSettingBehavior
Per line itemproducts_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 SKUproducts_by_sku: trueAfter 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.

info

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:

SettingDescription
Highest DiscountThis campaign competes with other Highest Discount campaigns in the same category. Only the best-value campaign applies per cart section.
StackableThis 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 typeHighest DiscountStackableNotes
PercentageYesYesFree choice
RatioYesYesFree choice
Direct ValueYesYesFree choice
Combo PriceForcedNoAlways Highest Discount β€” cannot be changed
StrikethroughForcedNoAlways Highest Discount β€” cannot be changed
Bundle (rule_group)ForcedNoAlways Highest Discount when bundle is enabled

Compatibility β€” event types​

Some event types do not support Highest Discount at all:

Event typeHighest DiscountStackable
CartYesYes
Custom EventNoOnly option
TriggerNoOnly option
Direct AssignmentNoOnly 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.