# How to Trigger a Medusa Workflow Programmatically: Complete Developer Guide

> Trigger Medusa workflows programmatically using the workflow factory and dependency injection. Learn how to run custom workflows with this developer guide.

- Repository: [Medusa/medusa](https://github.com/medusajs/medusa)
- Tags: how-to-guide
- Published: 2026-05-19

---

**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`](https://github.com/medusajs/medusa/blob/main/packages/medusa/src/api/admin/orders/route.ts).

```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`.

```ts
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`](https://github.com/medusajs/medusa/blob/main/packages/modules/workflow-engine-redis/src/utils/workflow-orchestrator-storage.ts) (line 423), this service handles the actual execution logic.

```ts
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`](https://github.com/medusajs/medusa/blob/main/packages/modules/workflow-engine-redis/src/services/workflow-orchestrator.ts)
- The storage helper at [`packages/modules/workflow-engine-redis/src/utils/workflow-orchestrator-storage.ts`](https://github.com/medusajs/medusa/blob/main/packages/modules/workflow-engine-redis/src/utils/workflow-orchestrator-storage.ts) manages 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`](https://github.com/medusajs/medusa/blob/main/packages/plugins/loyalty/src/workflows/hooks/after-order-credit-lines-created.ts)

## 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`](https://github.com/medusajs/medusa/blob/main/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.