Where to Find Medusa Workflow and API Route Implementations: A Complete Guide

You can find production-ready Medusa workflow and API route implementations in the official medusajs/medusa repository under packages/plugins/loyalty, packages/core/core-flows, and packages/medusa/src/api, which demonstrate patterns using createWorkflow, createStep, and thin HTTP route wrappers.

Medusa is an open-source ecommerce framework that uses a sophisticated workflow engine to handle complex business logic. Understanding where to locate real-world examples of Medusa workflow and API route implementations within the source code helps developers build custom commerce features correctly. This guide maps the essential file locations in the repository and breaks down the architectural patterns you will encounter.

Understanding Medusa's Workflow Architecture

The workflow engine lives in @medusajs/framework/workflows-sdk. All concrete workflows are built using the createWorkflow helper (and its companion createStep), while API routes follow a thin-wrapper pattern in packages/medusa/src/api.

Core Workflow Engine Location

The foundation of every workflow starts in packages/core/workflows-sdk/src/utils/composer/create-workflow.ts. This file exports createWorkflow, which accepts a unique identifier string and a function containing your business logic. Workflows return a WorkflowResponse instance that can register hooks and pass data between steps.

Real-World Workflow Implementation Examples

The repository ships dozens of production-ready examples in the plugins and core-flows packages. These demonstrate how to structure multi-step operations with compensation logic.

Store Credit Workflow Definition

The loyalty plugin provides a clear example of workflow composition. In packages/plugins/loyalty/src/workflows/store-credit/workflows/credit-store-credit-account.ts, the workflow wraps business logic with hooks for extensibility:

import { createWorkflow, WorkflowResponse } from "@medusajs/framework/workflows-sdk"

export const creditStoreCreditAccountWorkflow = createWorkflow(
  "credit-store-credit-account",
  (input: { accountId: string; amount: number }) => {
    // Example step: load the store‑credit service & apply credit
    const credit = creditStoreCreditAccountStep(input.accountId, input.amount)

    // Hook emitted after successful credit
    const hook = createHook("storeCreditCredited", { accountId: input.accountId })

    // The workflow returns a typed response and registers the hook
    return new WorkflowResponse(credit, { hooks: [hook] })
  }
)

This demonstrates the core pattern: createWorkflow(id, fn) → WorkflowResponse. The workflow is later invoked from an API route or another workflow.

Step Implementation with Compensation

Individual steps are defined using createStep in packages/plugins/loyalty/src/workflows/store-credit/steps/credit-store-credit-account.ts. This file shows how to implement idempotent steps with optional rollback logic:

import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"

export const creditStoreCreditAccountStep = createStep(
  "credit-store-credit-account",
  async (accountId: string, amount: number, { container }) => {
    const storeCreditService = container.resolve(Modules.STORE_CREDIT)
    await storeCreditService.credit(accountId, amount)
    return new StepResponse(void 0, { accountId })
  },
  // Compensation (rollback) – optional
  async (compensationData, { container }) => {
    const { accountId } = compensationData ?? {}
    if (!accountId) return
    const storeCreditService = container.resolve(Modules.STORE_CREDIT)
    await storeCreditService.debit(accountId, amount) // reverse the credit
  }
)

The compensation function (second argument to createStep) automatically runs if the workflow fails, ensuring data consistency.

Conditional and Nested Workflows

For complex business rules, you can implement conditional branching using the when utility. The integration test fixtures in packages/modules/workflow-engine-redis/integration-tests/__fixtures__/workflow_when.ts demonstrate nested workflows:

import { createWorkflow, when } from "@medusajs/framework/workflows-sdk"

export const parentWorkflow = createWorkflow("parent", (input) => {
  const sub = when(input.runSub, (i) => {
    return createWorkflow("sub", () => {
      // sub‑workflow logic
    })
  })

  return { subResult: sub }
})

This pattern allows you to conditionally execute sub-workflows based on runtime input, keeping your logic modular and readable.

API Route Implementation Patterns

