How to Trigger a Medusa Workflow Programmatically: Complete Developer Guide
You trigger a Medusa workflow by importing the workflow factory from @medusajs/core-flows, instantiating it with the Medusa dependency injection container, and invoking the .run() method with your input data.
Medusa v2 introduces a modular workflow engine that orchestrates complex business processes across modules. Understanding how to trigger a Medusa workflow programmatically is essential for extending core functionality in API routes, custom services, or standalone scripts within the medusajs/medusa ecosystem.
Understanding the Workflow Factory Pattern
Medusa workflows are TypeScript functions exported from the @medusajs/core-flows package. When you import a workflow, you receive a factory function that expects the Medusa dependency injection (DI) container as its argument.
As implemented in medusajs/medusa, calling this factory with a valid container returns an execution object exposing the .run() method. This method triggers the actual workflow execution through the internal workflow orchestrator service and returns a promise resolving to { result, transaction, errors }.
How to Trigger a Medusa Workflow Programmatically in API Routes
Accessing the Container via req.scope
In HTTP route handlers, Medusa provides the DI container through req.scope. This is the standard pattern used throughout the core codebase, such as in packages/medusa/src/api/admin/orders/route.ts.
import { deleteOrderWorkflow } from "@medusajs/core-flows"
import {
AuthenticatedMedusaRequest,
MedusaResponse,
} from "@medusajs/framework/http"
export const DELETE = async (
req: AuthenticatedMedusaRequest,
res: MedusaResponse
) => {
const { id } = req.params
// `req.scope` is the Medusa DI container
const { result } = await deleteOrderWorkflow(req.scope).run({
input: { id },
})
res.status(200).json({ id, deleted: true, result })
}
How to Trigger a Medusa Workflow Programmatically in Services and Scripts
Importing the Global Container
For server-side code outside of HTTP contexts—such as in services, subscribers, or custom scripts—you can import the container directly from @medusajs/medusa.
import { container } from "@medusajs/medusa"
import { createGiftCardsWorkflow } from "@medusajs/core-flows"
async function createGiftCards() {
const workflow = createGiftCardsWorkflow(container)
const { result } = await workflow.run({
input: {
value: 5000,
currency: "usd",
},
})
console.log("Created gift cards:", result)
}
Alternative: Using the Workflow Orchestrator Service Directly
If you only know the workflow ID rather than having imported the factory, resolve the workflowOrchestratorService from the container. According to the source in packages/modules/workflow-engine-redis/src/utils/workflow-orchestrator-storage.ts (line 423), this service handles the actual execution logic.
import { container } from "@medusajs/medusa"
async function triggerWorkflowById() {
const orchestrator = container.resolve("workflowOrchestratorService")
const { result } = await orchestrator.run("create-gift-cards", {
input: { value: 1000, currency: "usd" },
})
console.log(result)
}
This approach is used internally when workflows need to be triggered dynamically by string identifiers rather than static imports.
Understanding the Workflow Execution Flow
When you invoke .run(), the workflow orchestrator registers a new execution, persists the state via the selected engine (Redis or in-memory), and processes each step according to the workflow definition.
Key implementation details from the source:
- The orchestrator service is defined in
packages/modules/workflow-engine-redis/src/services/workflow-orchestrator.ts - The storage helper at
packages/modules/workflow-engine-redis/src/utils/workflow-orchestrator-storage.tsmanages execution persistence and state management - Example usages appear throughout the repository, including the loyalty plugin at
packages/plugins/loyalty/src/workflows/hooks/after-order-credit-lines-created.ts
Summary
- Medusa workflows are factory functions from
@medusajs/core-flowsthat require the DI container to instantiate. - In API routes, use
req.scopeas the container argument toworkflowFactory(req.scope). - In scripts and services, import
containerfrom@medusajs/medusato instantiate workflows. - The
.run()method accepts aninputobject and returns{ result, transaction, errors }. - For dynamic execution by string ID, resolve
workflowOrchestratorServiceand call its.run()method.
Frequently Asked Questions
Can I trigger a Medusa workflow from a custom script outside the Medusa server?
Yes, provided you initialize the Medusa container with the necessary modules and database connection. Import the container from @medusajs/medusa, pass it to your workflow factory, then await the .run() method. This pattern works in custom CLI tools or scheduled jobs as long as the Medusa environment is properly bootstrapped.
What is the difference between using workflow.run() and the orchestrator service?
Using workflow.run() is the high-level approach where you import the specific workflow factory and instantiate it with a container. The orchestrator service (workflowOrchestratorService.run()) is the low-level implementation used internally; you should use it only when you need to trigger a workflow by its string ID (e.g., "delete-order") rather than by importing the typed factory function.
How do I handle errors when triggering a Medusa workflow programmatically?
The object returned by await workflow.run() includes an errors property containing any execution failures. Always destructure this property alongside result: const { result, errors } = await workflow.run({ input }). If errors is not empty, inspect the array for step-specific failure details before proceeding with business logic.
Where are Medusa workflow definitions located in the source code?
Built-in workflow definitions reside in the packages/core/core-flows directory of the medusajs/medusa repository. For example, the delete order workflow is defined in packages/core/core-flows/src/order/workflows/delete-order.ts. These are exported via the @medusajs/core-flows package for consumption in your application code.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →