> ## 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: node types, edges, real-world patterns, status transitions, and best practices for reliable orchestration.

This guide walks through node types, edges, real-world patterns, status transitions, technical limits, and best practices.

## Node types

***

Every workflow consists of nodes. Each node has a `type` that defines how Flowker processes it at runtime.

### trigger

A trigger node is an execution entry point. Draft workflows can be incomplete, but activation requires at least one trigger node. When an execution starts through a trigger, the engine enters at that node and routes from it.

The examples on this page show node topology only. A real trigger node also carries a `triggerType` and that trigger's configuration in its `data`. See [Configuring a webhook trigger](/en/products/flowker/configuring-a-webhook-trigger) or [Running a workflow on a schedule](/en/products/flowker/running-a-workflow-on-a-schedule).

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

### executor

Calls an external service through a provider configuration. This node is the main integration point for fraud engines, payment providers, notification services, and other external systems.

The examples on this page show node topology only. A real executor node also carries a `providerConfigId` in its `data`, plus an `executorId` naming the catalog executor it invokes. A node that calls an operation of an uploaded OpenAPI document omits the `executorId` and carries `operation_path` and `operation_method` instead. See the [Integration guide](/en/products/flowker/integration-guide).

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

### conditional

Evaluates a condition 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.

The condition lives in the node's `data.condition`. A free-text expression evaluates to a boolean and produces the output handle `true` or `false`. Each outgoing edge declares which handle it follows via `sourceHandle`. Conditional nodes built in the Console use a structured, case-based condition where each case routes to its own output handle. See the [Canvas editor](/en/products/flowker/console/canvas-editor).

```json theme={null}
{
  "id": "node-risk-decision",
  "type": "conditional",
  "name": "Evaluate Risk Score",
  "data": { "condition": "node-fraud-check.riskScore < 70" }
}
```

### action

Represents a synchronous internal `set_output` operation. It can write an interpolated output value and, optionally, override the synchronous HTTP response status. It does not provide a built-in pause, generic event emission, or a generic state-change operation.

```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 handle of the source node this edge follows. Required to route out of a conditional node: it must match the branch outcome (`true` or `false` for a free-text condition). |
| `condition`    | Legacy free-text field kept for backward compatibility. It is not evaluated for routing — leave it empty for new workflows.                                                      |
| `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": "true",
  "label": "Approved"
}
```

Routing depends on the source node's type. A conditional node evaluates its `data.condition` and follows the single outgoing edge whose `sourceHandle` matches the branch outcome. If no edge matches, that branch ends. Every other node type follows all of its outgoing edges when it completes successfully.

## Status transitions

***

Workflows follow a well-defined lifecycle.

<Frame>
  <img src="https://mintcdn.com/lerian-49cb71fc/SEOef3JqTInYAAau/images/en/d2/flowker-workflow-status-transitions.svg?fit=max&auto=format&n=SEOef3JqTInYAAau&q=85&s=a046e93f9e4a2cbf44f5a32b69254b06" 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="817" height="327" data-path="images/en/d2/flowker-workflow-status-transitions.svg" />
</Frame>

* **draft**: the initial state. You can add nodes, edit edges, and change configuration only in `draft` status.
* **active**: a workflow you activated. Flowker can execute it. It accepts no modification while active.
* **inactive**: a workflow you deactivated. Flowker can no longer execute it, but you can move it back to `draft` for editing.

### Rules

* You can activate only a `draft` workflow (transition: `draft → active`).
* You can deactivate only an `active` workflow (transition: `active → inactive`).
* You can move only an `inactive` workflow 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/products/flowker/move-workflow-to-draft). This makes the workflow editable without needing to clone it.

Use this when you deactivated a workflow by mistake, or when you want to iterate on an existing workflow instead of creating a copy.

<Note>
  You can move only inactive workflows 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.

Use this 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` |

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 needs no branching.

