Hooks let you intercept handler execution and ctx.run() closures at the endpoint, service, or handler level. They are useful for integrating observability libraries, adding custom logging, or implementing other cross-cutting concerns.
How hooks work
A hook is a HooksProvider function that receives the invocation request context and returns interceptors:
const loggingHook: HooksProvider = (ctx) => ({
interceptor: {
handler: async (next) => {
console.log(`Handler starting: ${ctx.request.target}`);
try {
await next();
console.log(`Handler completed: ${ctx.request.target}`);
} catch (e) {
console.log(`Handler error: ${ctx.request.target}: ${e}`);
throw e;
}
},
run: async (name, next) => {
console.log(` Run "${name}" starting`);
try {
await next();
console.log(` Run "${name}" completed`);
} catch (e) {
console.log(` Run "${name}" error: ${e}`);
throw e;
}
},
},
});
The HooksProvider receives a context with ctx.request, which includes:
ctx.request.target — the invocation target (e.g. MyService/myHandler)
ctx.request.id — the invocation ID
The handler interceptor fires on every attempt. The run interceptor only fires when a ctx.run() closure executes. If the result is already in the journal (replay), the closure and its interceptor are skipped.
Always rethrow errors from interceptors. Swallowing an error changes the invocation outcome.
Registering hooks
You can register hooks at the service level. They apply to all handlers in that service.
const myService = restate.service({
name: "MyService",
handlers: {
myHandler: async (ctx: restate.Context, name: string) => {
return `Hello ${name}!`;
},
},
options: {
hooks: [loggingHook],
},
});
You can register multiple hooks. They execute in the order they are provided.
OpenTelemetry integration
See the tracing page to learn how to use hooks for OpenTelemetry integration.