> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/medusajs/medusa/llms.txt
> Use this file to discover all available pages before exploring further.

# Workflows

> Learn about Medusa's workflow orchestration engine, including createWorkflow, createStep, compensation, and workflow composition patterns.

# Workflows

Workflows are Medusa's orchestration engine for building reliable, distributed transactions with automatic compensation (rollback). They compose multiple steps into a single, fault-tolerant operation.

## Why Workflows?

Workflows solve critical challenges in distributed commerce systems:

* **Automatic rollback** - Failed steps trigger compensation functions
* **Idempotency** - Steps can be safely retried
* **Composability** - Reuse steps across multiple workflows
* **Observability** - Track execution state and errors
* **Background execution** - Async steps for long-running operations

<Note>
  Workflows are the recommended way to implement business logic that spans multiple modules or requires transactional guarantees.
</Note>

## Creating a Step

Steps are the building blocks of workflows. Each step defines an action and optional compensation.

### Basic Step Pattern

```typescript theme={null}
import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"
import { IApiKeyModuleService } from "@medusajs/framework/types"
import { Modules } from "@medusajs/framework/utils"

export type CreateApiKeysStepInput = {
  api_keys: CreateApiKeyDTO[]
}

export const createApiKeysStep = createStep(
  "create-api-keys",
  async (data: CreateApiKeysStepInput, { container }) => {
    const service = container.resolve<IApiKeyModuleService>(Modules.API_KEY)
    const created = await service.createApiKeys(data.api_keys)
    
    return new StepResponse(
      created,
      created.map((apiKey) => apiKey.id)
    )
  },
  async (createdIds, { container }) => {
    if (!createdIds?.length) {
      return
    }

    const service = container.resolve<IApiKeyModuleService>(Modules.API_KEY)
    await service.deleteApiKeys(createdIds)
  }
)
```

Source: `packages/core/core-flows/src/api-key/steps/create-api-keys.ts:33-52`

### Step Response

The `StepResponse` constructor accepts two arguments:

1. **Output** - Data returned to the workflow
2. **Compensation data** - Data passed to the compensation function

```typescript theme={null}
return new StepResponse(
  { product, variants },  // Output to workflow
  { productId: product.id }  // Data for compensation
)
```

<Tip>
  Only include the minimum data needed for compensation (typically IDs). The compensation function receives this data, not the full output.
</Tip>

### Step Context

Steps receive a context object with:

* `container` - Dependency injection container
* `context` - Shared context (transaction metadata, user info)
* `transactionId` - Unique transaction identifier
* `idempotencyKey` - For idempotent execution

```typescript theme={null}
export const myStep = createStep(
  "my-step",
  async (input: MyInput, { container, context }) => {
    const service = container.resolve("myService")
    const result = await service.doSomething(input, context)
    return new StepResponse(result)
  }
)
```

## Creating a Workflow

Workflows compose steps into a transaction with automatic compensation.

### Basic Workflow Pattern

```typescript theme={null}
import {
  createWorkflow,
  WorkflowData,
  WorkflowResponse,
  createHook,
} from "@medusajs/framework/workflows-sdk"
import { createApiKeysStep } from "../steps"
import type { ApiKeyDTO, CreateApiKeyDTO } from "@medusajs/framework/types"

export type CreateApiKeysWorkflowInput = {
  api_keys: CreateApiKeyDTO[]
}

export const createApiKeysWorkflow = createWorkflow(
  "create-api-keys",
  (input: WorkflowData<CreateApiKeysWorkflowInput>) => {
    const apiKeys = createApiKeysStep(input)

    const apiKeysCreated = createHook("apiKeysCreated", {
      apiKeys,
    })

    return new WorkflowResponse(apiKeys, {
      hooks: [apiKeysCreated],
    })
  }
)
```

Source: `packages/core/core-flows/src/api-key/workflows/create-api-keys.ts:51-64`

### Executing Workflows

Workflows are executed by calling them with a container and running:

```typescript theme={null}
import { createApiKeysWorkflow } from "@medusajs/core-flows"

export const POST = async (
  req: AuthenticatedMedusaRequest,
  res: MedusaResponse
) => {
  const { result } = await createApiKeysWorkflow(req.scope).run({
    input: {
      api_keys: [
        {
          type: "publishable",
          title: "Storefront",
          created_by: req.auth_context.actor_id,
        },
      ],
    },
  })

  res.json({ api_keys: result })
}
```

## Workflow Composition

### Transform Data

Use `transform` to manipulate step outputs:

```typescript theme={null}
import { createWorkflow, transform, WorkflowResponse } from "@medusajs/framework/workflows-sdk"
import { createProductStep, createPricesStep } from "./steps"

export const myWorkflow = createWorkflow(
  "create-product-with-prices",
  (input: WorkflowData<WorkflowInput>) => {
    const product = createProductStep(input)

    const priceData = transform({ product, input }, ({ product, input }) => {
      return {
        product_id: product.id,
        prices: input.prices.map(price => ({
          ...price,
          product_id: product.id,
        })),
      }
    })

    const prices = createPricesStep(priceData)

    return new WorkflowResponse({ product, prices })
  }
)
```

Source: `packages/core/workflows-sdk/src/utils/composer/transform.ts:34-58`

<Warning>
  You cannot directly manipulate data in the workflow function. Always use `transform` to access runtime values.
</Warning>

### Parallel Execution

Use `parallelize` to run steps concurrently:

