Getting Started

Install TwistedFlow, create your first flow, and run it in the desktop app or the CLI.

What is TwistedFlow?

TwistedFlow is a visual flow engine. You build automations, API clients, HTTP servers, test suites, and system tools by wiring nodes on a canvas. Then you run them in the desktop app, headlessly via CLI, or compile to standalone binaries.

There are 46 built-in nodes covering flow control, HTTP client/server, REST API server (routing, auth, CORS, rate limiting), CLI tools, string processing, data manipulation, variables, system I/O, and testing. You can extend TwistedFlow with project-local custom nodes written in Rust and compiled to WASM.

Installation

Desktop App

Download the latest release for your platform:

  • macOS (ARM + Intel) — .dmg
  • Linux (x64) — .AppImage or .deb

Grab it from GitHub Releases.

CLI

Download twistedflow-cli from GitHub Releases. Each release includes a twistedflow-cli-<platform>.tar.gz for macOS (ARM + Intel) and Linux (x64).

# Download and install (macOS Apple Silicon example)
tar -xzf twistedflow-cli-aarch64-apple-darwin.tar.gz
sudo mv twistedflow-cli /usr/local/bin/twistedflow

# Verify
twistedflow --help

Or build from source:

git clone https://github.com/imkarmadev/TwistedFlow.git
cd TwistedFlow/apps/desktop/src-tauri
cargo build --release -p twistedflow-cli

# Binary is at target/release/twistedflow-cli
sudo ln -sf $(pwd)/target/release/twistedflow-cli /usr/local/bin/twistedflow

From Source (Development)

Prerequisites: macOS or Linux, Bun >= 1.2, Rust >= 1.77, Xcode CLI tools on macOS.

git clone https://github.com/imkarmadev/TwistedFlow.git
cd TwistedFlow
bun install

cd apps/desktop
bun run dev

First Rust compile takes ~30s. After that, rebuilds are <5s.

Your first flow

1. Create a project

Open TwistedFlow and create a new project. This creates a folder on disk with twistedflow.toml, flows/, nodes/, and nodes-src/ directories.

2. Add nodes

Right-click the canvas (or press Space) to open the node palette. Add these nodes:

  • Start — the entry point
  • HTTP Request — set the URL to https://jsonplaceholder.typicode.com/users/1
  • Log — to print the response

3. Wire them up

Connect the exec pins (white diamonds) in order: Start → HTTP Request → Log. Then drag the HTTP Request's body output pin to the Log's value input pin.

4. Run it

