DocsWorkflowsControl Flow

Control Flow in Workflows: Branching, Merging, and Conditions

When you create a multi-step process, you may need to run steps in parallel, chain them sequentially, or follow different paths based on outcomes. This page describes how you can manage branching, merging, and conditions to construct workflows that meet your logic requirements. The code snippets show the key patterns for structuring complex control flow.

Parallel Execution

You can run multiple steps at the same time if they don’t depend on each other. This approach can speed up your workflow when steps perform independent tasks. The code below shows how to add two steps in parallel:

myWorkflow.step(fetchUserData).step(fetchOrderData);

See the Parallel Steps example for more details.

Sequential Execution

Sometimes you need to run steps in strict order to ensure outputs from one step become inputs for the next. Use .then() to link dependent operations. The code below shows how to chain steps sequentially:

myWorkflow.step(fetchOrderData).then(validateData).then(processOrder);

See the Sequential Steps example for more details.

Branching and Merging Paths

When different outcomes require different paths, branching is helpful. You can also merge paths later once they complete. The code below shows how to branch after stepA and later converge on stepF:

myWorkflow
  .step(stepA)
    .then(stepB)
    .then(stepD)
  .after(stepA)
    .step(stepC)
    .then(stepE)
  .after([stepD, stepE])
    .step(stepF);

In this example:

  • stepA leads to stepB, then to stepD.
  • Separately, stepA also triggers stepC, which in turn leads to stepE.
  • The workflow waits for both stepD and stepE to finish before proceeding to stepF.

See the Branching Paths example for more details.

Cyclical Dependencies

You can loop back to earlier steps based on conditions, allowing you to repeat tasks until certain results are achieved. The code below shows a workflow that repeats fetchData when a status is “retry”:

myWorkflow
  .step(fetchData)
  .then(processData)
  .after(processData)
  .step(finalizeData, {
    when: { "processData.status": "success" },
  })
  .step(fetchData, {
    when: { "processData.status": "retry" },
  });

If processData returns “success,” finalizeData runs. If it returns “retry,” the workflow loops back to fetchData.

See the Cyclical Dependencies example for more details.

Conditions

Use the when property to control whether a step runs based on data from previous steps. Below are three ways to specify conditions.

Option 1: Function

myWorkflow.step(
  new Step({
    id: "processData",
    execute: async ({ context }) => {
      // Action logic
    },
  }),
  {
    when: async ({ context }) => {
      const fetchData = context?.getStepPayload<{ status: string }>("fetchData");
      return fetchData?.status === "success";
    },
  },
);

Option 2: Query Object

myWorkflow.step(
  new Step({
    id: "processData",
    execute: async ({ context }) => {
      // Action logic
    },
  }),
  {
    when: {
      ref: {
        step: {
          id: "fetchData",
        },
        path: "status",
      },
      query: { $eq: "success" },
    },
  },
);

Option 3: Simple Path Comparison

myWorkflow.step(
  new Step({
    id: "processData",
    execute: async ({ context }) => {
      // Action logic
    },
  }),
  {
    when: {
      "fetchData.status": "success",
    },
  },
);

Accessing Previous Step Results

Steps access data from previous steps through the context object. The context contains a record of all step results and their payloads.

Using getStepPayload

getStepPayload retrieves a step’s output with type safety:

workflow.step(
  new Step({
    id: "processOrder",
    execute: async ({ context }) => {
      const userData = context.getStepPayload<{ userId: string }>("fetchUser");
      return {
        userId: userData?.userId,
        status: "processing"
      };
    },
  })
);

Using Path Notation

Path notation accesses step results through the machine context. For example, to access the status of the processOrder step:

workflow.step(
  new Step({
    id: "sendEmail",
    execute: async ({ context }) => {
      const orderStatus = context.steps.processOrder.payload.status;
      console.log(orderStatus);
    },
  })
);

The context object maintains type information when used with TypeScript. Nested objects in step outputs can be accessed with either method.