docs: improvements to the "Expose a Workflow Hook" documentation (#12992)
This commit is contained in:
@@ -6,19 +6,21 @@ export const metadata = {
|
|||||||
|
|
||||||
In this chapter, you'll learn how to expose a hook in your workflow.
|
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">
|
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.
|
||||||
|
|
||||||
Your workflow is reusable in other applications, and you allow performing an external action at some point in your workflow.
|
|
||||||
|
|
||||||
</Note>
|
</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:
|
For example:
|
||||||
|
|
||||||
export const hookHighlights = [
|
export const hookHighlights = [
|
||||||
["13", "createHook", "Add a hook to the workflow."],
|
["12", "createHook", "Add a hook to the workflow."],
|
||||||
["14", `"productCreated"`, "The hook's name."],
|
["13", `"validate"`, "The hook's name."],
|
||||||
["15", "productId", "The data to pass to the hook handler."],
|
["14", "", "The data to pass to the hook handler."],
|
||||||
["19", "hooks", "Return the list of hooks in the workflow."]
|
["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 {
|
import {
|
||||||
createStep,
|
createStep,
|
||||||
createHook,
|
createHook,
|
||||||
createWorkflow,
|
createWorkflow,
|
||||||
WorkflowResponse,
|
WorkflowResponse,
|
||||||
} from "@medusajs/framework/workflows-sdk"
|
} from "@medusajs/framework/workflows-sdk"
|
||||||
import { createProductStep } from "./steps/create-product"
|
import { createPostStep } from "./steps/create-post"
|
||||||
|
|
||||||
export const myWorkflow = createWorkflow(
|
export const createBlogPostWorkflow = createWorkflow(
|
||||||
"my-workflow",
|
"create-blog-post",
|
||||||
function (input) {
|
function (input) {
|
||||||
const product = createProductStep(input)
|
const validate = createHook(
|
||||||
const productCreatedHook = createHook(
|
"validate",
|
||||||
"productCreated",
|
{ post: input }
|
||||||
{ productId: product.id }
|
|
||||||
)
|
)
|
||||||
|
const post = createPostStep(input)
|
||||||
|
|
||||||
return new WorkflowResponse(product, {
|
return new WorkflowResponse(post, {
|
||||||
hooks: [productCreatedHook],
|
hooks: [validate],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -62,29 +64,61 @@ export const myWorkflow = createWorkflow(
|
|||||||
|
|
||||||
The `createHook` function accepts two parameters:
|
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.
|
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.
|
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?
|
### 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 = [
|
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}
|
```ts title="src/workflows/hooks/create-blog-post.ts" highlights={handlerHighlights}
|
||||||
import { myWorkflow } from "../my-workflow"
|
import { MedusaError } from "@medusajs/framework/utils"
|
||||||
|
import { createBlogPostWorkflow } from "../create-blog-post"
|
||||||
|
|
||||||
myWorkflow.hooks.productCreated(
|
createBlogPostWorkflow.hooks.validate(
|
||||||
async ({ productId }, { container }) => {
|
async ({ post }, { container }) => {
|
||||||
// TODO perform an action
|
// 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.
|
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.
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export const generatedEditDates = {
|
|||||||
"app/learn/fundamentals/data-models/page.mdx": "2025-03-18T07:55:56.252Z",
|
"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/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/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/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/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",
|
"app/learn/fundamentals/events-and-subscribers/emit-event/page.mdx": "2025-06-02T14:47:54.394Z",
|
||||||
|
|||||||
@@ -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).
|
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
|
# 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.
|
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:
|
For example:
|
||||||
|
|
||||||
```ts title="src/workflows/my-workflow/index.ts" highlights={hookHighlights}
|
```ts title="src/workflows/create-blog-post/index.ts" highlights={hookHighlights}
|
||||||
import {
|
import {
|
||||||
createStep,
|
createStep,
|
||||||
createHook,
|
createHook,
|
||||||
createWorkflow,
|
createWorkflow,
|
||||||
WorkflowResponse,
|
WorkflowResponse,
|
||||||
} from "@medusajs/framework/workflows-sdk"
|
} from "@medusajs/framework/workflows-sdk"
|
||||||
import { createProductStep } from "./steps/create-product"
|
import { createPostStep } from "./steps/create-post"
|
||||||
|
|
||||||
export const myWorkflow = createWorkflow(
|
export const createBlogPostWorkflow = createWorkflow(
|
||||||
"my-workflow",
|
"create-blog-post",
|
||||||
function (input) {
|
function (input) {
|
||||||
const product = createProductStep(input)
|
const validate = createHook(
|
||||||
const productCreatedHook = createHook(
|
"validate",
|
||||||
"productCreated",
|
{ post: input }
|
||||||
{ productId: product.id }
|
|
||||||
)
|
)
|
||||||
|
const post = createPostStep(input)
|
||||||
|
|
||||||
return new WorkflowResponse(product, {
|
return new WorkflowResponse(post, {
|
||||||
hooks: [productCreatedHook],
|
hooks: [validate],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -16049,29 +16113,61 @@ export const myWorkflow = createWorkflow(
|
|||||||
|
|
||||||
The `createHook` function accepts two parameters:
|
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.
|
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.
|
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?
|
### 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}
|
```ts title="src/workflows/hooks/create-blog-post.ts" highlights={handlerHighlights}
|
||||||
import { myWorkflow } from "../my-workflow"
|
import { MedusaError } from "@medusajs/framework/utils"
|
||||||
|
import { createBlogPostWorkflow } from "../create-blog-post"
|
||||||
|
|
||||||
myWorkflow.hooks.productCreated(
|
createBlogPostWorkflow.hooks.validate(
|
||||||
async ({ productId }, { container }) => {
|
async ({ post }, { container }) => {
|
||||||
// TODO perform an action
|
// 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.
|
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
|
# Compensation Function
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user