Hooks
Run typed server extension logic at supported Kurrier lifecycle events.
Hooks let trusted extensions participate in Kurrier lifecycle behavior without putting distribution-specific feature logic directly into core actions.
Hooks are server-side lifecycle capabilities. They are different from background workers and schedulers: hooks run as part of an application lifecycle point, while workers process queued jobs in the Nitro worker runtime.
Running a hook
Core Kurrier code runs hooks through kurrierServer:
await kurrierServer.hooks.run("workspace.beforeCreate", {
userId,
});The application code knows the lifecycle event and its context, but does not know which distribution or extension handles it.
Declaring a hook
A server extension attaches handlers by declaring them in hooks:
import type { KurrierExtension } from "@extensions";
import { enforceSingleWorkspace } from "./before-create";
export const singleWorkspaceServerExtension = {
manifest: {
id: "oss.single-workspace",
name: "Single Workspace",
},
hooks: {
"workspace.beforeCreate": enforceSingleWorkspace,
},
} satisfies KurrierExtension;When Kurrier runs workspace.beforeCreate, every registered handler for that hook is awaited.
Typed hook contexts
Supported hook names and their context types are defined by Kurrier's HookMap.
For example, the workspace lifecycle can include contexts shaped like:
export type HookMap = {
"workspace.beforeCreate": {
userId: string;
};
"workspace.afterCreate": {
userId: string;
workspaceId: string;
};
};The exact HookMap in the source tree is authoritative.
Because the hook name determines its context type, TypeScript checks both the caller and handler against the same contract.
Before and after hooks
A before hook is useful for validation, policy, or behavior that must happen before the core operation.
For example, the OSS distribution can enforce its single-workspace rule from:
hooks: {
"workspace.beforeCreate": enforceSingleWorkspace,
},An after hook is useful for work that depends on the completed core operation:
hooks: {
"workspace.afterCreate": logWorkspaceCreated,
},Hooks versus queued work
Do not use a lifecycle hook merely because some work should happen eventually.
Use a hook when the behavior belongs directly to the lifecycle operation and the caller should await it.
Use a worker when the work belongs on a queue, needs independent retries, or should run outside the request lifecycle.
A hook can enqueue work when appropriate, leaving the actual processing to a registered extension worker.
Adding a new lifecycle point
Use an existing hook when one already represents the lifecycle event your feature needs.
If a genuinely new lifecycle point is required:
- add the hook name and typed context to
HookMap - run it from the appropriate core application code through
kurrierServer.hooks.run(...) - implement the feature-specific handler in the extension
Keep the core hook context focused on the data extensions need.
Registration
Server extensions are registered by the active distribution. Feature code should declare hooks but should not register itself manually.
Kurrier runtimes have their own in-memory extension state, so registration happens inside the runtime that consumes the contribution.
Background worker and scheduler contributions follow the same composition principle but are consumed by the Nitro worker runtime.
Hooks are optional
Many extensions only contribute UI or background processing. Do not add hooks unless the feature needs to participate directly in a Kurrier lifecycle event.