Commerce Hooks
The Commerce app provides custom hooks that other apps can listen to.
OnCartMutation
Fires after every successful cart mutation. The rendered cart record is passed directly as the hook input — listening rules receive the cart fields at root level alongside hook: 'OnCartMutation'.
When It Fires
- After every successful cart mutation ingress: add-item, remove-item, set-quantity, set-address, set-email, set-phone, set-field, remove-field
- Only fires when the mutation succeeds — if the mutation returns an error (e.g., validation failure, item not found), the hook does not fire
- Does not fire on read-only operations: get-cart, cart SSE events
- Does not fire on cart creation (create-cart) — the cart has no items yet
Hook Input
The input is the full rendered cart object with hook added by the runtime:
{
hook: 'OnCartMutation'
cartId: uuid
cartNumber: text
cartState: 'open' | 'completed' | 'rejected'
channelKey: text
currencyCode: text
taxIncluded: bool
orderTotal: number
orderTaxTotal: number
shippingTotal: number
productTotal: number
items: [{
cartItemId: uuid
productNumber: text
quantity: number
unitPrice: number
total: number
tax: number
discount: number
}]
fields: { text -> value }
email: text | nothing
phone: text | nothing
address: value | nothing
invoiceAddress: value | nothing
locale: text | nothing
profileKey: text | nothing
}Listening Rule Example
Rules listen by declaring param input with hook: 'OnCartMutation' and the fields they need:
param input: {
hook: 'OnCartMutation'
cartId: uuid
currencyCode: text
orderTotal: number
orderTaxTotal: number
items: [{
productNumber: text
quantity: number
unitPrice: number
total: number
tax: number
}]
fields: { text -> value }
}
import 'iterators'
// Only process carts that have a PSP integration
let hasPaymentProvider = input.fields->'paymentProvider' is text
from hasPaymentProvider match
true |>
// Build update payload and schedule sync
from {
effect = 'ticketCommand'
type = 'setDynamicFields'
fields = { `field:lastSyncedTotal` = input.orderTotal }
}
from {
effect = 'scheduleJob'
definition = 'apps/my-psp/syncOrder'
at = now
parameters = { cartId = input.cartId, total = input.orderTotal }
}TIP
You only need to declare the fields your rule uses. Filtrera's type system automatically matches rules to hooks based on their declared input shape.
Supported Effects
The Commerce hook dispatcher processes these effect types:
| Effect | How it's applied |
|---|---|
ticketCommand | Batched into a single applyCommands call on the cart |
scheduleJob | Scheduled via scheduleJob runtime function |
Other effects are silently ignored.
No Feedback Loop
The OnCartMutation hook fires from the Commerce reactor runtime (ingresses), not from OnTicketCommands. When hook effects apply commands to the cart (e.g., setting metadata fields), those commands trigger OnTicketCommands on the ticket actor — but no Commerce hook fires again. This breaks the feedback loop that would occur if you used OnTicketCommands directly.
Evaluation Semantics
The hook is evaluated synchronously within each mutation ingress, after the cart has been rendered but before the response is returned. This means:
ticketCommandeffects (e.g., setting sync hashes) are applied before the responsescheduleJobeffects are scheduled before the response- The rendered cart returned to the storefront does not include changes from hook effects — it reflects the state at the time of rendering, before hook effects are applied
See Also
- triggerHook Reference — How custom hooks work
- Rule Effects — Available effect types
- PSP Integration — Using hooks for PSP synchronization