> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lerian.studio/llms.txt
> Use this file to discover all available pages before exploring further.

# Workflow Design Guide

> Design Flowker workflows with confidence — node types, edges, real-world patterns, status transitions, and best practices for reliable orchestration.

Design workflows in Flowker with clarity and control. This guide walks through node types, edges, real-world patterns, status transitions, technical limits, and best practices to help you build reliable and maintainable orchestrations.

## Node types

***

Every workflow is built from nodes. Each node has a `type` that defines how Flowker processes it at runtime.

### trigger

The entry point of a workflow. Every workflow must start with a trigger node. When execution begins, the engine enters through this node and starts routing from it.

```json theme={null}
{
  "id": "node-trigger",
  "type": "trigger",
  "name": "Payment Received"
}
```

### executor

Calls an external executor, an HTTP service, API, or provider registered in Flowker. This is the core building block for integrating with fraud engines, payment providers, notification services, and other external systems.

```json theme={null}
{
  "id": "node-fraud-check",
  "type": "executor",
  "name": "Check Fraud Score"
}
```

### conditional

Evaluates an expression against the execution context and routes to different branches based on the result. Use conditional nodes to implement branching logic, for example, routing to an approval path when risk is high, or continuing directly when it is low.

```json theme={null}
{
  "id": "node-risk-decision",
  "type": "conditional",
  "name": "Evaluate Risk Score"
}
```

### action

Represents internal workflow operations that do not involve external calls, such as transforming data, emitting events, or recording state changes.

```json theme={null}
{
  "id": "node-record-approval",
  "type": "action",
  "name": "Record Approval Decision"
}
```

## Edges

***

Edges connect nodes and define execution paths. Each edge includes the following fields:

| Field          | Description                                                        |
| -------------- | ------------------------------------------------------------------ |
| `id`           | Unique identifier for the edge.                                    |
| `source`       | ID of the origin node.                                             |
| `target`       | ID of the destination node.                                        |
| `sourceHandle` | Output port of the source node where the edge originates.          |
| `condition`    | Expression that must evaluate to true for the edge to be followed. |
| `label`        | Human-readable label used for visualization and debugging.         |

### Example edge

```json theme={null}
{
  "id": "edge-approved",
  "source": "node-risk-decision",
  "target": "node-process-payment",
  "sourceHandle": "approved",
  "condition": "node-risk-decision.riskScore < 70",
  "label": "Approved"
}
```

The `condition` field is evaluated against the execution context. If omitted, the edge is always followed when the source node completes successfully.

## Status transitions

***

Workflows follow a well-defined lifecycle. Understanding these transitions is key to safely deploying and evolving workflows.

<Frame>
  <img src="https://mintcdn.com/lerian-49cb71fc/n9r3fc-KWmGUuljH/images/en/docs/flowker-workflow-status-transitions.jpg?fit=max&auto=format&n=n9r3fc-KWmGUuljH&q=85&s=5bb2d4c11ac8da12299327bbd987603f" alt="Workflow status transition diagram showing three states: draft, active, and inactive. An arrow labeled 'activate' points from draft to active. An arrow labeled 'deactivate' points from active to inactive. An arrow labeled 'draft' points from inactive back to draft." width="2892" height="540" data-path="images/en/docs/flowker-workflow-status-transitions.jpg" />
</Frame>

* **draft** — The initial state. All modifications (adding nodes, editing edges, changing configuration) are only allowed in `draft` status.
* **active** — A workflow that has been activated. It can be executed. No modifications are permitted while active.
* **inactive** — A workflow that has been deactivated. It can no longer be executed, but it can be moved back to `draft` for editing.

### Rules

* Only a `draft` workflow can be activated (transition: `draft → active`).
* Only an `active` workflow can be deactivated (transition: `active → inactive`).
* Only an `inactive` workflow can be moved back to draft (transition: `inactive → draft`).
* Attempting an invalid transition returns error `FLK-0102`.
* Attempting to modify a workflow that is not in `draft` returns error `FLK-0103`.

### Moving an inactive workflow back to draft

If you deactivated a workflow and want to edit it again, move it back to `draft` by calling [`POST /v1/workflows/{id}/draft`](/en/reference/flowker/move-workflow-to-draft). This makes the workflow editable without needing to clone it.