```typescript theme={null}
import { createWorkflow, parallelize, WorkflowResponse } from "@medusajs/framework/workflows-sdk"
import { createPricesStep, attachProductToSalesChannelStep } from "./steps"

export const myWorkflow = createWorkflow(
  "my-workflow",
  (input: WorkflowInput) => {
    const product = createProductStep(input)

    const [prices, productSalesChannel] = parallelize(
      createPricesStep(product),
      attachProductToSalesChannelStep(product)
    )

    return new WorkflowResponse({
      prices,
      productSalesChannel,
    })
  }
)
```

Source: `packages/core/workflows-sdk/src/utils/composer/parallelize.ts:11-42`

### Conditional Execution

Use `when-then` for conditional step execution:

```typescript theme={null}
import { createWorkflow, when, WorkflowResponse } from "@medusajs/framework/workflows-sdk"
import { isActiveStep, anotherStep } from "./steps"

export const workflow = createWorkflow(
  "conditional-workflow",
  (input: { is_active: boolean }) => {
    const result = when(
      input,
      (input) => input.is_active
    ).then(() => {
      return isActiveStep()
    })

    const anotherStepResult = anotherStep(result)

    return new WorkflowResponse(anotherStepResult)
  }
)
```

Source: `packages/core/workflows-sdk/src/utils/composer/when.ts:31-62`

<Note>
  You cannot use regular `if` statements in workflows because they evaluate at composition time, not runtime. Use `when-then` instead.
</Note>

## Workflow Hooks

Hooks emit events at specific points in workflow execution:

```typescript theme={null}
import { createWorkflow, createHook, WorkflowResponse } from "@medusajs/framework/workflows-sdk"

export const deletePromotionsWorkflow = createWorkflow(
  "delete-promotions",
  (input: WorkflowData<{ ids: string[] }>) => {
    const deletedPromotions = deletePromotionsStep(input.ids)
    
    const promotionsDeleted = createHook("promotionsDeleted", {
      ids: input.ids,
    })
    
    return new WorkflowResponse(deletedPromotions, {
      hooks: [promotionsDeleted],
    })
  }
)
```

Subscribe to hooks:

```typescript theme={null}
import { deletePromotionsWorkflow } from "@medusajs/core-flows"

deletePromotionsWorkflow.hooks.promotionsDeleted(({ ids }) => {
  console.log(`Deleted promotions:`, ids)
})
```

## Async Steps

Steps can be marked as async for background execution:

```typescript theme={null}
export const longRunningStep = createStep(
  {
    name: "long-running-step",
    async: true,  // Run in background
  },
  async (input: StepInput, { container }) => {
    // This step will be executed asynchronously
    const result = await performLongOperation(input)
    return new StepResponse(result)
  },
  async (data, { container }) => {
    // Compensation also runs async
    await rollbackOperation(data)
  }
)
```

<Tip>
  Async steps require a workflow engine module (Redis or in-memory) to be configured. They're ideal for operations like sending emails, processing images, or external API calls.
</Tip>

## Compensation Flow

When a step fails, all previous steps are compensated in reverse order:

<Steps>
  <Step title="Step 1 executes">
    Creates a product → Returns `product.id` as compensation data
  </Step>

  <Step title="Step 2 executes">
    Creates prices → Returns `price.ids` as compensation data
  </Step>

  <Step title="Step 3 fails">
    Fails to attach to sales channel → Triggers compensation
  </Step>

  <Step title="Step 2 compensates">
    Deletes prices using `price.ids`
  </Step>

  <Step title="Step 1 compensates">
    Deletes product using `product.id`
  </Step>
</Steps>

```typescript theme={null}
export const createProductStep = createStep(
  "create-product",
  async (data: CreateProductInput, { container }) => {
    const productModule = container.resolve(Modules.PRODUCT)
    const product = await productModule.createProducts(data)
    
    // Return product as output, product.id for compensation
    return new StepResponse(product, product.id)
  },
  async (productId, { container }) => {
    if (!productId) return
    
    const productModule = container.resolve(Modules.PRODUCT)
    // Compensation: delete the created product
    await productModule.deleteProducts([productId])
  }
)
```

## Running Workflows as Steps

Workflows can be composed as steps in other workflows:

```typescript theme={null}
import { createWorkflow, WorkflowResponse } from "@medusajs/framework/workflows-sdk"
import { createProductVariantsWorkflow } from "./create-product-variants"

export const createProductsWorkflow = createWorkflow(
  "create-products",
  (input: WorkflowInput) => {
    const products = createProductsStep(input.products)

    // Run another workflow as a step
    const variants = createProductVariantsWorkflow.runAsStep({
      input: {
        product_variants: input.variants,
      },
    })

    return new WorkflowResponse({ products, variants })
  }
)
```

Source: `packages/core/workflows-sdk/src/utils/composer/create-workflow.ts:203-296`

## Best Practices

<CardGroup cols={2}>
  <Card title="Keep Steps Focused">
    Each step should do one thing. Split complex operations into multiple steps.
  </Card>

  <Card title="Always Provide Compensation">
    Every step that changes state should have a compensation function.
  </Card>

  <Card title="Minimize Compensation Data">
    Only pass IDs or minimal data needed to rollback.
  </Card>

  <Card title="Use Transform for Data Shaping">
    Don't manipulate data directly in the workflow function.
  </Card>
</CardGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="Events" href="/concepts/events">
    Learn how to emit and subscribe to events
  </Card>

  <Card title="Services" href="/concepts/services">
    Build services that power your workflow steps
  </Card>
</CardGroup>
