Workflow Canvas
Headless node-based workflow canvas with support for custom node types via nodeTypes, nodeRegistry, and iconMap. Unknown nodes fall back to UnknownFlowNode.
Add nodes with the + control, or right-click the canvas for the node picker. Cycle prevention is enabled for Zapier-style DAG workflows.
Sign in to view this example's source code.
Sign inSelect a node to open the inspector, or double-click to open it directly. Dialog UI lives in the app layer.
No selectionSign in to view this example's source code.
Sign inConnect boolean output to Condition (allowed) or Image Processor (blocked). Untyped handles elsewhere remain permissive.
Sign in to view this example's source code.
Sign inIntentionally invalid graph: no trigger, unreachable delay node, condition missing the false branch. Invalid nodes show a red badge on the canvas; graph-level issues appear below.
Workflow is valid
Sign in to view this example's source code.
Sign inProps
| Name | Type | Default | Description |
|---|---|---|---|
| nodes | WorkflowNode[] | Required | Workflow nodes with id, type, position, and display/configuration data |
| edges | WorkflowEdge[] | Required | Connections between workflow nodes |
| nodeTypes | NodeTypes | undefined | React Flow node type map. Unregistered domain types render via the built-in UnknownFlowNode fallback. |
| nodeRegistry | Record<string, NodeTypeConfig> | {} | Metadata for palette labels, handle topology, and custom node components via useWorkflowCanvasConfig. Each entry defines `handles[]` with explicit port ids. |
| iconMap | Partial<Record<string, LucideIcon>> | {} | Icon lookup for custom node components reading useWorkflowCanvasConfig |
| WorkflowNodeData.handles | WorkflowHandleConfig[] | undefined | Optional per-node handle override. When set, replaces the registry handles for that instance (e.g. merge nodes with N inputs). Edge sourceHandle/targetHandle map to handle id. |
| WorkflowHandleConfig.dataType | string | undefined | Output port type for source handles (e.g. boolean, image, json). Omit for permissive legacy behavior. |
| WorkflowHandleConfig.accepts | string[] | undefined | Allowed input types for target handles. Omit to accept any source. Use any or * to accept all typed sources. |
| isValidConnection | WorkflowIsValidConnection | undefined | Optional business-rule hook after structural and port-type checks. Receives source/target nodes and resolved handles. |
| preventCycles | boolean | false | When true, reject connections that would create a directed cycle (DAG enforcement). Also flags cycle edges in validateGraph when enabled via validationOptions. |
| NodeTypeConfig.role | "entry" | "normal" | "exit" | undefined | Graph validation role. Entry types are used as workflow start points for reachability checks. |
| NodeTypeConfig.requiredSourceHandles | string[] | undefined | Each listed source handle must have at least one outgoing edge when validateGraph runs. |
| NodeTypeConfig.requiredTargetHandles | string[] | undefined | Each listed target handle must have at least one incoming edge when validateGraph runs. |
| validationOptions | ValidateGraphOptions | undefined | Options passed to validateGraph. Defaults registry, isValidConnection, and preventCycles from canvas props. |
| validateOnChange | boolean | false | When true (or when onValidationChange is set), re-run validateGraph after graph mutations. |
| onValidationChange | (result: WorkflowGraphValidationResult) => void | undefined | Called with nodeErrors, edgeErrors, and graphErrors after validation. Also exposes result on useWorkflowCanvasConfig().validation. |
| editable | boolean | true | Enable dragging, connecting, selection, and deletion |
| onNodesChange | (nodes: WorkflowNode[]) => void | undefined | Called with normalized workflow nodes after canvas changes |
| onEdgesChange | (edges: WorkflowEdge[]) => void | undefined | Called with normalized workflow edges after canvas changes |
| onConnect | (connection: Connection) => void | undefined | Called when a new edge connection is created |
| onNodeSelect | (node: WorkflowNode | null) => void | undefined | Selection event - wire to app state; open drawers/modals in your layer, not inside the canvas |
| onNodeDoubleClick | (node: WorkflowNode) => void | undefined | Double-click event - convenience hook to open an inspector or dialog without embedding UI in the canvas |
| onAddNode | (node: WorkflowNode) => void | undefined | Called when a node is added via the canvas API |
| onPaneContextMenu | (position, event) => void | undefined | Called on pane right-click with flow coordinates |
| renderNodePicker | (args) => ReactNode | undefined | Render a node picker at flow position; receives addNode and close |
| onNodeDrop | (event, position, type) => void | undefined | Optional drag-and-drop hook for external node sources (use WORKFLOW_NODE_DRAG_TYPE) |
| renderControls | (api: WorkflowCanvasAPI) => ReactNode | undefined | Custom controls slot with imperative canvas API |
| showAddNodeInControls | boolean | false | Show add-node menu on default zoom controls |
| ref | WorkflowCanvasHandle | undefined | Imperative API: addNode, createNode, getNodes, getEdges, validateGraph, fitView, zoomIn, zoomOut, screenToFlowPosition |
| height | string | number | undefined | Explicit canvas height; parent should size the container when omitted |
| autoFitView | boolean | false | Fit viewport to nodes on mount |
| minimap | boolean | ReactNode | undefined (built-in minimap) | Built-in minimap when omitted or true; false to hide; ReactNode for a custom minimap |
| controls | ReactNode | false | undefined (built-in zoom controls) | Built-in controls when omitted; false to hide; ReactNode for custom controls |
| background | boolean | true | Toggle the dot grid background |