This is useful when you deactivated a workflow by mistake or when you want to iterate on an existing workflow instead of creating a copy.

<Note>
  Only inactive workflows can be moved to draft. If you need to modify an active workflow without taking it offline, use the clone approach described below.
</Note>

### Iterating safely with clone

To modify an active workflow, clone it first. Cloning creates a new `draft` from any status, copying all nodes and edges. You can then update, test, and activate it without impacting the current version.

This is the recommended approach for production versioning.

## Technical limits

***

| Limit                           | Value | Error code |
| ------------------------------- | ----- | ---------- |
| Maximum nodes per workflow      | 100   | `FLK-0113` |
| Maximum edges per workflow      | 200   | `FLK-0114` |
| Maximum execution input payload | 1 MB  | `FLK-0506` |

Keep these limits in mind when designing complex flows. Workflows with more than \~50 nodes usually indicate that the flow should be split into smaller, composable workflows.

## Common patterns

***

### Sequential

The simplest pattern. Nodes execute in a linear sequence. Use this when each step depends on the previous one and no branching is required.

<Frame>
  <img src="https://mintcdn.com/lerian-49cb71fc/n9r3fc-KWmGUuljH/images/en/docs/flowker-pattern-sequential.jpg?fit=max&auto=format&n=n9r3fc-KWmGUuljH&q=85&s=d053d9e731e17353ceaab92abd51c182" alt="Sequential workflow pattern: a trigger node connects to a first executor node, which connects to a second executor node, which connects to a third executor node. All connections are single directed arrows forming a straight line." width="3220" height="485" data-path="images/en/docs/flowker-pattern-sequential.jpg" />
</Frame>

**Example: Payment orchestration**

```json theme={null}
{
  "nodes": [
    { "id": "n1", "type": "trigger",  "name": "Payment Initiated" },
    { "id": "n2", "type": "executor", "name": "Validate Payment Data" },
    { "id": "n3", "type": "executor", "name": "Route to Provider" },
    { "id": "n4", "type": "executor", "name": "Send Confirmation Notification" }
  ],
  "edges": [
    { "id": "e1", "source": "n1", "target": "n2", "label": "Start" },
    { "id": "e2", "source": "n2", "target": "n3", "label": "Valid" },
    { "id": "e3", "source": "n3", "target": "n4", "label": "Routed" }
  ]
}
```

### Conditional branching

A `conditional` node evaluates an expression and routes execution accordingly. Each outgoing edge carries its own `condition`.

<Frame>
  <img src="https://mintcdn.com/lerian-49cb71fc/n9r3fc-KWmGUuljH/images/en/docs/flowker-pattern-conditional.jpg?fit=max&auto=format&n=n9r3fc-KWmGUuljH&q=85&s=c2ff57d7fb3543b851b884214d0d4883" alt="Conditional branching workflow pattern: a trigger node connects to an executor node, which connects to a conditional node. The conditional node has two outgoing arrows: one labeled 'Path A' pointing to a first executor node, and one labeled 'Path B' pointing to a second executor node." width="2736" height="571" data-path="images/en/docs/flowker-pattern-conditional.jpg" />
</Frame>

**Example: Anti-fraud check**

```json theme={null}
{
  "nodes": [
    { "id": "n1", "type": "trigger",     "name": "Transaction Received" },
    { "id": "n2", "type": "executor",    "name": "Get Fraud Score" },
    { "id": "n3", "type": "conditional", "name": "Evaluate Score" },
    { "id": "n4", "type": "executor",    "name": "Approve Transaction" },
    { "id": "n5", "type": "executor",    "name": "Reject Transaction" }
  ],
  "edges": [
    { "id": "e1", "source": "n1", "target": "n2", "label": "Start" },
    { "id": "e2", "source": "n2", "target": "n3", "label": "Score received" },
    {
      "id": "e3",
      "source": "n3",
      "target": "n4",
      "sourceHandle": "approved",
      "condition": "n2.fraudScore < 70",
      "label": "Approved"
    },
    {
      "id": "e4",
      "source": "n3",
      "target": "n5",
      "sourceHandle": "rejected",
      "condition": "n2.fraudScore >= 70",
      "label": "Rejected"
    }
  ]
}
```

## Real-world examples

***

### Anti-fraud check