Click the Run action in the top project bar (or use the Start node's play button). The flow executes and you'll see the JSON response in the Console tab in the bottom workspace (toggle with ` backtick key).

5. Run from CLI

twistedflow run ~/my-project/flows/main.flow.json

Keyboard shortcuts

KeyAction
Right-clickOpen node palette at cursor
SpaceOpen node palette at center
`Toggle console panel
MToggle minimap
BackspaceDelete selected node or edge
Cmd+ZUndo
Cmd+Shift+ZRedo

Nodes Reference

All 46 built-in nodes across 9 categories.

Every node has pins: exec pins (white diamonds) control flow order, data pins (colored circles) carry typed values. Drag pin to pin to wire them. Drag a pin to empty canvas to create a compatible node automatically.

Flow Control
Start
Entry point for every flow. Execution begins here. Every flow needs exactly one Start node.
exec-out →
If / Else
Boolean branching. Evaluates a condition and routes to the true or false exec path.
→ exec-in condition true → false →
Match
Switch/case routing. Compares a value against named cases, fires the matching branch. Great for routing on HTTP status codes.
→ exec-in value case-1 → case-2 → default →
ForEach (Sequential)
Iterates an array, runs the body chain once per item in order. The current item and index are available as output pins.
→ exec-in array body → item index done →
ForEach (Parallel)
Same as ForEach Sequential, but runs all iterations concurrently. Faster for independent operations like batch API calls.
→ exec-in array body → item index done →
Try / Catch
Error boundary. Runs the try branch; if any node in it fails, routes to the catch branch with the error message.
→ exec-in try → catch → error
EmitEvent
Broadcasts a named event with a payload. All matching OnEvent listeners fire their chains in parallel.
→ exec-in event name payload exec-out →
OnEvent
Listens for a named event. When the event fires, this node's exec chain runs. Output pins mirror the emitter's payload.
exec-out → payload
Retry
Re-execute a sub-chain with configurable retries and exponential backoff. If all attempts fail, routes to the failed branch.
→ exec-in body → out → failed → attempts succeeded error
HTTP Client
Request
Fires an HTTP request. Supports all methods, URL #{token} templates, three-layer header merge, 5 auth types (Bearer, Basic, API Key, OAuth2), Zod response schema, response time tracking, and response headers.
→ exec-in url (template) body (template) exec-out → status responseTime responseHeaders body fields
HTTP Server
Listen
Starts an HTTP server on a specified port. This is a process node — it stays alive until the flow is stopped. Each incoming request triggers the exec-request chain.
→ exec-in request → method path query headers body
Route
Multi-route dispatcher. Configure routes with method + path patterns. Supports :param extraction (/users/:id). Each route gets its own exec output. Falls back to not found.
→ exec-in method path query route-0 → route-1 → not found → params query
Send Response
Sends an HTTP response back to the client. Accepts dynamic headers from CORS, Cookie, or Set Headers nodes via in:headers.
→ exec-in status body headers exec-out →
Parse Body
Parse the request body. Auto-detects Content-Type (JSON, form-urlencoded, text) or force a mode. Pure data node — lazily resolved when downstream reads it.
body headers parsed contentType
CORS
Handle cross-origin requests. OPTIONS preflight gets auto-responded (204). Normal requests continue with Access-Control headers injected. Configure allowed origins, methods, headers, credentials.
→ exec-in method headers preflight → request → corsHeaders
Verify Auth
Validate incoming authentication. Modes: JWT (HS256 with expiry check), API Key (header validation), Basic (decode credentials), Bearer (extract token). Branches pass/fail.
→ exec-in headers secret pass → fail → claims token
Rate Limit
Sliding window rate limiter. Tracks requests per key (IP, header, or custom). Outputs X-RateLimit-* headers. Branches pass/limited.
→ exec-in headers key pass → limited → remaining rateLimitHeaders
Set Headers
Build response headers from configured key-value pairs. Supports #{template} tokens in values. Merge with upstream headers via in:merge.
merge headers
Cookie
Two modes: Parse reads the Cookie header into a name-value object. Set builds Set-Cookie headers with path, HttpOnly, Secure, SameSite, Max-Age attributes.
headers cookies / setCookieHeaders
Redirect
Send an HTTP redirect response. Configurable status code (301 permanent, 302 temporary, 307, 308) and target URL with template support.
→ exec-in url exec-out →
Serve Static
Serve files from disk. Configurable root directory, index file, and path prefix stripping. Detects 25+ MIME types. Path traversal protection built in.
→ exec-in path exec-out → filePath contentType found
Data
BreakObject
Splits an object into one output pin per field. Introspects the source schema at design time — pins update automatically when you wire a different source.
object field-1 field-2 ...
MakeObject
Assembles an object from named typed input pins. The inverse of BreakObject. Add fields in the inspector.
field-1 field-2 object
Convert
Type coercion between string, number, integer, boolean, and JSON. Smart — detects the source type and filters valid target conversions.
value result
Tap
Pass-through debug probe. Shows every value that flows through it inline on the canvas. Accumulates values across parallel iterations.
value value
Log
Exec-chain print sink. Writes values to the bottom console panel with timestamps. Toggle the console with the backtick key.
→ exec-in value exec-out →
Filter
Filter array items by expression. Supports field comparisons like item.status == 200, item.id > 5, item.tags contains "urgent".
→ exec-in array exec-out → result count
Map
Transform each item in an array. Modes: pluck (extract a single field), pick (select fields), template (format with #{field}).
→ exec-in array exec-out → result count
Merge
Deep-merge objects or concatenate arrays. Auto-detects types. Two inputs: a and b. Modes: deep, shallow, concat, auto.
a b result
Reduce
Aggregate an array into a single value. Operations: sum, count, join, min, max, first, last, flatten, unique, groupBy.
→ exec-in array exec-out → result
CLI
Parse Args
Parses CLI arguments into structured data. Handles --flag value, -f shorthand, --flag=value, and positional args. Pure data node — no exec pins needed.
flags positional raw
Stdin
Reads from standard input. Works with piped data (cat data.json | twistedflow run flow.json). Outputs raw content, split lines, and parsed JSON.
→ exec-in exec-out → content lines json
Stderr
Writes to stderr. Essential for CLI tools: data goes to stdout (via Print), progress/errors go to stderr (via Stderr). Same formatting as Print.
→ exec-in value exec-out →
Prompt
Interactive user input. Modes: text (free input with optional default), confirm (y/n → boolean), password (hidden input). Message can be wired dynamically.
→ exec-in message exec-out → answer
String
Regex
Regular expression operations. Modes: match (test + capture groups), extract (all matches), replace (substitution), split (split by pattern). Supports case-insensitive flag.
value matched / matches / result / parts groups
Template
String interpolation with #{var} tokens. Same template syntax as HTTP Request URLs. Wire input pins to provide values. Pure data node.
dynamic inputs result
Encode/Decode
Encoding conversion. Supports base64, base64url, hex, and url (percent-encoding). Toggle between encode and decode.
value result
Hash
Cryptographic hashing. Algorithms: sha256, sha512, md5, hmac-sha256 (with key pin). Output as hex or base64.
value key (HMAC) hash
Variables
EnvVar
Reads a value from the active .env file. Configure the variable name in the inspector. The value is resolved at flow start.
value
SetVariable
Sets a typed runtime variable. When the flow has declared variables (via the Variables Panel), a dropdown picker lists them; otherwise falls back to freetext. The value pin is colored by the variable's declared type.
→ exec-in value exec-out →
GetVariable
Reads a typed runtime variable. The output pin is colored by the variable's declared type. Break Object can introspect the output schema when the variable is declared as object, auto-generating sub-pins for each field. Returns the declared default (or null) if the variable hasn't been set yet.
value

Variables Panel: Click the empty canvas (deselect all nodes) to open the Variables Panel in the inspector. Declare flow-level typed variables with a name, type (string, number, boolean, object, array), and default value. Declared defaults are pre-seeded into the variable store at execution start. Set/Get Variable nodes show a dropdown of declared variables, and pin colors reflect the declared type: string (pink), number (green), boolean (red), object (blue), array (purple).

System
Print
Writes to stdout. Most useful in CLI/binary mode where there's no canvas. In the desktop app, output appears in the console panel.
→ exec-in value exec-out →
ShellExec
Runs a shell command and captures stdout, stderr, and exit code as output pins.
→ exec-in command exec-out → stdout stderr exitCode
FileRead
Reads a file from disk and outputs its content as a string.
→ exec-in path exec-out → content
FileWrite
Writes content to a file on disk. Creates the file if it doesn't exist.
→ exec-in path content exec-out →
Sleep
Pauses execution for a specified duration in milliseconds.
→ exec-in ms exec-out →
Exit
Exits the flow with a status code. Code 0 means success, anything else means failure. Most useful in CLI/binary mode.
→ exec-in code
Testing
Assert
Asserts that a condition is true. If the assertion fails, the flow errors with the assertion message. Use for building test suites.
→ exec-in condition message exec-out →
AssertType
Asserts that a value matches an expected type (string, number, boolean, object, array). Fails the flow if the type doesn't match.
→ exec-in value expected type exec-out →

CLI Reference

Run flows headlessly and compile them to standalone binaries.

The release archive contains the CLI binary twistedflow-cli, but the recommended install name is twistedflow. It shares the same Rust runtime and builder crates as the desktop app. Download it from GitHub Releases (twistedflow-cli-<platform>.tar.gz) and install:

# macOS Apple Silicon
tar -xzf twistedflow-cli-aarch64-apple-darwin.tar.gz
sudo mv twistedflow-cli /usr/local/bin/twistedflow

twistedflow run

Run a .flow.json file headlessly.

twistedflow run <file> [options]
OptionDescription
<file>Path to the .flow.json file
-e, --env KEY=VALSet environment variables (repeatable)
--base-url URLBase URL for HTTP requests
--plugins DIRExtra plugin directories (comma-separated). Project nodes/ is loaded automatically.
-q, --quietOnly print errors
-- [args]Pass arguments through to the flow (accessible via ParseArgs node)

Examples

# Basic run
twistedflow run ./flows/main.flow.json

# With env vars
twistedflow run ./flows/main.flow.json -e API_KEY=abc123 -e BASE_URL=https://api.example.com

# Pass arguments to the flow (ParseArgs node reads these)
twistedflow run ./flows/my-tool.flow.json -- --input data.json --verbose

# Pipe data into a flow (Stdin node reads this)
cat urls.txt | twistedflow run ./flows/check-urls.flow.json

# With base URL and quiet mode
twistedflow run ./flows/health-check.flow.json --base-url https://api.example.com -q

Output

Node status is printed to stderr. Log/Print node output goes to stdout. This means you can pipe flow output:

twistedflow run ./flows/transform.flow.json | jq '.result'

twistedflow build

Compile a project into a standalone binary. The binary embeds the flow JSON and environment variables — just run it, no arguments needed.

twistedflow build <project> [options]
OptionDescription
<project>Path to the project folder (must contain twistedflow.toml)
-o, --output NAMEOutput binary name
--flow NAMEWhich flow to compile (default: first in flows/)
--env NAMEWhich .env file to embed (default: .env)
--releaseBuild with optimizations (slower compile, faster binary)

Examples

# Build with defaults
twistedflow build ~/my-project -o my-app

# Build specific flow with prod env
twistedflow build ~/my-project -o health-checker --flow health-check --env prod

# Build optimized release binary
twistedflow build ~/my-project -o my-app --release

# Run the compiled binary
./my-app

Tip: The desktop app also has a Build action in the top project bar. It uses the same shared Rust builder directly, not a CLI subprocess.

Exit codes

CodeMeaning
0Flow completed successfully
1Flow failed (node error, assertion failure, etc.)

Custom Nodes

Extend TwistedFlow with project-local custom nodes written in Rust and compiled to WASM. Author them from the desktop app or the CLI.

How it works

TwistedFlow uses wasmtime to load WebAssembly modules as custom node types. Plugins appear in the node palette alongside built-in nodes, with no runtime performance penalty vs built-in Rust nodes for most workloads.

Project-local layout

Custom nodes are project assets. TwistedFlow loads built .wasm files from the current project's nodes/ directory. Editable Rust source lives in nodes-src/.

my-project/
  nodes/ — built and installed .wasm custom nodes
    my-plugin.wasm
  nodes-src/ — optional editable Rust source
    my-plugin/
      Cargo.toml
      src/lib.rs

Quick start from CLI

Three commands from an empty directory to an installed custom node:

twistedflow plugin new my-plugin --category Utility --node Hello
# edit my-plugin/src/lib.rs
cd my-plugin
twistedflow plugin build

The plugin new command scaffolds a complete Rust crate. The plugin build command compiles to wasm32-wasip1, validates the output, and installs into the nearest parent TwistedFlow project's nodes/ directory by default.

Desktop authoring

  1. Open a project
  2. Open the Custom Nodes tab in the bottom workspace
  3. Click New Node
  4. Edit the generated Rust source in your editor
  5. Click Build Plugin on the node tile

The desktop app writes source under <project>/nodes-src/ and installs validated artifacts into <project>/nodes/.

The nodes! macro

Declare one or more nodes in a single nodes! { ... } block:

use twistedflow_plugin::*;

nodes! {
    node "Uppercase" (
        type_id = "uppercase",
        category = "Text"
    ) {
        inputs:  [{ key: "text",   data_type: "string" }],
        outputs: [{ key: "result", data_type: "string" }],
        execute: |inputs| {
            let text = inputs.get_string("text").unwrap_or_default();
            PluginOutputs::new().set("result", text.to_uppercase())
        }
    }
}

Pin types

TypeReads fromReader method
stringtextget_string()
numberint or floatget_number()
booleantrue/falseget_bool()
objectJSON objectget_object()
arrayJSON arrayget_array()
unknownanythingget_value()

Host callbacks

Plugins can call back into TwistedFlow. Currently one callback is exposed:

// Print a message to the console panel (desktop) or stdout (CLI)
host::log("starting heavy work");

Messages appear in the same stream as the built-in Log node, tagged with the invoking node's id.

Plugin validation

When you run twistedflow plugin build, the built .wasm is validated before installation:

  • Required exports (tf_metadata, tf_execute) must exist
  • Node metadata must parse as valid JSON
  • Pin data_type values must be from the valid set

Invalid plugins fail with a clear error message — you won't silently ship a broken plugin.

Full API reference and tutorials: see the plugin author guide on GitHub. Examples at examples/plugins/.

Project Structure

TwistedFlow projects are plain folders on disk. No database, git-friendly by default.

Folder layout

my-project/
  twistedflow.toml — project name
  .env — default environment
  .env.dev — dev environment
  .env.prod — prod environment
  flows/
    main.flow.json
    health-check.flow.json
  nodes/ — built/installed project custom nodes
    my-custom-node.wasm
  nodes-src/ — optional editable Rust source for those nodes
    my-custom-node/
      Cargo.toml
      src/lib.rs

twistedflow.toml

Minimal config file. Currently just holds the project name:

name = "My Project"

Environments (.env files)

Standard dotenv format. Each .env file is an environment:

FileEnvironment
.envDefault (always loaded as fallback)
.env.devDevelopment
.env.stagingStaging
.env.prodProduction

Access env vars in flows via the EnvVar node. Wire the output pin to wherever you need the value.

# .env.dev
API_KEY=dev-key-12345
BASE_URL=http://localhost:3000
DEBUG=true

Flows (.flow.json)

Each flow is a JSON file in the flows/ directory. A flow contains:

  • nodes — array of node objects (type, position, config, pins)
  • edges — array of connections between pins
  • variables — array of typed variable declarations ({ name, type, default }). Defaults are pre-seeded at execution start.
  • viewport — saved zoom/pan position

Flows are created and edited in the desktop app. You can also export/import them as .flow.json files.

Git workflow

Since projects are just files, they work naturally with git:

cd my-project
git init
git add .
git commit -m "initial flow"

# Collaborate
git push origin main

Tip: Add .env.prod to your .gitignore if it contains secrets. Use .env.prod.example as a template.