Skip to main content

What are guards?

Guards are the brain of Presentum. They contain your business logic for deciding what gets shown, when, and in what order. Guards run every time candidates change or when external triggers fire. They inspect:
  • Current state and history
  • Storage (impressions, dismissals, conversions)
  • Candidates (potential presentations)
  • Context (user data, app state)
Then they mutate state by adding, removing, or reordering items in slots.
Guards are your primary tool for scheduling presentations, removing ineligible items, periodic refreshes, and complex eligibility rules.

Basic guard

Here’s a simple guard that sets the highest priority campaign as active:

Guard parameters

PresentumStorage
required
Persistence layer for tracking impressions, dismissals, and conversions.
List<PresentumHistoryEntry>
required
Complete history of state changes. Useful for analyzing past decisions.
PresentumState$Mutable
required
Mutable state you can modify. Changes you make here will be committed after all guards run.
List<TItem>
required
All potential presentations. These are the items guards evaluate for eligibility.
Map<String, Object?>
required
Shared data between guards. Pass information from one guard to the next.

Production examples

Scheduling guard

This guard from a production app handles priority, sequencing, impression limits, and cooldowns:
Full scheduling guard implementation ->

Remove ineligible guard

Removes items that are no longer eligible (e.g., expired campaigns). This guard is generic and can be reused in any presentum:
Full remove ineligible guard ->

Sync state with candidates guard

Keeps state synchronized with latest candidate data using diff algorithm. This guard is generic and can be reused in any presentum
Full sync guard implementation ->

Guard execution order

Guards run in sequence, with each guard receiving the state mutated by previous guards:
Order matters! Guards early in the chain prepare data for later guards.

Refresh triggers

Guards can subscribe to external changes and re-run automatically:
When lifecycle.notifyListeners() is called, the engine re-runs all guards with current state.

State mutation methods

Inside guards, you can mutate state freely:
See full set of short-cut commands in the Presentum state API.

Context sharing

Pass data between guards using the context parameter:

Production guard chain

Here’s how a production app structures guards:
See full production initialization ->
Order matters! Early guards prepare data, middle guards apply logic, late guards clean up.

Guard patterns

Impression limiting

Cooldown management

User targeting

Sequencing logic

Best practices

Each guard should have one responsibility. Don’t create one giant guard that does everything. Good: ImpressionLimitGuard, CooldownGuard, SegmentTargetingGuard Bad: MegaGuardThatDoesEverything
For complex conditions, use the built-in eligibility system instead of manual if/else chains.
Learn more about eligibility ->
Use the context parameter to pass data between guards instead of external state.
This creates infinite loops. Guards run automatically when needed.

Common mistakes

Don’t mutate state outside guards
Don’t fetch data in guards

Next steps

Implementing guards

Step-by-step guard building guide

Eligibility system

Use declarative eligibility rules

Storage

Understand the storage interface

Production examples

See real-world guards