Nodes & Edges
The building blocks of flow workflows.
Nodes & Edges
Workflows in flow are directed acyclic graphs (DAGs) made up of nodes and edges. Nodes perform operations — generating images, transforming text, routing data — and edges carry typed values between them.
Node categories
| Category | Purpose | Examples |
|---|---|---|
| Variables | Provide input values to the workflow | Text, Image, Number, Boolean, JSON |
| Generators | Create new content using AI models | Flux Schnell, Stable Diffusion, Kling, Minimax |
| Logic | Control data flow | Router, Filter, Conditional |
| Compose | Combine multiple inputs | Image Composite, Video Stitch, Concat |
| Transform | Modify existing content | Upscale, Background Remove, Face Swap, Style Transfer |
| Action | Produce side effects | HTTP Request, Webhook |
| Utility | Helper operations | Preview, JavaScript, Prompt Template, List |
Node data structure
Every node has the same base structure:
interface NodeData {
defType: string; // Plugin type identifier (e.g. "fal-flux-schnell")
label: string; // Display name
config: Record<string, unknown>; // Plugin-specific configuration
handleCounts: Record<string, number>; // Dynamic handle counts
}defTypelinks the node to its plugin definition (NodeDef), which declares the available handles, config fields, and step function.configstores user-configured values like model parameters, prompt text, or image dimensions. Each plugin defines which config fields are available.handleCountstracks the count for expandable handles. For example, an image composite node might have{ "layer": 3 }to indicate three layer inputs.
Edges
Edges connect an output handle on one node to an input handle on another:
interface Edge {
id: string;
source: string; // Source node ID
sourceHandle: string; // Output handle ID on the source
target: string; // Target node ID
targetHandle: string; // Input handle ID on the target
}Connections are type-checked — you can only connect an output to an input with a compatible handle type. The editor enforces this visually and prevents invalid connections.
Data flow
Data flows left to right through the graph:
[Text Variable] ──TEXT──> [Flux Schnell] ──IMAGE──> [Upscale] ──IMAGE──> [Preview]
"a cat" prompt image image- Variable nodes produce their configured values as outputs (e.g. a text node outputs its text content)
- The execution engine resolves edges to determine which values flow into each node's inputs
- Each node executes with its resolved inputs and produces outputs
- Outputs flow along edges to downstream nodes
- The process continues in topological order until all nodes have executed
Nodes with no input edges (like variable nodes) execute first. Nodes with multiple inputs wait until all upstream nodes have completed.
Node identity
All entity IDs in flow use nanoid-based identifiers with prefixes:
| Entity | Prefix | Example |
|---|---|---|
| Project | pj- | pj-abc123def456 |
| Workflow | wf- | wf-xyz789ghi012 |
| Workflow Run | wr- | wr-run456jkl789 |
| Node Run | nr- | nr-nod012mno345 |
IDs are 12 lowercase alphanumeric characters after the prefix, generated via lib/nanoid.ts.
Durable vs. ephemeral data
Node data exists in two forms:
| Field prefix | Storage | Lifecycle |
|---|---|---|
No prefix (config, label, handleCounts) | Yjs document (durable, synced) | Persisted across sessions, synced between users |
_ prefix (_output, _inputs, _runs) | React state (ephemeral) | Per-client, recomputed on load, stripped before persistence |
selected, measured, dragging | React state (ephemeral) | React Flow internal state, never persisted |
The toDurable() function strips all ephemeral fields before writing to Yjs, ensuring only stable configuration is synced and persisted.