Skip to content

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

ContextAvailable
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:

filtrera
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:

filtrera
// 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:

filtrera
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

filtrera
param input: OnOrderCreated

let hookEffects = { orderId = input.order.orderId } triggerHook 'OnOrderValidated'

// Forward everything
from hookEffects

Filtering Effects

filtrera
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:

filtrera
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

New

In 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:

filtrera
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:

filtrera
// Listener returns custom effect
from {
  effect = 'custom'
  type = 'priceLookup'
  priceListId = 'wholesale-2025'
  discount = 0.15
}
filtrera
// Caller collects custom effects
let hookEffects = { currencyCode = 'SEK' } triggerHook 'OnPriceListSelect'

let priceList = hookEffects
  where is { effect: 'custom', type: 'priceLookup' }
  first

Custom effects are silently ignored by the platform's effect processor — they only have meaning when collected by the caller.

Hook Naming Convention

ConventionExample
Use On prefixOnCartMutation, OnClaimResolve
PascalCaseOnOrderValidated, OnPriceListSelect
Describe the eventWhat happened, not what should happen

See Also

© 2024 Hantera AB. All rights reserved.