> This page is for version v2026-02-09 (default).
> For other versions, use one of these documentation indexes:
> - v2026-02-09 (default): https://docs.extend.ai/2026-02-09/llms.txt
> - v2025-04-21: https://docs.extend.ai/2025-04-21/llms.txt
> - v2024-12-23: https://docs.extend.ai/2024-12-23/llms.txt

> ## Documentation Index
> Fetch the complete documentation index at: https://docs.extend.ai/llms.txt
> Use this file to discover all available pages before exploring further.
>
> ## API version
> The current API version is `2026-02-09`, served at the site root (no version prefix in URLs).
> If this page URL contains `/2025-04-21/` or `/2024-12-23/`, you are reading an older API version.
> Prefer the current docs at https://docs.extend.ai/llms.txt unless the user explicitly needs that older version.
> Do not treat older-version pages as the source of truth for new integrations.

# Router Step

> Configure ROUTER steps with ordered code or semantic branches using workflows.create, workflows.update, and workflowVersions.create, or Extend Studio. Read matched branches from workflowRuns.retrieve.

Router steps send each workflow run down the first branch whose condition matches. Use a Router when the workflow needs more than a pass/fail decision, such as routing invoices by value, document type, region, or exception category.

> **Note**
>
> Use Router for new branching logic. Existing legacy Conditional steps continue to work, but Composer chat does not create or edit them.

## How routing works

Each branch has a name and one predicate block. Like a [Validation step](/workflows/workflow-steps/validation-step), that block can use JavaScript code or an LLM-powered semantic check.

Branches run from top to bottom:

1. The Router evaluates the first branch.
2. If it matches, the run follows that branch and later branches are not evaluated.
3. If it does not match, the Router evaluates the next branch.
4. If no branch matches, the run follows the **Else** output. Leaving Else unconnected stops the workflow at the Router.

This makes branch order significant. Put the most specific or highest-priority conditions first.

## Via the API

Add a step with `type: "ROUTER"` to the workflow's `steps` array. Each entry in `config.branches` has a stable `id` and a code or semantic predicate in `block`. Connect a branch to its destination with `next[].branchId`.

### Define ordered branches and the fallback

This step assumes an upstream extractor named `extract_invoice` produces a numeric `total` and a `vendor_name`. The first branch uses code to select high-value invoices; the second uses a semantic check to select suspicious vendor names.

```json
{
  "name": "route_invoice",
  "type": "ROUTER",
  "config": {
    "branches": [
      {
        "id": "high_value",
        "name": "High value",
        "block": {
          "name": "High value threshold",
          "blockType": "code",
          "code": {
            "language": "javascript",
            "code": "const total = context.outputs.extract_invoice.total.value; return [total != null && total >= 10000, 'Review invoices of at least 10000'];"
          }
        }
      },
      {
        "id": "suspicious_vendor",
        "name": "Suspicious vendor",
        "block": {
          "name": "Vendor check",
          "blockType": "semantic",
          "semantic": {
            "promptText": "The vendor name {{ outputs.extract_invoice.vendor_name.value }} is suspicious or is a placeholder rather than a plausible company name."
          }
        }
      }
    ]
  },
  "next": [
    { "step": "finance_review", "branchId": "high_value" },
    { "step": "fraud_review", "branchId": "suspicious_vendor" },
    { "step": "send_response", "branchId": "otherwise" }
  ]
}
```

| Field              | How to use it                                                                                                                                                |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `name`             | A unique step name, from 1 to 255 characters. Use this name in upstream `next[].step` entries.                                                               |
| `config.branches`  | From 1 to 100 branches, in evaluation order. The first matching predicate selects its route.                                                                 |
| `branches[].id`    | Required, non-empty, and unique within this Router. Use the same value in `next[].branchId`. `"otherwise"` is reserved and cannot be a configured branch ID. |
| `branches[].name`  | Optional display name, up to 255 characters. Omit on update to preserve the existing name.                                                                   |
| `branches[].block` | A code or semantic block with `name`, `blockType`, and the matching `code` or `semantic` object. Block `id` is optional on input and generated when omitted. |
| `next[].step`      | The name of a downstream step in the same workflow.                                                                                                          |
| `next[].branchId`  | A configured branch ID, or `"otherwise"` for the **Else** route.                                                                                             |