A transaction arrives, a fraud score is retrieved, and execution is routed to approval or rejection.

```json theme={null}
{
  "nodes": [
    { "id": "n1", "type": "trigger",     "name": "Transaction Received" },
    { "id": "n2", "type": "executor",    "name": "Get Fraud Score" },
    { "id": "n3", "type": "conditional", "name": "Evaluate Fraud Score" },
    { "id": "n4", "type": "executor",    "name": "Approve Transaction" },
    { "id": "n5", "type": "executor",    "name": "Reject and Notify" }
  ],
  "edges": [
    { "id": "e1", "source": "n1", "target": "n2", "label": "Start" },
    { "id": "e2", "source": "n2", "target": "n3", "label": "Score received" },
    {
      "id": "e3",
      "source": "n3",
      "target": "n4",
      "sourceHandle": "approved",
      "condition": "n2.fraudScore < 70",
      "label": "Low risk"
    },
    {
      "id": "e4",
      "source": "n3",
      "target": "n5",
      "sourceHandle": "rejected",
      "condition": "n2.fraudScore >= 70",
      "label": "High risk"
    }
  ]
}
```

### Payment orchestration

A linear flow that validates incoming payment data, routes it to the appropriate provider, and sends a confirmation.

```json theme={null}
{
  "nodes": [
    { "id": "n1", "type": "trigger",  "name": "Payment Initiated" },
    { "id": "n2", "type": "executor", "name": "Validate Payment Data" },
    { "id": "n3", "type": "executor", "name": "Route to Payment Provider" },
    { "id": "n4", "type": "executor", "name": "Send Confirmation" }
  ],
  "edges": [
    { "id": "e1", "source": "n1", "target": "n2", "label": "Start" },
    { "id": "e2", "source": "n2", "target": "n3", "label": "Valid" },
    { "id": "e3", "source": "n3", "target": "n4", "label": "Payment routed" }
  ]
}
```

### KYC onboarding

A customer's documents are checked by an external service. A conditional node evaluates whether manual review is required. If so, a manual approval action is triggered before activating the account.

```json theme={null}
{
  "nodes": [
    { "id": "n1", "type": "trigger",     "name": "Onboarding Started" },
    { "id": "n2", "type": "executor",    "name": "Run Document Check" },
    { "id": "n3", "type": "conditional", "name": "Manual Review Required?" },
    { "id": "n4", "type": "action",      "name": "Request Manual Approval" },
    { "id": "n5", "type": "executor",    "name": "Activate Account" }
  ],
  "edges": [
    { "id": "e1", "source": "n1", "target": "n2", "label": "Start" },
    { "id": "e2", "source": "n2", "target": "n3", "label": "Check complete" },
    {
      "id": "e3",
      "source": "n3",
      "target": "n4",
      "sourceHandle": "review",
      "condition": "n2.reviewRequired == true",
      "label": "Needs review"
    },
    {
      "id": "e4",
      "source": "n3",
      "target": "n5",
      "sourceHandle": "clear",
      "condition": "n2.reviewRequired == false",
      "label": "Auto-approved"
    },
    { "id": "e5", "source": "n4", "target": "n5", "label": "Approved" }
  ]
}
```

### Manual approval flow

A submission is sent for review. An approval action waits for a decision. A conditional node then routes to either the approved or rejected path.

```json theme={null}
{
  "nodes": [
    { "id": "n1", "type": "trigger",     "name": "Request Submitted" },
    { "id": "n2", "type": "executor",    "name": "Submit for Review" },
    { "id": "n3", "type": "action",      "name": "Await Approval Decision" },
    { "id": "n4", "type": "conditional", "name": "Decision Received" },
    { "id": "n5", "type": "executor",    "name": "Process Approved Request" },
    { "id": "n6", "type": "executor",    "name": "Notify Rejection" }
  ],
  "edges": [
    { "id": "e1", "source": "n1", "target": "n2", "label": "Start" },
    { "id": "e2", "source": "n2", "target": "n3", "label": "Submitted" },
    { "id": "e3", "source": "n3", "target": "n4", "label": "Decision received" },
    {
      "id": "e4",
      "source": "n4",
      "target": "n5",
      "sourceHandle": "approved",
      "condition": "n3.decision == 'approved'",
      "label": "Approved"
    },
    {
      "id": "e5",
      "source": "n4",
      "target": "n6",
      "sourceHandle": "rejected",
      "condition": "n3.decision == 'rejected'",
      "label": "Rejected"
    }
  ]
}
```

