docs: improvements to the "Expose a Workflow Hook" documentation (#12992)

This commit is contained in:
Shahed Nasser
2025-07-18 15:33:24 +03:00
committed by GitHub
parent 7b1debfe12
commit 19ae4beafa
3 changed files with 183 additions and 53 deletions

View File

@@ -6,19 +6,21 @@ export const metadata = {
In this chapter, you'll learn how to expose a hook in your workflow.
## When to Expose a Hook
<Note>
<Note title="Expose workflow hooks when" type="success">
Your workflow is reusable in other applications, and you allow performing an external action at some point in your workflow.
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.
</Note>
<Note title="Don't expose workflow hooks if" type="error">
## When to Expose a Hook?
Your workflow isn't reusable by other applications. Use a step that performs what a hook handler would instead.
Medusa exposes hooks in many of its workflows to allow you to inject custom functionality.
</Note>
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.
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.
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.
---
@@ -29,32 +31,32 @@ To expose a hook in your workflow, use `createHook` from the Workflows SDK.
For example:
export const hookHighlights = [
["13", "createHook", "Add a hook to the workflow."],
["14", `"productCreated"`, "The hook's name."],
["15", "productId", "The data to pass to the hook handler."],
["12", "createHook", "Add a hook to the workflow."],
["13", `"validate"`, "The hook's name."],
["14", "", "The data to pass to the hook handler."],
["19", "hooks", "Return the list of hooks in the workflow."]
]
```ts title="src/workflows/my-workflow/index.ts" highlights={hookHighlights}
```ts title="src/workflows/create-blog-post/index.ts" highlights={hookHighlights}
import {
createStep,
createHook,
createWorkflow,
WorkflowResponse,
} from "@medusajs/framework/workflows-sdk"
import { createProductStep } from "./steps/create-product"
import { createPostStep } from "./steps/create-post"
export const myWorkflow = createWorkflow(
"my-workflow",
export const createBlogPostWorkflow = createWorkflow(
"create-blog-post",
function (input) {
const product = createProductStep(input)
const productCreatedHook = createHook(
"productCreated",
{ productId: product.id }
const validate = createHook(
"validate",
{ post: input }
)
const post = createPostStep(input)
return new WorkflowResponse(product, {
hooks: [productCreatedHook],
return new WorkflowResponse(post, {
hooks: [validate],
})
}
)
@@ -62,29 +64,61 @@ export const myWorkflow = createWorkflow(
The `createHook` function accepts two parameters:
1. The first is a string indicating the hook's name. You use this to consume the hook later.
2. The second is the input to pass to the hook handler.
1. The first is a string indicating the hook's name. Developers consuming the hook will use this name to access the hook.
2. The second is the input to pass to the hook handler. Developers consuming the hook will receive this input in the hook handler.
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.
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.
### How to Consume the Hook?
To consume the hook of the workflow, create the file `src/workflows/hooks/my-workflow.ts` with the following content:
To consume the hook of the workflow, create the file `src/workflows/hooks/create-blog-post.ts` with the following content:
export const handlerHighlights = [
["3", "productCreated", "Invoke the hook, passing it a step function as a parameter."],
["4", "validate", "Invoke the hook, passing it a step function as a parameter."],
]
```ts title="src/workflows/hooks/my-workflow.ts" highlights={handlerHighlights}
import { myWorkflow } from "../my-workflow"
```ts title="src/workflows/hooks/create-blog-post.ts" highlights={handlerHighlights}
import { MedusaError } from "@medusajs/framework/utils"
import { createBlogPostWorkflow } from "../create-blog-post"
myWorkflow.hooks.productCreated(
async ({ productId }, { container }) => {
createBlogPostWorkflow.hooks.validate(
async ({ post }, { container }) => {
// TODO perform an action
if (!post.additional_data.custom_title) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
"Custom title is required"
)
}
}
)
```
The hook is available on the workflow's `hooks` property using its name `productCreated`.
The hook is available on the workflow's `hooks` property using its name `validate`.
You invoke the hook, passing a step function (the hook handler) as a parameter.
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.
You can also access the Medusa container in the hook handler to perform actions like using Query or module services.
For example:
```ts title="src/workflows/hooks/create-blog-post.ts"
import { createBlogPostWorkflow } from "../create-blog-post"
createBlogPostWorkflow.hooks.validate(
async ({ post }, { container }) => {
const query = container.resolve("query")
const { data: existingPosts } = await query.graph({
entity: "post",
fields: ["*"],
})
// TODO do something with existing posts...
}
)
```
Learn more about the hook handler in the [Workflow Hooks](../workflow-hooks/page.mdx) chapter.

View File

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

View File

@@ -9504,6 +9504,64 @@ So, always rollback the migration before deleting it.
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).
***
## Data Migration Scripts
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.
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`.
### How to Create a Data Migration Script
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.
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:
```ts title="src/migration-scripts/migrate-blog-data.ts" highlights={dataMigrationHighlights}
import { MedusaModule } from "@medusajs/framework/modules-sdk";
import { ExecArgs } from "@medusajs/framework/types";
import { BLOG_MODULE } from "../modules/blog";
import { createWorkflow } from "@medusajs/framework/workflows-sdk";
export default async function migrateBlogData({ container }: ExecArgs) {
// Check that the blog module exists
if (!MedusaModule.isInstalled(BLOG_MODULE)) {
return
}
await migrateBlogDataWorkflow(container).run({})
}
const migrateBlogDataWorkflow = createWorkflow(
"migrate-blog-data",
() => {
// Assuming you have these steps
createDefaultSiteStep()
assignBlogDataToSiteStep()
}
)
```
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.
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.
#### Test Data Migration Script
To test out the data migration script, run the migration command:
```bash
npx medusa db:migrate
```
Medusa will run any pending migrations and migration scripts, including your script.
If the script runs successfully, Medusa won't run the script again.
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.
# Environment Variables
@@ -16008,11 +16066,17 @@ The next time you start the Medusa application, it will run this job every day a
In this chapter, you'll learn how to expose a hook in your workflow.
## When to Expose a Hook
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.
Your workflow is reusable in other applications, and you allow performing an external action at some point in your workflow.
## When to Expose a Hook?
Your workflow isn't reusable by other applications. Use a step that performs what a hook handler would instead.
Medusa exposes hooks in many of its workflows to allow you to inject custom functionality.
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.
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.
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.
***
@@ -16022,26 +16086,26 @@ To expose a hook in your workflow, use `createHook` from the Workflows SDK.
For example:
```ts title="src/workflows/my-workflow/index.ts" highlights={hookHighlights}
```ts title="src/workflows/create-blog-post/index.ts" highlights={hookHighlights}
import {
createStep,
createHook,
createWorkflow,
WorkflowResponse,
} from "@medusajs/framework/workflows-sdk"
import { createProductStep } from "./steps/create-product"
import { createPostStep } from "./steps/create-post"
export const myWorkflow = createWorkflow(
"my-workflow",
export const createBlogPostWorkflow = createWorkflow(
"create-blog-post",
function (input) {
const product = createProductStep(input)
const productCreatedHook = createHook(
"productCreated",
{ productId: product.id }
const validate = createHook(
"validate",
{ post: input }
)
const post = createPostStep(input)
return new WorkflowResponse(product, {
hooks: [productCreatedHook],
return new WorkflowResponse(post, {
hooks: [validate],
})
}
)
@@ -16049,29 +16113,61 @@ export const myWorkflow = createWorkflow(
The `createHook` function accepts two parameters:
1. The first is a string indicating the hook's name. You use this to consume the hook later.
2. The second is the input to pass to the hook handler.
1. The first is a string indicating the hook's name. Developers consuming the hook will use this name to access the hook.
2. The second is the input to pass to the hook handler. Developers consuming the hook will receive this input in the hook handler.
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.
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.
### How to Consume the Hook?
To consume the hook of the workflow, create the file `src/workflows/hooks/my-workflow.ts` with the following content:
To consume the hook of the workflow, create the file `src/workflows/hooks/create-blog-post.ts` with the following content:
```ts title="src/workflows/hooks/my-workflow.ts" highlights={handlerHighlights}
import { myWorkflow } from "../my-workflow"
```ts title="src/workflows/hooks/create-blog-post.ts" highlights={handlerHighlights}
import { MedusaError } from "@medusajs/framework/utils"
import { createBlogPostWorkflow } from "../create-blog-post"
myWorkflow.hooks.productCreated(
async ({ productId }, { container }) => {
createBlogPostWorkflow.hooks.validate(
async ({ post }, { container }) => {
// TODO perform an action
if (!post.additional_data.custom_title) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
"Custom title is required"
)
}
}
)
```
The hook is available on the workflow's `hooks` property using its name `productCreated`.
The hook is available on the workflow's `hooks` property using its name `validate`.
You invoke the hook, passing a step function (the hook handler) as a parameter.
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.
You can also access the Medusa container in the hook handler to perform actions like using Query or module services.
For example:
```ts title="src/workflows/hooks/create-blog-post.ts"
import { createBlogPostWorkflow } from "../create-blog-post"
createBlogPostWorkflow.hooks.validate(
async ({ post }, { container }) => {
const query = container.resolve("query")
const { data: existingPosts } = await query.graph({
entity: "post",
fields: ["*"],
})
// TODO do something with existing posts...
}
)
```
Learn more about the hook handler in the [Workflow Hooks](https://docs.medusajs.com/learn/fundamentals/workflows/workflow-hooks/index.html.md) chapter.
# Compensation Function