Use `language: "javascript"` for code predicates, and return `true`, `false`, or `[boolean, "reason"]`. A route name is not a valid return value. Semantic predicates use a non-empty `semantic.promptText` with `{{ outputs.<step>.<field>.value }}` templates. Both address upstream steps by their public names; see [Code branches](#code-branches) and [Semantic branches](#semantic-branches) for context and template details.

The fallback is implicit: do not add an `otherwise` entry to `config.branches`. Add a `next` entry with `branchId: "otherwise"` to connect it. If no branch matches and this route is unconnected, processing stops at the Router. A predicate execution error fails the step instead of selecting the fallback.

### Save, deploy, and read results

1. Include this definition in the complete `steps` array when calling [Create Workflow](/api-reference/endpoints/workflow/create-workflow) (`workflows.create`) or [Update Workflow](/api-reference/endpoints/workflow/update-workflow) (`workflows.update`). Include the trigger, parse, upstream extraction, and every downstream step, and connect the upstream step to `route_invoice`. Updating `steps` replaces the entire draft step list.
2. To preserve an existing Router's branches, keep its step name and omit `config`. Providing `config.branches` replaces the branch list and its order. Keep branch IDs stable when renaming or reordering branches. Omitting `block` preserves an existing branch's predicate; a new branch with no `block` receives a default predicate that returns `true`, so supply a predicate for every new branch.
3. [Create a Workflow Version](/api-reference/endpoints/workflow/create-workflow-version) (`workflowVersions.create`) to deploy the draft. Passing `steps` deploys those definitions directly without modifying the draft.
4. [Run the workflow](/api-reference/endpoints/workflow/create-workflow-run), then [retrieve the run](/api-reference/endpoints/workflow/get-workflow-run) (`workflowRuns.retrieve`). Find the `stepRuns` entry with `stepType: "ROUTER"` and `step.name: "route_invoice"`.

The step's `result.matchedBranch` contains the selected branch's `id` and `name`, or `null` when no branch matched. `result.branches` contains only evaluated branches, in order, each with `id`, `name`, `matched`, and optional `reasoning` or `error`. Later branches are not evaluated after a match. The entire `result` is nullable for a step run without a result.

See [Router in the step reference](/workflows/configuring-workflows#router) for the configuration contract and deployment workflow.

## Via Extend Studio

### Build a Router with Composer chat

Open the workflow you want to edit in Extend Studio and open **Composer** in the workflow builder. The same chat panel can build Router branches and Validation blocks. You need workflow edit access for Composer to change the draft.

![Workflow builder showing the Composer chat panel beside step configuration, with the prompt box, approval mode, and Review and Undo controls](/_fern-img/de725e4f5f995240d2b9a2b628e2f6b1f69c7266edf9ed8b146478da7beb5d5a.webp)

1. Start a new chat with **+**, or continue the current conversation. Composer uses the workflow displayed in the builder.
2. Choose **Ask for approval** or **Edit automatically** beneath the message box. Ask for approval lets you inspect and accept a proposed update; Edit automatically applies changes to the draft while Composer works.
3. Name the upstream step, list the branches in evaluation order, choose code or semantic logic for each branch, and specify every destination, including **Else**. Name the upstream fields when you know them; Composer can inspect the available variables.

```text
After extract_invoice, add a Router named route_invoice with these branches in order:
1. High value: use code to match total values of at least 10,000 and route to finance_review.
2. Suspicious vendor: use a semantic check for suspicious or placeholder vendor_name values and route to fraud_review.
Route everything else through Else to send_response. Create any missing downstream review or response steps.
```

4. Open **Review** on the proposed update. Confirm the predicates, branch order, branch connections, and Else destination. Accept the proposal if you chose **Ask for approval**; use **Undo** to revert an applied change.
5. Refine individual branches in the same conversation without rebuilding the Router.

```text
Change the High value branch threshold to 25,000, then move Suspicious vendor above it. Keep the branch destinations and Else route unchanged.
```

6. Select the Router node and **Configure** to inspect the generated predicates. Test documents that match each branch and one that matches neither. Inspect the selected route before deploying the workflow.

The chat history menu lets you reopen a saved conversation to continue edits. See [Composer chat](/general/composer-chat) for permissions, approval modes, and history.

![Composer chat history menu showing saved workflow conversations and the search box for reopening a previous chat](/_fern-img/fe6f1f3f38663696f5a82d6ba4cab56cb22bfb4ca3992986ea5a5868822a1830.webp)

### Configure branches manually

1. Add a **Router** node and connect an upstream step to it.
2. Select **Configure**.
3. Name the first branch, choose **Code** or **Semantic**, and define when it should match.
4. Select **Add branch** for each additional route.
5. Drag branches into evaluation order.
6. On the workflow diagram, connect each named output and the **Else** output to downstream steps. Changes save automatically.

### Code branches

Return `true` to select the branch or `false` to continue to the next branch. Return `[boolean, "reason"]` to include an explanation in the run result.

```javascript
const total = context.outputs.extract_invoice.total.value;

if (total == null) return [false, "No invoice total"];
return [total >= 10000, `Invoice total is ${total}`];
```

Router code predicates accept boolean results only. Do not return a route name from the code—the matching branch already determines the route.

Router code uses the same context shape as [Validation code blocks](/workflows/workflow-steps/validation-step#code-blocks). In a multi-file workflow after a Collect step, use `context.outputs.<step>` for aggregate checks and `context.files[i].outputs.<step>` to associate an output with a specific file. The aggregate output arrays are not index-aligned with `context.files`, because routing may skip a step for some files.

### Semantic branches

Write a yes-or-no criterion and optionally insert upstream values with `{{ }}` templates. Variables inside the templates are formatted and queried using [JMESPath](https://jmespath.org/).

```text
The applicant names in {{ outputs.extract_loan_application.applicant_names.value }} match the borrower names on the promissory notes in {{ outputs.extract_mortgage_package.promissory_note_borrower_names.value }}.
```

A yes result selects the branch. A no result continues to the next branch.

### View run results

Open a completed workflow run and select the Router step. The result shows the selected route and each branch that was evaluated before the match. Code reasons and semantic reasoning appear with their branch results. If no branch matched, the result shows that the run followed Else.