Skip to content

Commit 19ae4be

Browse files
authored
docs: improvements to the "Expose a Workflow Hook" documentation (medusajs#12992)
1 parent 7b1debf commit 19ae4be

File tree

3 files changed

+183
-53
lines changed

3 files changed

+183
-53
lines changed

www/apps/book/app/learn/fundamentals/workflows/add-workflow-hook/page.mdx

Lines changed: 64 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,21 @@ export const metadata = {
66

77
In this chapter, you'll learn how to expose a hook in your workflow.
88

9-
## When to Expose a Hook
9+
<Note>
1010

11-
<Note title="Expose workflow hooks when" type="success">
12-
13-
Your workflow is reusable in other applications, and you allow performing an external action at some point in your workflow.
11+
Refer to the [Workflow Hooks](../workflow-hooks/page.mdx) chapter to learn what a workflow hook is and how to consume Medusa's workflow hooks.
1412

1513
</Note>
1614

17-
<Note title="Don't expose workflow hooks if" type="error">
15+
## When to Expose a Hook?
1816

19-
Your workflow isn't reusable by other applications. Use a step that performs what a hook handler would instead.
17+
Medusa exposes hooks in many of its workflows to allow you to inject custom functionality.
2018

21-
</Note>
19+
You can also expose your own hooks in your workflows to allow other developers to consume them. This is useful when you're creating a workflow in a [plugin](../../plugins/page.mdx) and you want plugin users to extend the workflow's functionality.
20+
21+
For example, you are creating a blog plugin and you want to allow developers to perform custom validation before a blog post is created. You can expose a hook in your workflow that developers can consume to perform their custom validation.
22+
23+
If your workflow is not in a plugin, you probably don't need to expose a hook as you can perform the necessary actions directly in the workflow.
2224

2325
---
2426

@@ -29,62 +31,94 @@ To expose a hook in your workflow, use `createHook` from the Workflows SDK.
2931
For example:
3032

3133
export const hookHighlights = [
32-
["13", "createHook", "Add a hook to the workflow."],
33-
["14", `"productCreated"`, "The hook's name."],
34-
["15", "productId", "The data to pass to the hook handler."],
34+
["12", "createHook", "Add a hook to the workflow."],
35+
["13", `"validate"`, "The hook's name."],
36+
["14", "", "The data to pass to the hook handler."],
3537
["19", "hooks", "Return the list of hooks in the workflow."]
3638
]
3739

38-
```ts title="src/workflows/my-workflow/index.ts" highlights={hookHighlights}
40+
```ts title="src/workflows/create-blog-post/index.ts" highlights={hookHighlights}
3941
import {
4042
createStep,
4143
createHook,
4244
createWorkflow,
4345
WorkflowResponse,
4446
} from "@medusajs/framework/workflows-sdk"
45-
import { createProductStep } from "./steps/create-product"
47+
import { createPostStep } from "./steps/create-post"
4648

47-
export const myWorkflow = createWorkflow(
48-
"my-workflow",
49+
export const createBlogPostWorkflow = createWorkflow(
50+
"create-blog-post",
4951
function (input) {
50-
const product = createProductStep(input)
51-
const productCreatedHook = createHook(
52-
"productCreated",
53-
{ productId: product.id }
52+
const validate = createHook(
53+
"validate",
54+
{ post: input }
5455
)
56+
const post = createPostStep(input)
5557

56-
return new WorkflowResponse(product, {
57-
hooks: [productCreatedHook],
58+
return new WorkflowResponse(post, {
59+
hooks: [validate],
5860
})
5961
}
6062
)
6163
```
6264

6365
The `createHook` function accepts two parameters:
6466

65-
1. The first is a string indicating the hook's name. You use this to consume the hook later.
66-
2. The second is the input to pass to the hook handler.
67+
1. The first is a string indicating the hook's name. Developers consuming the hook will use this name to access the hook.
68+
2. The second is the input to pass to the hook handler. Developers consuming the hook will receive this input in the hook handler.
6769

68-
The workflow must also pass an object having a `hooks` property as a second parameter to the `WorkflowResponse` constructor. Its value is an array of the workflow's hooks.
70+
You must also return the hook in the workflow's response by passing a `hooks` property to the `WorkflowResponse`'s second parameter object. Its value is an array of the workflow's hooks.
6971

7072
### How to Consume the Hook?
7173

72-
To consume the hook of the workflow, create the file `src/workflows/hooks/my-workflow.ts` with the following content:
74+
To consume the hook of the workflow, create the file `src/workflows/hooks/create-blog-post.ts` with the following content:
7375

7476
export const handlerHighlights = [
75-
["3", "productCreated", "Invoke the hook, passing it a step function as a parameter."],
77+
["4", "validate", "Invoke the hook, passing it a step function as a parameter."],
7678
]
7779

78-
```ts title="src/workflows/hooks/my-workflow.ts" highlights={handlerHighlights}
79-
import { myWorkflow } from "../my-workflow"
80+
```ts title="src/workflows/hooks/create-blog-post.ts" highlights={handlerHighlights}
81+
import { MedusaError } from "@medusajs/framework/utils"
82+
import { createBlogPostWorkflow } from "../create-blog-post"
8083

81-
myWorkflow.hooks.productCreated(
82-
async ({ productId }, { container }) => {
84+
createBlogPostWorkflow.hooks.validate(
85+
async ({ post }, { container }) => {
8386
// TODO perform an action
87+
if (!post.additional_data.custom_title) {
88+
throw new MedusaError(
89+
MedusaError.Types.INVALID_DATA,
90+
"Custom title is required"
91+
)
92+
}
8493
}
8594
)
8695
```
8796

88-
The hook is available on the workflow's `hooks` property using its name `productCreated`.
97+
The hook is available on the workflow's `hooks` property using its name `validate`.
8998

9099
You invoke the hook, passing a step function (the hook handler) as a parameter.
100+
101+
The hook handler is essentially a step function. You can perform in it any actions you perform in a step. For example, you can throw an error, which would stop the workflow execution.
102+
103+
You can also access the Medusa container in the hook handler to perform actions like using Query or module services.
104+
105+
For example:
106+
107+
```ts title="src/workflows/hooks/create-blog-post.ts"
108+
import { createBlogPostWorkflow } from "../create-blog-post"
109+
110+
createBlogPostWorkflow.hooks.validate(
111+
async ({ post }, { container }) => {
112+
const query = container.resolve("query")
113+
114+
const { data: existingPosts } = await query.graph({
115+
entity: "post",
116+
fields: ["*"],
117+
})
118+
119+
// TODO do something with existing posts...
120+
}
121+
)
122+
```
123+
124+
Learn more about the hook handler in the [Workflow Hooks](../workflow-hooks/page.mdx) chapter.

www/apps/book/generated/edit-dates.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export const generatedEditDates = {
2323
"app/learn/fundamentals/data-models/page.mdx": "2025-03-18T07:55:56.252Z",
2424
"app/learn/fundamentals/modules/remote-link/page.mdx": "2024-09-30T08:43:53.127Z",
2525
"app/learn/fundamentals/api-routes/protected-routes/page.mdx": "2025-06-19T16:04:36.064Z",
26-
"app/learn/fundamentals/workflows/add-workflow-hook/page.mdx": "2024-12-09T14:42:39.693Z",
26+
"app/learn/fundamentals/workflows/add-workflow-hook/page.mdx": "2025-07-18T11:33:15.959Z",
2727
"app/learn/fundamentals/events-and-subscribers/data-payload/page.mdx": "2025-05-01T15:30:08.421Z",
2828
"app/learn/fundamentals/workflows/advanced-example/page.mdx": "2024-09-11T10:46:59.975Z",
2929
"app/learn/fundamentals/events-and-subscribers/emit-event/page.mdx": "2025-06-02T14:47:54.394Z",

www/apps/book/public/llms-full.txt

Lines changed: 118 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9504,6 +9504,64 @@ So, always rollback the migration before deleting it.
95049504

95059505
To learn more about the Medusa CLI's database commands, refer to [this CLI reference](https://docs.medusajs.com/resources/medusa-cli/commands/db/index.html.md).
95069506

9507+
***
9508+
9509+
## Data Migration Scripts
9510+
9511+
In some use cases, you may need to perform data migration after updates to the database. For example, after you added a `Site` data model to the Blog Module, you want to assign all existing posts and authors to a default site. Another example is updating data stored in a third-party system.
9512+
9513+
In those scenarios, you can instead create a data migration script. They are asynchronous function that the Medusa application executes once when you run the `npx medusa db:migrate command`.
9514+
9515+
### How to Create a Data Migration Script
9516+
9517+
You can create data migration scripts in a TypeScript or JavaScript file under the `src/migration-scripts` directory. The file must export an asynchronous function that will be executed when the `db:migrate` command is executed.
9518+
9519+
For example, to create a data migration script for the Blog Module example, create the file `src/migration-scripts/migrate-blog-data.ts` with the following content:
9520+
9521+
```ts title="src/migration-scripts/migrate-blog-data.ts" highlights={dataMigrationHighlights}
9522+
import { MedusaModule } from "@medusajs/framework/modules-sdk";
9523+
import { ExecArgs } from "@medusajs/framework/types";
9524+
import { BLOG_MODULE } from "../modules/blog";
9525+
import { createWorkflow } from "@medusajs/framework/workflows-sdk";
9526+
9527+
export default async function migrateBlogData({ container }: ExecArgs) {
9528+
// Check that the blog module exists
9529+
if (!MedusaModule.isInstalled(BLOG_MODULE)) {
9530+
return
9531+
}
9532+
9533+
await migrateBlogDataWorkflow(container).run({})
9534+
}
9535+
9536+
const migrateBlogDataWorkflow = createWorkflow(
9537+
"migrate-blog-data",
9538+
() => {
9539+
// Assuming you have these steps
9540+
createDefaultSiteStep()
9541+
9542+
assignBlogDataToSiteStep()
9543+
}
9544+
)
9545+
```
9546+
9547+
In the above example, you default export an asynchronous function that receives an object parameter with the [Medusa Container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md) property.
9548+
9549+
In the function, you first ensure that the Blog Module is installed to avoid errors otherwise. Then, you run a workflow that you've created in the same file that performs the necessary data migration.
9550+
9551+
#### Test Data Migration Script
9552+
9553+
To test out the data migration script, run the migration command:
9554+
9555+
```bash
9556+
npx medusa db:migrate
9557+
```
9558+
9559+
Medusa will run any pending migrations and migration scripts, including your script.
9560+
9561+
If the script runs successfully, Medusa won't run the script again.
9562+
9563+
If there are errors in the script, you'll receive an error in the migration script logs. Medusa will keep running the script every time you run the migration command until it runs successfully.
9564+
95079565

95089566
# Environment Variables
95099567

@@ -16008,11 +16066,17 @@ The next time you start the Medusa application, it will run this job every day a
1600816066

1600916067
In this chapter, you'll learn how to expose a hook in your workflow.
1601016068

16011-
## When to Expose a Hook
16069+
Refer to the [Workflow Hooks](https://docs.medusajs.com/learn/fundamentals/workflows/workflow-hooks/index.html.md) chapter to learn what a workflow hook is and how to consume Medusa's workflow hooks.
16070+
16071+
## When to Expose a Hook?
16072+
16073+
Medusa exposes hooks in many of its workflows to allow you to inject custom functionality.
1601216074

16013-
Your workflow is reusable in other applications, and you allow performing an external action at some point in your workflow.
16075+
You can also expose your own hooks in your workflows to allow other developers to consume them. This is useful when you're creating a workflow in a [plugin](https://docs.medusajs.com/learn/fundamentals/plugins/index.html.md) and you want plugin users to extend the workflow's functionality.
1601416076

16015-
Your workflow isn't reusable by other applications. Use a step that performs what a hook handler would instead.
16077+
For example, you are creating a blog plugin and you want to allow developers to perform custom validation before a blog post is created. You can expose a hook in your workflow that developers can consume to perform their custom validation.
16078+
16079+
If your workflow is not in a plugin, you probably don't need to expose a hook as you can perform the necessary actions directly in the workflow.
1601616080

1601716081
***
1601816082

@@ -16022,56 +16086,88 @@ To expose a hook in your workflow, use `createHook` from the Workflows SDK.
1602216086

1602316087
For example:
1602416088

16025-
```ts title="src/workflows/my-workflow/index.ts" highlights={hookHighlights}
16089+
```ts title="src/workflows/create-blog-post/index.ts" highlights={hookHighlights}
1602616090
import {
1602716091
createStep,
1602816092
createHook,
1602916093
createWorkflow,
1603016094
WorkflowResponse,
1603116095
} from "@medusajs/framework/workflows-sdk"
16032-
import { createProductStep } from "./steps/create-product"
16096+
import { createPostStep } from "./steps/create-post"
1603316097

16034-
export const myWorkflow = createWorkflow(
16035-
"my-workflow",
16098+
export const createBlogPostWorkflow = createWorkflow(
16099+
"create-blog-post",
1603616100
function (input) {
16037-
const product = createProductStep(input)
16038-
const productCreatedHook = createHook(
16039-
"productCreated",
16040-
{ productId: product.id }
16101+
const validate = createHook(
16102+
"validate",
16103+
{ post: input }
1604116104
)
16105+
const post = createPostStep(input)
1604216106

16043-
return new WorkflowResponse(product, {
16044-
hooks: [productCreatedHook],
16107+
return new WorkflowResponse(post, {
16108+
hooks: [validate],
1604516109
})
1604616110
}
1604716111
)
1604816112
```
1604916113

1605016114
The `createHook` function accepts two parameters:
1605116115

16052-
1. The first is a string indicating the hook's name. You use this to consume the hook later.
16053-
2. The second is the input to pass to the hook handler.
16116+
1. The first is a string indicating the hook's name. Developers consuming the hook will use this name to access the hook.
16117+
2. The second is the input to pass to the hook handler. Developers consuming the hook will receive this input in the hook handler.
1605416118

16055-
The workflow must also pass an object having a `hooks` property as a second parameter to the `WorkflowResponse` constructor. Its value is an array of the workflow's hooks.
16119+
You must also return the hook in the workflow's response by passing a `hooks` property to the `WorkflowResponse`'s second parameter object. Its value is an array of the workflow's hooks.
1605616120

1605716121
### How to Consume the Hook?
1605816122

16059-
To consume the hook of the workflow, create the file `src/workflows/hooks/my-workflow.ts` with the following content:
16123+
To consume the hook of the workflow, create the file `src/workflows/hooks/create-blog-post.ts` with the following content:
1606016124

16061-
```ts title="src/workflows/hooks/my-workflow.ts" highlights={handlerHighlights}
16062-
import { myWorkflow } from "../my-workflow"
16125+
```ts title="src/workflows/hooks/create-blog-post.ts" highlights={handlerHighlights}
16126+
import { MedusaError } from "@medusajs/framework/utils"
16127+
import { createBlogPostWorkflow } from "../create-blog-post"
1606316128

16064-
myWorkflow.hooks.productCreated(
16065-
async ({ productId }, { container }) => {
16129+
createBlogPostWorkflow.hooks.validate(
16130+
async ({ post }, { container }) => {
1606616131
// TODO perform an action
16132+
if (!post.additional_data.custom_title) {
16133+
throw new MedusaError(
16134+
MedusaError.Types.INVALID_DATA,
16135+
"Custom title is required"
16136+
)
16137+
}
1606716138
}
1606816139
)
1606916140
```
1607016141

16071-
The hook is available on the workflow's `hooks` property using its name `productCreated`.
16142+
The hook is available on the workflow's `hooks` property using its name `validate`.
1607216143

1607316144
You invoke the hook, passing a step function (the hook handler) as a parameter.
1607416145

16146+
The hook handler is essentially a step function. You can perform in it any actions you perform in a step. For example, you can throw an error, which would stop the workflow execution.
16147+
16148+
You can also access the Medusa container in the hook handler to perform actions like using Query or module services.
16149+
16150+
For example:
16151+
16152+
```ts title="src/workflows/hooks/create-blog-post.ts"
16153+
import { createBlogPostWorkflow } from "../create-blog-post"
16154+
16155+
createBlogPostWorkflow.hooks.validate(
16156+
async ({ post }, { container }) => {
16157+
const query = container.resolve("query")
16158+
16159+
const { data: existingPosts } = await query.graph({
16160+
entity: "post",
16161+
fields: ["*"],
16162+
})
16163+
16164+
// TODO do something with existing posts...
16165+
}
16166+
)
16167+
```
16168+
16169+
Learn more about the hook handler in the [Workflow Hooks](https://docs.medusajs.com/learn/fundamentals/workflows/workflow-hooks/index.html.md) chapter.
16170+
1607516171

1607616172
# Compensation Function
1607716173

0 commit comments

Comments
 (0)