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:

Summary

  • Medusa workflows are factory functions from @medusajs/core-flows that require the DI container to instantiate.
  • In API routes, use req.scope as the container argument to workflowFactory(req.scope).
  • In scripts and services, import container from @medusajs/medusa to instantiate workflows.
  • The .run() method accepts an input object and returns { result, transaction, errors }.
  • For dynamic execution by string ID, resolve workflowOrchestratorService and 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →