Skip to main content
A workflow is the orchestration layer. It coordinates a business operation across one or more modules as a series of steps, with automatic rollback (compensation) when any step fails. Every mutation in Mercur runs inside a workflow. This is the single most important rule in the architecture.
All mutations go through a workflow. API routes, subscribers, and scheduled jobs never write to the database directly. They run a workflow. That is what gives every mutation validation, event emission, and rollback.
Workflows are a Medusa framework primitive. This page focuses on the constraints and conventions that trip people, and agents, up.

The composition function is not normal JavaScript

The function you pass to createWorkflow is a composition function. It runs once at build time to wire steps together. It does not execute your business logic at request time. Because of that, it has hard constraints.
Inside a createWorkflow composition function you must not:
  • use async / await
  • use arrow functions for the composition body (use a named function)
  • use if / else, for, while, or try/catch
  • use new Date(), Math.random(), or any non-deterministic call
  • access properties of a step’s output directly (e.g. result.id)
These run at composition time, not execution time, so they either do nothing useful or break replay and rollback.
Anything that looks like normal logic goes into a step (for side effects) or a transform (for shaping data between steps):
src/workflows/create-brands.ts
Need a conditional or a computed value? Use transform to derive data, when to run a step conditionally, and put date, random, or id generation inside a step. Never branch in the composition body itself.

One mutation per step and compensation

A step is the unit of work and the unit of rollback. The rule: each step performs a single mutation and defines how to undo it. createStep takes an invoke function and a compensation function. The invoke returns a StepResponse whose second argument is the data the compensation needs.
src/workflows/steps/create-brands.ts
When a later step in the workflow throws, Medusa runs the compensation functions of the already-completed steps in reverse. The createBrands above is undone by deleteBrands. Splitting mutations one per step is what makes this reliable: a step that does two writes can only half-compensate.

Reuse built-in steps

Don’t hand-roll what the framework already ships. Medusa’s core-flows exports composable steps you should reuse instead of writing your own:
Prefer runAsStep over duplicating logic. When two workflows need the same sequence, extract it into its own workflow and call it as a step from both. You get one place to maintain, and correct compensation for free.

Hooks let others extend your workflow

Expose extension points with createHook so consumers can inject behaviour such as validation or side effects without forking the workflow. Add a validate hook before the mutation and a brandsCreated hook after it:
Consumers register a handler on the hook to run custom logic at that point. This is the sanctioned way to extend a workflow. See Extend a workflow.

The query engine

Reads inside a step, and anywhere else, go through Query, the graph engine that resolves data across modules and links. Resolve it from the container and call query.graph:
Query is for reads. Never try to mutate through it, and never resolve another module’s service inside a step to read its data. Go through Query so module isolation and links are respected.

Checklist for a workflow

  • Composition function is a named function, with no async/await, if, loops, try/catch, new Date(), or step-output property access.
  • Data shaping between steps uses transform; conditional steps use when.
  • Each step does exactly one mutation and defines a compensation function.
  • StepResponse passes the compensation the data it needs to undo the work.
  • Built-in steps (createRemoteLinkStep, emitEventStep) and runAsStep are reused instead of reimplemented.
  • Reads use query.graph; no cross-module service calls.
  • Extension points are exposed as hooks, not by forking.

Next steps

Extend a workflow

Register handlers on a workflow’s hooks to add behaviour without forking.

Best practices overview

See how workflows fit the wider Mercur architecture.