Documentation Index Fetch the complete documentation index at: https://mintlify.com/medusajs/medusa/llms.txt
Use this file to discover all available pages before exploring further.
Workflows are composable, transactional business processes in Medusa. They allow you to build complex operations from reusable steps with automatic compensation (rollback) on failure.
What is a Workflow?
A workflow is:
A sequence of steps that execute in order
Transactional - all steps succeed or all are rolled back
Composable - workflows can call other workflows
Asynchronous - steps can run in the background
Type-safe - fully typed inputs and outputs
Creating Your First Workflow
Create a Step
Steps are the building blocks of workflows. Each step has an invocation function and optional compensation function: src/workflows/brand/steps/create-brand.ts
import { createStep , StepResponse } from "@medusajs/framework/workflows-sdk"
import { IBrandModuleService } from "../../../modules/brand/types"
import { BRAND_MODULE } from "../../../modules/brand"
export interface CreateBrandStepInput {
name : string
description ?: string
}
export const createBrandStep = createStep (
"create-brand" ,
async ( input : CreateBrandStepInput , { container }) => {
const brandService = container . resolve < IBrandModuleService >(
BRAND_MODULE
)
const brand = await brandService . createBrands ( input )
return new StepResponse (
brand ,
{ brandId: brand . id } // Compensation input
)
},
async ( compensationData , { container }) => {
if ( ! compensationData ) {
return
}
const brandService = container . resolve < IBrandModuleService >(
BRAND_MODULE
)
await brandService . deleteBrands ([ compensationData . brandId ])
}
)
The StepResponse takes two arguments:
The output of the step (returned to the workflow)
Data to pass to the compensation function if rollback is needed
Create the Workflow
Compose steps into a workflow using createWorkflow: src/workflows/brand/create-brand.ts
import {
createWorkflow ,
WorkflowData ,
WorkflowResponse ,
} from "@medusajs/framework/workflows-sdk"
import { createBrandStep } from "./steps/create-brand"
export interface CreateBrandWorkflowInput {
name : string
description ?: string
}
export const createBrandWorkflow = createWorkflow (
"create-brand" ,
( input : WorkflowData < CreateBrandWorkflowInput >) => {
const brand = createBrandStep ( input )
return new WorkflowResponse ( brand )
}
)
Use the Workflow
Execute the workflow from an API route or another workflow: src/api/admin/brands/route.ts
import { MedusaRequest , MedusaResponse } from "@medusajs/framework/http"
import { createBrandWorkflow } from "../../../workflows/brand/create-brand"
export async function POST (
req : MedusaRequest ,
res : MedusaResponse
) {
const { result : brand } = await createBrandWorkflow ( req . scope ). run ({
input: req . body ,
})
res . json ({ brand })
}
Advanced Workflow Patterns
Multiple Steps with Compensation
Here’s a complete example showing multiple steps with compensation:
src/workflows/brand/steps/delete-brand.ts
import type { IBrandModuleService } from "../../../modules/brand/types"
import { BRAND_MODULE } from "../../../modules/brand"
import { createStep , StepResponse } from "@medusajs/framework/workflows-sdk"
export const deleteBrandStep = createStep (
"delete-brand" ,
async ( ids : string [], { container }) => {
const brandService = container . resolve < IBrandModuleService >( BRAND_MODULE )
await brandService . softDeleteBrands ( ids )
return new StepResponse ( void 0 , ids )
},
async ( idsToRestore , { container }) => {
if ( ! idsToRestore ?. length ) {
return
}
const brandService = container . resolve < IBrandModuleService >( BRAND_MODULE )
await brandService . restoreBrands ( idsToRestore )
}
)
src/workflows/brand/delete-brand.ts
import {
createHook ,
createWorkflow ,
WorkflowData ,
WorkflowResponse ,
} from "@medusajs/framework/workflows-sdk"
import { deleteBrandStep } from "./steps/delete-brand"
export interface DeleteBrandWorkflowInput {
ids : string []
}
export const deleteBrandWorkflow = createWorkflow (
"delete-brand" ,
( input : WorkflowData < DeleteBrandWorkflowInput >) => {
const deletedBrands = deleteBrandStep ( input . ids )
const brandsDeleted = createHook ( "brandsDeleted" , {
ids: input . ids ,
})
return new WorkflowResponse ( deletedBrands , {
hooks: [ brandsDeleted ],
})
}
)
Use transform to manipulate data between steps:
import { transform } from "@medusajs/framework/workflows-sdk"
export const updateBrandWorkflow = createWorkflow (
"update-brand" ,
( input : WorkflowData < UpdateBrandInput >) => {
const brand = getBrandStep ( input . id )
const updateData = transform (
{ brand , input },
({ brand , input }) => ({
id: brand . id ,
name: input . name ?? brand . name ,
description: input . description ?? brand . description ,
})
)
const updatedBrand = updateBrandStep ( updateData )
return new WorkflowResponse ( updatedBrand )
}
)
Conditional Execution
Use when to conditionally execute steps:
import { when } from "@medusajs/framework/workflows-sdk"
export const processBrandWorkflow = createWorkflow (
"process-brand" ,
( input : WorkflowData < ProcessBrandInput >) => {
const brand = createBrandStep ( input )
when ({ brand }, ({ brand }) => {
return brand . logo_url === undefined
}). then (() => {
const defaultLogo = assignDefaultLogoStep ( brand . id )
})
return new WorkflowResponse ( brand )
}
)
Parallel Execution
Use parallelize to run independent steps concurrently:
import { parallelize } from "@medusajs/framework/workflows-sdk"
export const setupBrandWorkflow = createWorkflow (
"setup-brand" ,
( input : WorkflowData < SetupBrandInput >) => {
const brand = createBrandStep ( input . brand )
const [ products , collections ] = parallelize (
createProductsStep ( input . products ),
createCollectionsStep ( input . collections )
)
return new WorkflowResponse ({ brand , products , collections })
}
)
Query Data in Workflows
Use useQueryGraphStep to fetch data:
import { useQueryGraphStep } from "@medusajs/framework/workflows-sdk"
export const getBrandDetailWorkflow = createWorkflow (
"get-brand-detail" ,
( input : WorkflowData <{ id : string ; fields : string [] }>) => {
const { data : brands } = useQueryGraphStep ({
entity: "brand" ,
filters: { id: input . id },
fields: input . fields ,
})
const brand = transform ({ brands }, ({ brands }) => brands [ 0 ])
return new WorkflowResponse ( brand )
}
)
Step Context
The step context provides access to:
container - Dependency injection container
context - Shared context across steps
metadata - Additional metadata
idempotencyKey - Unique key for idempotent execution
export const myStep = createStep (
"my-step" ,
async ( input , { container , context , metadata }) => {
const logger = container . resolve ( "logger" )
logger . info ( "Executing step" , { context , metadata })
// Your logic here
}
)
Error Handling
Workflows automatically handle errors and trigger compensation:
import { MedusaError } from "@medusajs/framework/utils"
export const validateBrandStep = createStep (
"validate-brand" ,
async ( input : { name : string }) => {
if ( ! input . name || input . name . length < 2 ) {
throw new MedusaError (
MedusaError . Types . INVALID_DATA ,
"Brand name must be at least 2 characters"
)
}
return new StepResponse ({ valid: true })
}
)
When a step throws an error, all previously executed steps run their compensation functions in reverse order.
Workflow Hooks
Hooks allow you to emit events when a workflow completes:
import { createHook } from "@medusajs/framework/workflows-sdk"
export const createBrandWorkflow = createWorkflow (
"create-brand" ,
( input : WorkflowData < CreateBrandInput >) => {
const brand = createBrandStep ( input )
const brandCreated = createHook ( "brandCreated" , {
id: brand . id ,
})
return new WorkflowResponse ( brand , {
hooks: [ brandCreated ],
})
}
)
Subscribe to hooks in subscribers (see Event Subscribers ).
Best Practices
Keep steps focused on a single responsibility
Always provide compensation functions for steps that modify data
Use transform for data manipulation instead of complex logic in steps
Name steps and workflows descriptively
Type your inputs and outputs for type safety
Use parallelize for independent operations
Handle errors with proper MedusaError types
Test workflows in isolation
Next Steps
Create API Routes Build HTTP endpoints that use workflows
Event Subscribers React to workflow events and hooks