This page describes the visual flow editor UI in Langflow: the ReactFlow-based canvas where users create, connect, and configure component nodes. It covers the component hierarchy from the page entry point down to individual node rendering, drag-and-drop mechanics, connection validation, keyboard shortcuts, and the node toolbar.
For details on how node input and output parameters are rendered, see GenericNode Component. For how connections between ports are type-checked, see Connection Validation. For the Zustand stores that back this UI, see State Management. For how a build is triggered from this page, see Build and Execution Flow.
The flow editor is mounted at the /flow/:id route and rendered by FlowPage. It composes a sidebar, the ReactFlow canvas, a floating toolbar, and an optional inspection/playground panel.
Figure: FlowPage component hierarchy
Sources: src/frontend/src/pages/FlowPage/index.tsx1-230 src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx1-95
FlowPage (src/frontend/src/pages/FlowPage/index.tsx31-230) is the route-level component. Its responsibilities are:
| Responsibility | Implementation |
|---|---|
Load flow by URL param id | useGetFlow() mutation, called in useEffect |
| Block navigation on unsaved changes | useBlocker from react-router-dom |
| Stop build on navigation | stopBuilding() from flowStore |
| Provide component type registry | useGetTypes() query |
| Expose playground panel | FlowPageSlidingContainerContent |
| Render canvas | <Page> (alias for PageComponent) |
The changesNotSaved flag is computed by JSON-comparing currentFlow (live edit state) with currentSavedFlow (last persisted state) using customStringify.
Sources: src/frontend/src/pages/FlowPage/index.tsx44-50 src/frontend/src/pages/FlowPage/index.tsx119-175
PageComponent (src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx) wraps the ReactFlow component from @xyflow/react with all event handlers, node/edge type registrations, and UI overlays.
const nodeTypes = {
genericNode: GenericNode,
noteNode: NoteNode,
};
const edgeTypes = {
default: DefaultEdge,
};
Source: src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx86-93
The canvas only renders if component templates and types are loaded and the initial build query has returned (showCanvas flag):
const showCanvas =
Object.keys(templates).length > 0 &&
Object.keys(types).length > 0 &&
!isFetching;
Source: src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx217-220
When helperLineEnabled is set in flowStore, dragging a node activates alignment guides. The getHelperLines function computes alignment candidates from all other nodes, and getSnapPosition returns a snapped coordinate. These are applied in onNodesChangeWithHelperLines, which intercepts position-change events before forwarding them to the standard onNodesChange handler.
Sources: src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx394-492
Figure: Drop flow — from sidebar drag to canvas node creation
Sources: src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx498-555
The onDrop handler (src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx507-555) also handles file drops:
event.dataTransfer.types contains a supported node type key, it calls addComponent with the parsed node data and screen coordinates.event.dataTransfer.types contains "Files", it calls uploadFlow to import a JSON flow file at the drop position.alertStore.The isSupportedNodeTypes utility (from src/frontend/src/utils/utils.ts) checks whether the drag data MIME type is a recognized node category string.
When the user draws an edge between two node ports, ReactFlow calls onConnect. This is wired to:
takeSnapshot() — records undo state in flowsManagerStoreonConnect(params) — the flowStore action, which calls addEdge after passing through isValidConnectionFigure: Connection validation data flow
Sources: src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx385-392 src/frontend/src/stores/flowStore.ts306-336
The onEdgeUpdate and onEdgeUpdateEnd callbacks handle dragging an existing edge endpoint to a new port. edgeUpdateSuccessful is a ref that tracks whether the reconnection landed on a valid target. If the drag is released over empty canvas, the edge is removed:
const onEdgeUpdateEnd = useCallback((_, edge: Edge): void => {
if (!edgeUpdateSuccessful.current) {
setEdges((eds) => eds.filter((edg) => edg.id !== edge.id));
}
edgeUpdateSuccessful.current = true;
}, []);
Sources: src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx557-580
All canvas-level shortcuts are bound using useHotkeys from react-hotkeys-hook. Shortcut key strings are read from useShortcutsStore, making them user-configurable (see Settings and Configuration).
| Action | Handler | Guard |
|---|---|---|
| Undo | handleUndo → undo() | not inside .noflow element |
| Redo | handleRedo → redo() | not inside .noflow element |
| Copy | handleCopy → setLastCopiedSelection | inside .react-flow__node or selection |
| Cut | handleCut → setLastCopiedSelection(_, true) | no text selection active |
| Paste | handlePaste → paste(lastCopiedSelection, pos) | not inside .noflow |
| Duplicate | handleDuplicate → paste(selectedNodes, pos) | selected nodes exist |
| Delete | handleDelete → deleteNode/deleteEdge | not inside .nodelete, not locked |
| Group | handleGroup → handleGroupNode | selection menu visible |
| Download | handleDownload → opens ExportModal | not inside .noflow |
| Escape | handleEscape → setRightClickedNodeId(null) | always |
The isWrappedWithClass utility (src/frontend/src/pages/FlowPage/components/PageComponent/utils/is-wrapped-with-class.ts) walks up the DOM tree to check if the event target is inside a CSS-class-guarded element (e.g. noflow, nodelete, nodrag), preventing shortcuts from firing inside text inputs inside nodes.
Sources: src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx230-383
When multiple nodes are selected, SelectionMenu appears. Pressing the group shortcut (or using the menu) calls handleGroupNode:
validateSelection(lastSelection, edges) — checks that no edge both starts and ends inside the selection (which would prevent clean grouping).generateFlow(selection, nodes, edges, name) — creates a FlowType object representing the sub-flow.generateNodeFromFlow(newFlow, getNodeId) — wraps the sub-flow as a single group node.setNodes.The inverse operation, Ungroup, is available via NodeToolbarComponent and calls expandGroupNode from reactflowUtils.ts.
Sources: src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx169-201 src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx205-229
Each node on the canvas is a GenericNode component (src/frontend/src/CustomNodes/GenericNode/index.tsx). It receives data: NodeDataType and selected: boolean as props.
Figure: GenericNode internal structure
Sources: src/frontend/src/CustomNodes/GenericNode/index.tsx486-672
Nodes have a showNode flag (stored in data.showNode). When false, the node collapses to a compact header-only view with width w-48 instead of w-80. Inputs and outputs still render but in a condensed layout. Minimization is only allowed if the node has at most one connected input (enforced by the isMinimal check in NodeToolbarComponent).
Sources: src/frontend/src/CustomNodes/GenericNode/index.tsx113 src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx137-138
useBuildStatus returns the current BuildStatus for a node (e.g. TO_BUILD, BUILDING, BUILT, ERROR). The CustomNodeStatus component uses this to set setBorderColor, which is applied as a Tailwind class to the outer div of the node, visually indicating build state.
Sources: src/frontend/src/CustomNodes/GenericNode/index.tsx90 src/frontend/src/CustomNodes/GenericNode/index.tsx588-602
When a node's component code is outdated compared to the current template in typesStore, componentsToUpdate in flowStore will contain an entry for that node. GenericNode reads this via useShallow and renders either NodeUpdateComponent (to prompt an update) or NodeLegacyComponent (for deprecated components with a replacement). Users can dismiss these banners; dismissed node IDs are persisted in localStorage under the key dismiss_<flowId>.
Sources: src/frontend/src/CustomNodes/GenericNode/index.tsx125-139 src/frontend/src/CustomNodes/GenericNode/index.tsx364-372
NodeToolbarComponent (src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx) appears above a node when it is selected (and no other node is selected) or when right-clicked.
Figure: NodeToolbarComponent — buttons and menu items
Sources: src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx486-788
Right-clicking a node calls onNodeContextMenu in PageComponent, which:
setRightClickedNodeId(node.id) on flowStoreGenericNode checks rightClickedNodeId === data.id and passes openDropdownOnRightClick={true} to NodeToolbarComponent, which programmatically opens the "More" dropdown via setDropdownOpen(true).
Sources: src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx611-655 src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx307-314
Toggling Tool Mode on a node adds a component_as_tool output. This is done by calling mutateTemplate which POSTs to the backend with the new tool_mode value. The node's template is reloaded and updateNodeInternals forces ReactFlow to re-measure handles.
Sources: src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx169-183
FlowToolbar (src/frontend/src/components/core/flowToolbarComponent) is rendered at the top of the canvas area. It provides flow-level actions: run/build, open playground, flow settings, export, undo/redo, and the note-adding mode toggle. For build-related behavior triggered from the toolbar, see Build and Execution Flow.
| Component | Purpose |
|---|---|
SelectionMenu | Multi-node action menu (group, copy, delete) shown after box-select |
InspectionPanel | Slide-out panel for inspecting node outputs inline |
FlowBuildingComponent | Renders the build progress overlay |
HelperLines | SVG lines drawn during node drag for alignment |
ConnectionLineComponent | Custom rendering of in-progress edge being drawn |
UpdateAllComponents | Bulk update banner when multiple nodes are outdated |
MemoizedBackground | ReactFlow Background grid pattern |
MemoizedCanvasControls | Zoom in/out/fit controls |
MemoizedSidebarTrigger | Toggle button for the component sidebar |
Sources: src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx67-84
The canvas relies on three primary Zustand stores:
| Store | Key state for the canvas |
|---|---|
useFlowStore | nodes, edges, reactFlowInstance, currentFlow, isBuilding, flowPool, rightClickedNodeId, lastCopiedSelection, dismissedNodes |
useFlowsManagerStore | currentFlowId, takeSnapshot, undo, redo, autoSaving |
useTypesStore | types, templates — required before canvas renders |
Figure: Canvas state read/write paths
Sources: src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx104-153 src/frontend/src/CustomNodes/GenericNode/index.tsx80-100 src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx62-100
Whenever setNodes or setEdges is called in flowStore, if autoSaveFlow is defined, it is called immediately after updating state. autoSaveFlow is set by PageComponent via useAutoSaveFlow, which debounces saves to the backend.
// flowStore.ts — setNodes action
get().updateCurrentFlow({ nodes: newChange, edges: newEdges });
if (get().autoSaveFlow) {
get().autoSaveFlow!();
}
Sources: src/frontend/src/stores/flowStore.ts306-323 src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx226-228
In addition to genericNode, the canvas supports noteNode — a sticky-note type. Adding a note is toggled via a button in FlowToolbar, which sets isAddingNote state in PageComponent. While in note-adding mode, a shadow box follows the cursor. Clicking places a NoteNode at the cursor position with default dimensions NOTE_NODE_MIN_WIDTH × NOTE_NODE_MIN_HEIGHT.
NoteNode has its own NoteDataType and noteClassType and does not participate in connection validation or build execution.
Sources: src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx160-168 src/frontend/src/types/flow/index.ts46-62
Export is triggered by the download keyboard shortcut or via NodeToolbarComponent. It opens ExportModal, which:
downloadFlow (or removeApiKeys first if the checkbox is unchecked) from reactflowUtils.ts.json file via the browserImport happens on file drop (onDrop with "Files" data type) or via sidebar buttons. uploadFlow reads the JSON, calls processDataFromFlow to normalize node IDs and edge handles, and then adds the flow to the canvas.
Sources: src/frontend/src/modals/exportModal/index.tsx53-100 src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx532-553
Refresh this wiki
This wiki was recently refreshed. Please wait 3 days to refresh again.