workflow
Version:
Workflow SDK - Build durable, resilient, and observable workflows
224 lines (166 loc) • 8.38 kB
text/mdx
---
title: defineHook
description: Create type-safe hooks with consistent payload types and optional validation.
type: reference
summary: Use defineHook to create a reusable, type-safe hook definition with optional schema validation.
prerequisites:
- /docs/foundations/hooks
related:
- /docs/api-reference/workflow/create-hook
---
Creates a type-safe hook helper that ensures the payload type is consistent between hook creation and resumption.
This is a lightweight wrapper around [`createHook()`](/docs/api-reference/workflow/create-hook) and [`resumeHook()`](/docs/api-reference/workflow-api/resume-hook) to avoid type mismatches. It also supports optional runtime validation and transformation of payloads using any [Standard Schema v1](https://standardschema.dev) compliant validator like Zod or Valibot.
<Callout>
We recommend using `defineHook()` over `createHook()` in production codebases for better type safety and optional runtime validation.
</Callout>
<Callout type="warn">
Schema validation checks the payload's shape, not who sent it. Authorize callers before calling `resume()`. See [Hook and webhook security](/docs/foundations/hooks#security).
</Callout>
```ts lineNumbers
import { defineHook } from "workflow";
const nameHook = defineHook<{
name: string;
}>();
export async function nameWorkflow() {
"use workflow";
const hook = nameHook.create(); // [!code highlight]
const result = await hook; // Fully typed as { name: string }
console.log("Name:", result.name);
}
```
## API signature
### Parameters
<TSDoc
definition={`
import { defineHook } from "workflow";
export default defineHook;`}
showSections={['parameters']}
/>
### Returns
`defineHook()` returns a `TypedHook<TInput, TOutput>`:
<TSDoc
definition={`
interface TypedHook<TInput, TOutput> {
/** Creates the hook. Call inside a "use workflow" function. */
create(options?: HookOptions): Hook<TOutput>;
/** Resumes the hook from runtime code. Resolves to the resumed hook; throws HookNotFoundError if the token does not match an active hook. */
resume(token: string, payload: TInput): Promise<HookEntity>;
}
export default TypedHook;`}
/>
`create()` is called inside a `"use workflow"` function to create the hook; `resume()` is called from runtime code (an API route or server action). When a `schema` is provided, `resume()` accepts the raw input type (`TInput`) and the workflow receives the validated and possibly transformed output type (`TOutput`); without a schema, `TOutput` defaults to `TInput`. `resume()` throws [`HookNotFoundError`](/docs/api-reference/workflow-errors/hook-not-found-error) if the token does not match an active hook. It does not return `null`.
## Examples
### Basic type-safe hook definition
By defining the hook once with a specific payload type, you can reuse it in multiple workflows and API routes with automatic type safety.
```typescript lineNumbers
import { defineHook } from "workflow";
// Define once with a specific payload type
const approvalHook = defineHook<{ // [!code highlight]
approved: boolean; // [!code highlight]
comment: string; // [!code highlight]
}>(); // [!code highlight]
// In your workflow
export async function workflowWithApproval() {
"use workflow";
const hook = approvalHook.create();
const result = await hook; // Fully typed as { approved: boolean; comment: string }
console.log("Approved:", result.approved);
console.log("Comment:", result.comment);
}
```
### Resuming with type safety
Hooks can be resumed using the same defined hook and a token. By using the same hook, you can ensure that the payload matches the defined type when resuming a hook. `resume()` resolves to the resumed hook and throws [`HookNotFoundError`](/docs/api-reference/workflow-errors/hook-not-found-error) if the token does not match an active hook.
```typescript lineNumbers
import { HookNotFoundError } from "workflow/errors";
// Use the same defined hook to resume
export async function POST(request: Request) {
const { token, approved, comment } = await request.json();
try {
// Type-safe resumption - TypeScript ensures the payload matches
const hook = await approvalHook.resume(token, { // [!code highlight]
approved, // [!code highlight]
comment, // [!code highlight]
}); // [!code highlight]
return Response.json({ success: true, runId: hook.runId });
} catch (error) {
if (HookNotFoundError.is(error)) { // [!code highlight]
return Response.json({ error: "Hook not found" }, { status: 404 });
}
throw error;
}
}
```
### Validate and transform with schema
You can provide runtime validation and transformation of hook payloads using the `schema` option. This option accepts any validator that conforms to the [Standard Schema v1](https://standardschema.dev) specification.
<Callout type="info">
Standard Schema is a standardized specification for schema validation libraries. Most popular validation libraries support it, including Zod, Valibot, ArkType, and Effect Schema. You can also write custom validators.
</Callout>
#### Using Zod with defineHook
Here's an example using [Zod](https://zod.dev) to validate and transform hook payloads:
```typescript lineNumbers
import { defineHook } from "workflow";
import { z } from "zod";
export const approvalHook = defineHook({
schema: z.object({ // [!code highlight]
approved: z.boolean(), // [!code highlight]
comment: z.string().min(1).transform((value) => value.trim()), // [!code highlight]
}), // [!code highlight]
});
export async function approvalWorkflow(approvalId: string) {
"use workflow";
const hook = approvalHook.create({
token: `approval:${approvalId}`,
});
// Payload is automatically typed based on the schema
const { approved, comment } = await hook;
console.log("Approved:", approved);
console.log("Comment (trimmed):", comment);
}
```
When resuming the hook from an API route, the schema validates and transforms the incoming payload before the workflow resumes:
```typescript lineNumbers
export async function POST(request: Request) {
// Incoming payload: { token: "...", approved: true, comment: " Ready! " }
const { token, approved, comment } = await request.json();
// The schema validates and transforms the payload:
// - Checks that `approved` is a boolean
// - Checks that `comment` is a non-empty string
// - Trims whitespace from the comment
// If validation fails, an error is thrown and the hook is not resumed
await approvalHook.resume(token, { // [!code highlight]
approved, // [!code highlight]
comment, // Automatically trimmed to "Ready!" // [!code highlight]
}); // [!code highlight]
return Response.json({ success: true });
}
```
#### Using other Standard Schema libraries
The same pattern works with any Standard Schema v1 compliant library. Here's an example with [Valibot](https://valibot.dev):
```typescript lineNumbers
import { defineHook } from "workflow";
import * as v from "valibot";
export const approvalHook = defineHook({
schema: v.object({ // [!code highlight]
approved: v.boolean(), // [!code highlight]
comment: v.pipe(v.string(), v.minLength(1), v.trim()), // [!code highlight]
}), // [!code highlight]
});
```
### Customizing tokens
Tokens are used to identify a specific hook and for resuming a hook. You can customize the token to be more specific to a use case.
```typescript lineNumbers
import { defineHook } from "workflow";
const slackHook = defineHook<{ text: string; userId: string }>();
export async function slackBotWorkflow(channelId: string) {
"use workflow";
const hook = slackHook.create({
token: `slack:${channelId}`, // [!code highlight]
});
const message = await hook;
console.log(`Message from ${message.userId}: ${message.text}`);
}
```
`create()` accepts the same options as `createHook()`. If a newer run should replace one that still holds the token, pass [`experimental_force: true`](/docs/api-reference/workflow/create-hook#take-over-a-token-another-run-holds) to take the token over instead of getting [`HookConflictError`](/docs/api-reference/workflow-errors/hook-conflict-error).
## Related functions
- [`createHook()`](/docs/api-reference/workflow/create-hook): Create a hook in a workflow.
- [`resumeHook()`](/docs/api-reference/workflow-api/resume-hook): Resume a hook with a payload.