## Best practices

***

### Node naming conventions

Use descriptive, action-oriented names that communicate what the node does, not what type it is.

* **correct**: `Validate Payment Data`, `Get Fraud Score`, `Notify Customer`, `Await Approval`
* **wrong**: `executor1`, `conditional node`, `node3`

Good names make workflows readable without opening the node configuration. They also appear in audit logs and execution traces, making debugging significantly faster.

### Edge condition expressions

Conditions on edges are evaluated against the execution context. Keep them simple and explicit:

* Use direct field comparisons: `<nodeId>.status == 'approved'`
* Use numeric comparisons: `<nodeId>.riskScore < 70`
* Use boolean fields: `<nodeId>.reviewRequired == true`

Avoid complex expressions that are hard to read or debug. If logic is non-trivial, consider routing through a `conditional` node that encapsulates the decision with a clear name.

An invalid expression returns `FLK-0105`. Always validate expressions before activating a workflow.

### Error handling strategies

Design workflows to handle failure explicitly:

* Add rejection paths from `conditional` nodes for every decision point that can fail.
* Use separate `executor` nodes for retry logic or fallback providers.
* Name error paths clearly (e.g., `Reject and Notify`, `Fallback to Manual Review`) so audit logs are self-explanatory.

### Avoiding cycles

Flowker uses a DFS-based cycle guard at runtime. If a cycle is detected during execution, the workflow fails with `FLK-0508`. Cycles are not caught at design time, so validate your edge structure before activating.

Rules to prevent cycles:

* Edges must always point forward in the flow — never back to a previously executed node.
* Review the graph visually before activating any workflow with branching or merging paths.
* If a retry or loop is needed, model it as a separate workflow invocation, not a back-edge in the current graph.

### Versioning via clone

Never edit an active workflow directly. Instead:

<Steps>
  <Step>
    Clone the workflow (creates a new `draft` with all nodes and edges copied).
  </Step>

  <Step>
    Make your changes in the draft.
  </Step>

  <Step>
    Test the draft with execution runs.
  </Step>

  <Step>
    Activate the new version.
  </Step>

  <Step>
    Deactivate the old version if it is no longer needed.
  </Step>
</Steps>

This preserves the execution history of the active version and gives you a clean rollback path if the new version has issues.

## Creating workflows from templates

***

Instead of building a workflow from scratch, you can create one from a pre-built template in the catalog. Templates provide ready-made node and edge structures for common patterns — payment validation, fraud checks, onboarding flows, and more.

Call [`POST /v1/workflows/from-template`](/en/reference/flowker/create-workflow-from-template) with the template ID, a name for the new workflow, and any parameters the template requires. The result is a new workflow in `draft` status that you can review, customize, and activate.

<Accordion title="Example request">
  ```json theme={null}
  POST /v1/workflows/from-template

  {
    "templateId": "payment-validation",
    "params": {
      "providerConfigId": "a1b2c3d4-e5f6-4789-a012-345678901234",
      "currency": "USD"
    },
    "name": "Payment Validation - USD",
    "description": "Validates USD payments before ledger write"
  }
  ```
</Accordion>

To discover available templates and their required parameters, use the [List catalog templates](/en/reference/flowker/list-catalog-templates) and [Get catalog template](/en/reference/flowker/get-catalog-template) endpoints. The template detail endpoint returns a JSON Schema describing the expected parameters, including dynamic options for fields that reference provider configurations.

## Error reference

***

The following error codes are relevant to workflow design and execution:

| Code       | Description                                         |
| ---------- | --------------------------------------------------- |
| `FLK-0102` | Invalid status transition                           |
| `FLK-0103` | Workflow cannot be modified — not in draft status   |
| `FLK-0105` | Invalid conditional expression                      |
| `FLK-0113` | Too many nodes — maximum is 100                     |
| `FLK-0114` | Too many edges — maximum is 200                     |
| `FLK-0506` | Execution input payload too large — maximum is 1 MB |
| `FLK-0508` | Cycle detected during workflow execution            |
