Custom Hooks - triggerHook
triggerHook enables apps to define custom hook points that other rules can listen to, without requiring platform changes. It triggers a secondary rule evaluation inline and returns the resulting effects to the caller.
Availability
| Context | Available |
|---|---|
Rules (.hrl) | ✅ |
Ingresses (.hrc) | ✅ |
Jobs (.hrc) | ✅ |
Modules (.module.hrc) | ✅ (when imported by an ingress or job) |
Syntax
triggerHook is a filter that takes hook data as input and the hook name as argument:
let hookEffects = { some = 'data', more = 'fields' } triggerHook 'OnMyHook'Input
Any record value. The fields become directly accessible to listening rules alongside a hook field that the runtime adds automatically.
Argument
A text literal — the hook name. Convention: On prefix, PascalCase (e.g., OnClaimResolve, OnCartMutation).
Return Value
An iterator of records — the effects emitted by all rules that matched the hook. Returns an empty iterator [] when called inside a secondary evaluation (recursion prevention).
Hook Input Construction
The runtime adds a hook field to the input record before evaluating rules:
// Caller:
{ orderId = '...' } triggerHook 'OnOrderValidated'
// What listening rules receive:
{
hook = 'OnOrderValidated'
orderId = '...'
}There is no data wrapper — the hook data fields are at the top level alongside hook.
Listening Rules
Rules listen to a custom hook by declaring a param input with a literal hook type:
param input: {
hook: 'OnOrderValidated'
orderId: uuid
}
import 'iterators'
from {
effect = 'custom'
type = 'addComplianceFlag'
flag = 'validated'
}The literal type hook: 'OnOrderValidated' ensures this rule only fires for that specific hook. It is automatically skipped for all native hooks and other custom hooks.
TIP
Listening rules can declare a subset of the input fields. As long as the types match, Filtrera's type system will accept the rule. You don't need to match every field the caller provides.
Recursion Prevention
triggerHook is single-level only. If a rule triggered by triggerHook calls triggerHook again, it immediately returns an empty iterator. This is enforced by the runtime — there is no configuration.
Using triggerHook in Rules
In rules, triggerHook returns effects that the calling rule can forward, filter, or batch. The actor spec processes all forwarded effects.
Forwarding All Effects
param input: OnOrderCreated
let hookEffects = { orderId = input.order.orderId } triggerHook 'OnOrderValidated'
// Forward everything
from hookEffectsFiltering Effects
param input: OnOrderCreated
let hookEffects = { orderId = input.order.orderId } triggerHook 'OnOrderValidated'
// Only forward validation errors
from hookEffects where is { effect: 'validationError' }Batching orderCommand Effects
A common pattern: collect orderCommand effects from hook listeners and batch them into a single messageActor:
let hookEffects = { orderId = orderId, lines = lines } triggerHook 'OnClaimResolve'
let hookOrderCommands = hookEffects where e => e.effect == 'orderCommand'
from hookOrderCommands count > 0 match
true |> {
effect = 'messageActor'
actorType = 'order'
actorId = orderId
messageType = 'applyCommands'
body = { commands = hookOrderCommands }
}Using triggerHook in Ingresses and Jobs
NewIn the reactor runtime (ingresses, jobs, modules), triggerHook works the same way — it triggers rule evaluation and returns the effects. However, there is no actor spec to automatically process the returned effects. The calling component must explicitly apply them.
Effect Processing
The caller receives effect records and must use its own runtime capabilities (messageActor, scheduleJob, etc.) to apply them:
import 'iterators'
let hookEffects = { cartId = cartId, cart = renderedCart } triggerHook 'OnCartMutation'
let effects = hookEffects buffer
// Apply ticketCommand effects via messageActor
let commandEffects = effects where is { effect: 'ticketCommand' } buffer
from commandEffects count > 0 match
true |>
from messageActor ('ticket', cartId, [{
type = 'applyCommands'
body = {
commands = commandEffects
select e => { type = e.type, fields = e.fields }
as `list`
}
}])
// Schedule jobs from scheduleJob effects
let jobEffects = effects where is { effect: 'scheduleJob' } buffer
from jobEffects select j =>
from scheduleJob (j.definition, j.at, j.parameters)Custom Effects
When defining hook points, listeners often use the custom effect type to return data to the caller without triggering platform-level processing:
// Listener returns custom effect
from {
effect = 'custom'
type = 'priceLookup'
priceListId = 'wholesale-2025'
discount = 0.15
}// Caller collects custom effects
let hookEffects = { currencyCode = 'SEK' } triggerHook 'OnPriceListSelect'
let priceList = hookEffects
where is { effect: 'custom', type: 'priceLookup' }
firstCustom effects are silently ignored by the platform's effect processor — they only have meaning when collected by the caller.
Hook Naming Convention
| Convention | Example |
|---|---|
Use On prefix | OnCartMutation, OnClaimResolve |
| PascalCase | OnOrderValidated, OnPriceListSelect |
| Describe the event | What happened, not what should happen |
See Also
- Rule Effects — Available effect types including
custom - Rule Hooks — Built-in lifecycle hooks
- Common Patterns — Recipe-style examples