Medusa API routes are thin wrappers that extract HTTP parameters, invoke workflows through the dependency injection container (req.scope), and return JSON responses.

Admin API Routes Invoking Workflows

Admin routes typically use AuthenticatedMedusaRequest and trigger workflows to perform write operations. The store credit endpoint in packages/medusa/src/api/admin/store-credit-accounts/[id]/credit/route.ts shows the standard pattern:

import {
  AuthenticatedMedusaRequest,
  MedusaResponse,
} from "@medusajs/framework/http"
import { creditStoreCreditAccountWorkflow } from "@medusajs/loyalty"

export const POST = async (
  req: AuthenticatedMedusaRequest<{ amount: number }>,
  res: MedusaResponse
) => {
  const { id } = req.params
  const { amount } = req.body

  // Run the workflow via the DI container (req.scope)
  await creditStoreCreditAccountWorkflow(req.scope).run({
    input: { accountId: id, amount },
  })

  res.status(200).json({ id, credited: true, amount })
}

The key interaction occurs via creditStoreCreditAccountWorkflow(req.scope).run(), which accesses the Medusa container to resolve services and execute steps.

Storefront API Routes

Storefront routes follow the same structure but use MedusaRequest instead of the authenticated variant. The products listing in packages/medusa/src/api/store/products/route.ts illustrates a simple read operation:

import {
  MedusaRequest,
  MedusaResponse,
} from "@medusajs/framework/http"
import { listProducts } from "@medusajs/medusa"

export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
  const products = await listProducts(req.scope, {
    limit: req.query.limit,
    offset: req.query.offset,
  })

  res.json({ products, count: products.length })
}

This demonstrates the basic route skeleton used across the entire api/store namespace, where req.scope provides access to Medusa's service layer.

Workflow Execution Endpoints

Medusa also provides generic administrative endpoints for manual workflow execution. The route at packages/medusa/src/api/admin/workflows-executions/[workflow_id]/run/route.ts allows you to trigger any registered workflow via HTTP, which is useful for debugging or administrative operations.

Key Source Files to Explore

To deepen your understanding of Medusa workflow and API route implementations, explore these specific files in the develop branch:

Summary

  • Workflows are defined with createWorkflow in files under packages/*/workflows/**, returning a WorkflowResponse that can include hooks.
  • Steps are atomic units created via createStep, optionally accepting a compensation function for rollback scenarios.
  • API routes are thin HTTP wrappers that import request/response types, extract parameters, invoke workflows through req.scope, and return JSON.
  • The admin "workflow-executions" endpoints let you manually trigger or inspect any workflow, providing an excellent testing interface.
  • Production examples in the loyalty plugin demonstrate real-world patterns for store credit, gift cards, and points systems.

Frequently Asked Questions

Where is the main workflow engine code located in the Medusa repository?

The main workflow engine code resides in packages/core/workflows-sdk, specifically in src/utils/composer/create-workflow.ts. This package exports createWorkflow, createStep, WorkflowResponse, and StepResponse, which form the foundation of Medusa's business logic orchestration.

How do I invoke a Medusa workflow from an API route?

You invoke a workflow by calling the workflow function with req.scope as the first argument, then chaining .run() with your input data. For example: await myWorkflow(req.scope).run({ input: { id: "example" } }). The req.scope object is Medusa's dependency injection container that resolves services required by the workflow steps.

What is the purpose of the compensation function in createStep?

The compensation function (the second argument to createStep) acts as a rollback mechanism. If any step in a workflow fails after this step has completed, Medusa automatically executes the compensation function to undo the step's changes. This pattern ensures data consistency during distributed transactions, such as reversing a store credit if a subsequent inventory check fails.

Can I find examples of conditional workflow execution?

Yes, the integration test fixtures in packages/modules/workflow-engine-redis/integration-tests/__fixtures__/workflow_when.ts demonstrate the when utility for conditional branching. This allows you to execute sub-workflows or specific steps only when certain input conditions are met, which is essential for implementing complex business rules without cluttering your main workflow logic.

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 →