<Frame>
  <img src="https://mintcdn.com/lerian-49cb71fc/SEOef3JqTInYAAau/images/en/d2/flowker-pattern-sequential.svg?fit=max&auto=format&n=SEOef3JqTInYAAau&q=85&s=340149b4d91ac1ceb12d735624e90861" 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="957" height="268" data-path="images/en/d2/flowker-pattern-sequential.svg" />
</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 its condition and routes execution accordingly. The branch outcome selects the outgoing edge the node follows, matched by `sourceHandle`.

<Frame>
  <img src="https://mintcdn.com/lerian-49cb71fc/SEOef3JqTInYAAau/images/en/d2/flowker-pattern-conditional.svg?fit=max&auto=format&n=SEOef3JqTInYAAau&q=85&s=bf118a79f905ff81a2994ea171eef3bf" 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="999" height="394" data-path="images/en/d2/flowker-pattern-conditional.svg" />
</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",
      "data": { "condition": "n2.fraudScore < 70" }
    },
    { "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": "true",
      "label": "Approved"
    },
    {
      "id": "e4",
      "source": "n3",
      "target": "n5",
      "sourceHandle": "false",
      "label": "Rejected"
    }
  ]
}
```

## Real-world examples

***

### Anti-fraud check

A transaction arrives, an executor node gets the fraud score, and a conditional node routes execution 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",
      "data": { "condition": "n2.fraudScore < 70" }
    },
    { "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": "true",
      "label": "Low risk"
    },
    {
      "id": "e4",
      "source": "n3",
      "target": "n5",
      "sourceHandle": "false",
      "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

Use a workflow to submit a document check to an external approval system. Flowker does not have a built-in pause: for asynchronous human review, start a later, separate workflow execution after your approval system publishes its decision.

### Manual approval flow

An executor node submits the request for review. An executor retrieves the review decision from the external system. A conditional node then routes to either the approved or rejected path. Flowker executions run straight through. Flowker has no built-in pause step, so a human decision must come from an external system the workflow queries.

```json theme={null}
{
  "nodes": [
    { "id": "n1", "type": "trigger",     "name": "Request Submitted" },
    { "id": "n2", "type": "executor",    "name": "Submit for Review" },
    { "id": "n3", "type": "executor",    "name": "Get Approval Decision" },
    {
      "id": "n4",
      "type": "conditional",
      "name": "Decision Received",
      "data": { "condition": "n3.decision == 'approved'" }
    },
    { "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": "true",
      "label": "Approved"
    },
    {
      "id": "e5",
      "source": "n4",
      "target": "n6",
      "sourceHandle": "false",
      "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`, `Get Approval Decision`
* **wrong**: `executor1`, `conditional node`, `node3`

Good names make workflows readable without opening the node configuration. They also appear in execution records and traces.

### Condition expressions

Flowker evaluates free-text conditions on conditional nodes against the execution context at runtime. Keep them simple and explicit:

* Use direct field comparisons: `<nodeId>.status == 'approved'`
* Use numeric comparisons: `<nodeId>.riskScore < 70`
* Use boolean fields: `<nodeId>.reviewRequired == true`
* Combine with `AND` / `OR` when needed: `<nodeId>.score < 70 AND <nodeId>.verified == true`

Avoid complex expressions. They make the workflow hard to read and debug. If logic is non-trivial, give the `conditional` node a clear name that encapsulates the decision.

A missing condition fails the execution, and so does a condition that fails to evaluate at runtime (`FLK-0105` identifies an invalid conditional expression). Always test conditions 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 execution records are self-explanatory.

### Avoiding cycles

Flowker uses a DFS-based cycle guard at runtime. When the cycle guard finds a cycle 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 you need a retry or a loop, 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>
    Validate or preview the draft. Activate it before running execution tests.
  </Step>

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

  <Step>
    Deactivate the old version if you no longer need it.
  </Step>
</Steps>

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

## 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            |
