Hooks
Hooks let you observe the lifecycle of a flow without putting logging, metrics, and debugging logic inside the steps themselves.
They are one of the main ways to make workflows operationally transparent.
What hooks are for
Hooks are useful for:
- structured logging
- metrics collection
- tracing integration
- alerts and monitoring
- debugging execution order
They are not the place for core business logic. Your workflow should still behave correctly even if every hook is removed.
Defining hooks
You can define hooks when creating a flow:
ts
const flow = create("my-flow", {
hooks: {
onFlowStart: ({ flowName }) => console.log(`Flow ${flowName} started`),
onFlowComplete: ({ result }) => console.log(`Flow completed with status ${result.status}`),
onFlowFail: ({ result }) => console.error("Flow failed", result),
onStepStart: ({ stepName }) => console.log(`Step ${stepName} started`),
onStepComplete: ({ stepName, result }) => console.log(`Step ${stepName} completed`, result),
onStepFail: ({ stepName, error }) => console.error(`Step ${stepName} failed`, error),
onCompensate: ({ stepName }) => console.log(`Compensating ${stepName}`),
onCompensateComplete: ({ stepName }) => console.log(`Compensation finished for ${stepName}`),
}
});Available hooks
| Hook | Description |
|---|---|
onFlowStart | Triggered when flow execution begins. |
onFlowComplete | Triggered when the flow completes successfully or is cancelled via the normal completion path. |
onFlowFail | Triggered when the flow fails. |
onStepStart | Triggered before a step starts executing. |
onStepComplete | Triggered after a step completes successfully. |
onStepFail | Triggered after a step fails. |
onCompensate | Triggered before a compensation function runs. |
onCompensateComplete | Triggered after a compensation function completes. |
What hook payloads contain
Most hook payloads include:
flowNameinputcontext
Step-level hooks also include the step name and step-specific result or error details.
This gives you enough information to produce high-quality logs or metrics without reaching into global state.
Important behavior
Hook failures are swallowed internally.
That means:
- hooks may throw
- hooks may be async
- a broken hook does not break the workflow
This design keeps observability code from becoming a source of business failures.
Practical examples
Logging
ts
onStepComplete: ({ flowName, stepName, result }) => {
logger.info({ flowName, stepName, result });
}Metrics
ts
onFlowComplete: ({ flowName, result }) => {
metrics.timing(`flows.${flowName}.duration_ms`, result.durationMs);
}Error reporting
ts
onStepFail: ({ flowName, stepName, error }) => {
errorTracker.captureException(error, {
tags: { flowName, stepName }
});
}Best Practices
- Keep hooks side-effect-light and operational in nature.
- Prefer structured logs over string logs.
- Use hooks to observe steps, not to decide business flow behavior.
- Include correlation identifiers from input or context in your telemetry.