import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import DocCard from '@theme/DocCard';
import Icons from '@theme/Icon';

# Workflows Introduction

A workflow is a series of queries and actions that complete a task. Workflows are made up of a series of steps that interact with Medusa’s commerce modules, custom services, or external systems.

You construct a Workflow similar to how you create a JavaScript function, but unlike regular functions, a Medusa Workflow creates an internal representation of your steps. This makes it possible to keep track of your Workflow’s progress, automatically retry failing steps, and, if necessary, roll back steps.

Workflows can be used to define a flow with interactions across multiple systems, integrate third-party services into your commerce application, or automate actions within your application. Any flow with a series of steps can be implemented as a workflow.

## Example: Your First Workflow

The tools to build Workflows are installed by default in Medusa projects. For other Node.js projects, you can install the `@medusajs/workflows-sdk` package from npm.

### Create a Step

A workflow is made of a series of steps. A step is created using the `createStep` utility function.

Create the file `src/workflows/hello-world.ts` with the following content:

```ts title=src/workflows/hello-world.ts
import { 
  createStep,
  StepResponse,
} from "@medusajs/workflows-sdk"

const step1 = createStep("step-1", async () => {
  return new StepResponse(`Hello from step one!`)
})
```

This creates one step that returns a hello message.

### Create a Workflow

Next, you can create a workflow using the `createWorkflow` function:

```ts title=src/workflows/hello-world.ts
import {
  createStep, 
  StepResponse,
  createWorkflow,
} from "@medusajs/workflows-sdk"

type WorkflowOutput = {
  message: string;
};

const step1 = createStep("step-1", async () => {
  return new StepResponse(`Hello from step one!`)
})

const myWorkflow = createWorkflow<any, WorkflowOutput>(
  "hello-world",
  function () {
    const str1 = step1({})

    return {
      message: str1,
    }
  }
)
```

This creates a `hello-world` workflow. When you create a workflow, it’s constructed but not executed yet.

### Execute the Workflow

You can execute a workflow from different places within Medusa.

- Use API Routes if you want the workflow to execute in response to an API request or a webhook.
- Use Subscribers if you want to execute a workflow when an event is triggered.
- Use Scheduled Jobs if you want your workflow to execute on a regular schedule.

To execute the workflow, invoke it passing the Medusa container as a parameter, then use its `run` method:

<Tabs groupId="resource-type" isCodeTabs={true}>
  <TabItem value="api-route" label="API Route" attributes={{
    title: "src/api/store/custom/route.ts"
  }} default>

    ```ts
    import type { 
      MedusaRequest, 
      MedusaResponse
    } from "@medusajs/medusa";
    import myWorkflow from "../../../workflows/hello-world";

    export async function GET(
      req: MedusaRequest, 
      res: MedusaResponse
    ) {
      const { result } = await myWorkflow(req.scope)
        .run()
        
      res.send(result)
    }
    ```

  </TabItem>
  <TabItem value="subscribers" label="Subscribers" attributes={{
    title: "src/subscribers/create-customer.ts"
  }}>

    ```ts
    import { 
      type SubscriberConfig, 
      type SubscriberArgs,
      CustomerService,
      Customer,
    } from "@medusajs/medusa"
    import myWorkflow from "../workflows/hello-world"

    export default async function handleCustomerCreate({ 
      data, eventName, container, pluginOptions 
    }: SubscriberArgs<Customer>) {
      myWorkflow(container)
        .run()
        .then(({ result }) => {
          console.log(
            `New user: ${result.message}`
          )
        })
    }

    export const config: SubscriberConfig = {
      event: CustomerService.Events.CREATED,
      context: {
        subscriberId: "hello-customer"
      }
    }
    ```

  </TabItem>
  <TabItem value="job" label="Scheduled Job" attributes={{
    title: "src/jobs/message-daily.ts"
  }}>

    ```ts
    import { 
      type ScheduledJobConfig, 
      type ScheduledJobArgs,
    }  from "@medusajs/medusa"
    import myWorkflow from "../workflows/hello-world"

    export default async function handler({ 
      container, 
      data, 
      pluginOptions,
    }: ScheduledJobArgs) {
      myWorkflow(container)
        .run()
        .then(({ result }) => {
          console.log(
            result.message
          )
        })
    }

    export const config: ScheduledJobConfig = {
      name: "run-once-a-day",
      schedule: "0 0 * * *",
      data: {},
    }
    ```

  </TabItem>
</Tabs>

If you run your backend and trigger the execution of the workflow (based on where you’re executing it), you should see the message `Hello from step one!`.

### Pass Inputs to Steps

Steps in a workflow can accept parameters.

For example, create a new step that accepts an input and returns a message with that input:

```ts title=src/workflows/hello-world.ts
type WorkflowInput = {
  name: string;
};

const step2 = createStep(
  "step-2", 
  async ({ name }: WorkflowInput) => {
    return new StepResponse(`Hello ${name} from step two!`)
  }
)
```

Then, update the workflow to accept input and pass it to the new step:

```ts title=src/workflows/hello-world.ts
import {
  // previous imports 
  transform,
} from "@medusajs/workflows-sdk"

// ...

const myWorkflow = createWorkflow<
  WorkflowInput, WorkflowOutput
>(
  "hello-world",
  function (input) {
    const str1 = step1({})
    const str2 = step2(input)

    const result = transform(
      {
        str1,
        str2,
      },
      (input) => ({
        message: `${input.str1}\n${input.str2}`,
      })
    )

    return result
  }
)
```

Notice that to use the results of the steps, you must use the `transform` utility function. It gives you access to the real-time results of the steps once the workflow is executed.

If you execute the workflow again, you’ll see:

- A `Hello from step one!` message, indicating that step one ran first.
- A `Hello {name} from step two` message, indicating that step two ran after.

### Add Error Handling

Errors can occur in a workflow. To avoid data inconsistency, you can pass a compensation function as a third parameter to the `createStep` function.

The compensation function only runs if an error occurs throughout the Workflow. It’s useful to undo or roll back actions you’ve performed in a step.

For example, change step one to add a compensation function and step two to throw an error:

```ts title=src/workflows/hello-world.ts
const step1 = createStep("step-1", async () => {
  const message = `Hello from step one!`
    
  console.log(message)

  return new StepResponse(message)
}, async () => {
  console.log("Oops! Rolling back my changes...")
})

const step2 = createStep(
  "step-2", 
  async ({ name }: WorkflowInput) => {
    throw new Error("Throwing an error...")
  }
)
```

If you execute the Workflow, you should see:

- `Hello from step one!` logged, indicating that the first step ran successfully.
- `Oops! Rolling back my changes...` logged, indicating that the second step failed and the compensation function of the first step ran consequently.

:::note[Try it Out]

You can try out this guide on [Stackblitz](https://stackblitz.com/edit/stackblitz-starters-etznpy?file=compensation-demo.ts&view=editor).

:::

---

## More Advanced Example

Let’s cover a more realistic example.

For example, you can build a workflow that updates a product’s CMS details both in Medusa and an external CMS service:

```ts
import { createWorkflow } from "@medusajs/workflows-sdk"
import { Product } from "@medusajs/medusa" 
import { updateProduct, sendProductDataToCms } from "./steps"

type WorkflowInput = {
  id: string
  title: string,
  description: string,
  images: string[]
}

const updateProductCmsWorkflow = createWorkflow<
  WorkflowInput, 
  Product
>("update-product-cms", function (input) {
    const product = updateProduct(input)
    sendProductDataToCms(product)

    return product
})
```

As these steps are making changes to data in the Medusa backend and a third-party service, it’s useful to provide a compensation function for each step that rolls back the changes.

For example, you can pass a compensation function to the `updateProduct` step that reverts the product update in case an error occurs:

```ts
const updateProduct = createStep(
  "update-product", 
  async (input: WorkflowInput, context) => {
    const productService: ProductService = 
      context.container.resolve("productService")
    
    const { id, ...updateData } = input
    const previousProductData = await productService.retrieve(
      id, {
        select: ["title", "description", "images"],
      }
    )
    const product = productService.update(id, updateData)

    return new StepResponse(product, {
      id,
      previousProductData,
    })
  }, async ({ id, previousProductData }, context) => {
    const productService: ProductService = 
      context.container.resolve("productService")

    productService.update(id, previousProductData)
  }
)
```

Your steps may interact with external systems. For example, the `sendProductDataToCms` step communicates with an external CMS service. With the error handling and roll-back features that workflows provide, developers can ensure data delivery between multiple systems in their stack.

---

## Constraints on Workflow Constructor Function

The Workflow Builder, `createWorkflow`, comes with a set of constraints:

- The function passed to the `createWorkflow` can’t be an arrow function:

```ts
// Don't
const myWorkflow = createWorkflow<
  WorkflowInput, 
  WorkflowOutput
>("hello-world", (input) => {
  // ...
  }
)

// Do
const myWorkflow = createWorkflow<WorkflowInput, WorkflowOutput>
  ("hello-world", function (input) {
  // ...
  }
)
```

- The function passed to the `createWorkflow` can’t be an asynchronous function.
- Since the constructor function only defines how the workflow works, you can’t directly manipulate data within the function. To do that, you must use the `transform` function:

```ts
1// Don't
const myWorkflow = createWorkflow<
  WorkflowInput, 
  WorkflowOutput
>("hello-world", function (input) {
    const str1 = step1(input)
    const str2 = step2(input)
    
    return {
      message: `${input.str1}${input.str2}`,
    }
  }
)

// Do
const myWorkflow = createWorkflow<
  WorkflowInput, 
  WorkflowOutput
>("hello-world", function (input) {
    const str1 = step1(input)
    const str2 = step2(input)
    
    const result = transform({
      str1,
      str2,
    }, (input) => ({
      message: `${input.str1}${input.str2}`,
    }))

    return result
  }
) 
```

---

## Next Steps

<DocCard item={{
    type: 'link',
    href: '/references/workflows',
    label: 'Workflows API Reference',
    customProps: {
      icon: Icons['academic-cap-solid'],
      description: 'Learn about workflow\'s utility methods.'
    }
  }}
/>
