2.10. Workflows

In this chapter, you’ll learn about workflows and how to define and execute them.

What is a Workflow?#

In other commerce platforms, it's difficult to implement commerce features where a single task performs operations on core and custom data models, or connects to third-party systems. It's also almost impossible to maintain their execution, handle errors gracefully, and have more control over how these tasks are executed.

Medusa solves this major pain point with workflows. A workflow is a series of queries and actions, called steps, that complete a task. You construct a workflow similar to how you create a JavaScript function.

However, unlike regular functions, workflows:

  • Create an internal representation of your steps, allowing you to track them and their progress.
  • Support defining roll-back logic for each step, so that you can handle errors gracefully and your data remain consistent across systems.
  • Perform long actions asynchronously, giving you control over when a step starts and finishes.

You implement all custom flows within workflows, then execute them from API routes, subscribers, and scheduled jobs.


How to Create and Execute a Workflow?#

1. Create the Steps#

A workflow is made of a series of steps. A step is created using the createStep utility function imported from @medusajs/framework/workflows-sdk.

Create the file src/workflows/hello-world.ts with the following content:

src/workflows/hello-world.ts
1import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"2
3const step1 = createStep(4  "step-1", 5  async () => {6    return new StepResponse(`Hello from step one!`)7  }8)

The createStep function accepts the step's unique name as a first parameter, and the step's function as a second parameter.

Steps must return an instance of StepResponse, whose parameter is the data to return to the workflow executing the step.

Steps can accept input parameters. For example, add the following to src/workflows/hello-world.ts:

src/workflows/hello-world.ts
1type WorkflowInput = {2  name: string3}4
5const step2 = createStep(6  "step-2", 7  async ({ name }: WorkflowInput) => {8    return new StepResponse(`Hello ${name} from step two!`)9  }10)

This adds another step whose function accepts as a parameter an object with a name property.

2. Create a Workflow#

Next, add the following to the same file to create the workflow using the createWorkflow function:

src/workflows/hello-world.ts
1import {2  // other imports...3  createWorkflow,4  WorkflowResponse,5} from "@medusajs/framework/workflows-sdk"6
7// ...8
9const myWorkflow = createWorkflow(10  "hello-world",11  function (input: WorkflowInput) {12    const str1 = step1()13    // to pass input14    const str2 = step2(input)15
16    return new WorkflowResponse({17      message: str2,18    })19  }20)21
22export default myWorkflow

The createWorkflow function accepts the workflow's unique name as a first parameter, and the workflow's function as a second parameter. The workflow can accept input which is passed as a parameter to the function.

The workflow must return an instance of WorkflowResponse, whose parameter is returned to workflow executors.

3. Execute the Workflow#

You can execute a workflow from different customizations:

  • Execute in an API route to expose the workflow's functionalities to clients.
  • Execute in a subscriber to use the workflow's functionalities when a commerce operation is performed.
  • Execute in a scheduled job to run the workflow's functionalities automatically at a specified repeated interval.

To execute the workflow, invoke it passing the Medusa container as a parameter. Then, use its run method:

4. Test Workflow#

To test out your workflow, start your Medusa application:

Then, if you added the API route above, send a GET request to /workflow:

Code
curl http://localhost:9000/workflow

You’ll receive the following response:

Example Response
1{2  "message": "Hello John from step two!"3}

Access Medusa Container in Workflow Steps#

A step receives an object as a second parameter with configurations and context-related properties. One of these properties is the container property, which is the Medusa container to allow you to resolve framework and commerce tools in your application.

For example, consider you want to implement a workflow that returns the total products in your application. Create the file src/workflows/product-count.ts with the following content:

src/workflows/product-count.ts
8
9const getProductCountStep = createStep(10  "get-product-count", 11  async (_, { container }) => {12    const productModuleService = container.resolve(Modules.PRODUCT)13
14    const [, count] = await productModuleService.listAndCountProducts()15
16    return new StepResponse(count)17  }18)19
20const productCountWorkflow = createWorkflow(21  "product-count",22  function () {23    const count = getProductCountStep()24
25    return new WorkflowResponse({26      count,27    })28  }29)30
31export default productCountWorkflow

In getProductCountStep, you use the container to resolve the Product Module's main service. Then, you use its listAndCountProducts method to retrieve the total count of products and return it in the step's response. You then execute this step in the productCountWorkflow.

You can now execute this workflow in a custom API route, scheduled job, or subscriber to get the total count of products.

TipFind a full list of the registered resources in the Medusa container and their registration key in this reference . You can use these resources in your custom workflows.
Was this chapter helpful?
Edit this page