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:
- Core workflow utilities:
packages/core/workflows-sdk/src/utils/composer/create-workflow.ts– Implementation ofcreateWorkflowand step composition. - Workflow examples (plugins):
packages/plugins/loyalty/src/workflows/gift-cards/workflows/create-gift-cards.ts– Full-featured workflow with steps, hooks, and compensation. - Workflow tests (reference):
packages/modules/workflow-engine-redis/integration-tests/__tests__/index.spec.ts– End-to-end tests that run real workflows against the Redis engine. - API-route pattern (admin):
packages/medusa/src/api/admin/orders/[id]/route.ts– CRUD route that triggers an order-related workflow. - API-route pattern (store):
packages/medusa/src/api/store/carts/[id]/complete/route.ts– Store-side route that finalizes a cart via a workflow. - Step with compensation:
packages/plugins/loyalty/src/workflows/store-credit/steps/credit-store-credit-account.ts– Production example of rollback logic.
Summary
- Workflows are defined with
createWorkflowin files underpackages/*/workflows/**, returning aWorkflowResponsethat 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →