Building a Drag-and-Drop Agent Builder: Visual Workflow Editor with React
Create a visual agent workflow editor using React, drag-and-drop libraries, and a node-based canvas. Learn node rendering, connection drawing, and workflow serialization.
Why Visual Agent Builders Matter
Not every agent designer is a developer. Product managers, domain experts, and operations teams need to define agent workflows — which tools to call, when to escalate, how to route conversations — without writing code. A visual workflow editor lets them drag agent nodes onto a canvas, connect them with edges, and configure behavior through form panels. React Flow is the dominant library for building these interfaces in React.
Setting Up React Flow
React Flow provides the canvas, node rendering, edge drawing, and interaction handling. Install it and create a basic workflow editor.
import {
ReactFlow,
Background,
Controls,
MiniMap,
Node,
Edge,
useNodesState,
useEdgesState,
addEdge,
Connection,
} from "@xyflow/react";
import "@xyflow/react/dist/style.css";
const initialNodes: Node[] = [
{
id: "triage",
type: "agentNode",
position: { x: 250, y: 50 },
data: { label: "Triage Agent", model: "gpt-4o" },
},
{
id: "support",
type: "agentNode",
position: { x: 100, y: 250 },
data: { label: "Support Agent", model: "gpt-4o-mini" },
},
{
id: "billing",
type: "agentNode",
position: { x: 400, y: 250 },
data: { label: "Billing Agent", model: "gpt-4o-mini" },
},
];
const initialEdges: Edge[] = [
{ id: "e1", source: "triage", target: "support", label: "support" },
{ id: "e2", source: "triage", target: "billing", label: "billing" },
];
function WorkflowEditor() {
const [nodes, setNodes, onNodesChange] =
useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] =
useEdgesState(initialEdges);
const onConnect = (connection: Connection) => {
setEdges((eds) => addEdge(connection, eds));
};
return (
<div className="w-full h-[700px] border rounded-xl">
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
nodeTypes={nodeTypes}
fitView
>
<Background />
<Controls />
<MiniMap />
</ReactFlow>
</div>
);
}
Custom Agent Nodes
Default nodes are plain rectangles. Create custom nodes that display agent information with connection handles for inputs and outputs.
import { Handle, Position, NodeProps } from "@xyflow/react";
interface AgentNodeData {
label: string;
model: string;
}
function AgentNode({ data }: NodeProps) {
const nodeData = data as unknown as AgentNodeData;
return (
<div className="bg-white border-2 border-blue-200 rounded-xl
shadow-md px-4 py-3 min-w-[180px]">
<Handle
type="target"
position={Position.Top}
className="w-3 h-3 bg-blue-500"
/>
<div className="flex items-center gap-2 mb-1">
<div className="w-8 h-8 bg-blue-100 rounded-lg
flex items-center justify-center text-lg">
A
</div>
<div>
<p className="font-semibold text-sm">{nodeData.label}</p>
<p className="text-xs text-gray-500">{nodeData.model}</p>
</div>
</div>
<Handle
type="source"
position={Position.Bottom}
className="w-3 h-3 bg-blue-500"
/>
</div>
);
}
const nodeTypes = { agentNode: AgentNode };
The Handle components define connection points. target handles accept incoming edges, source handles start outgoing edges. Position them at the top and bottom for a top-to-bottom flow layout.
The Node Palette with Drag-and-Drop
A sidebar palette lets users drag new node types onto the canvas. Use React DnD or the native HTML drag API.
See AI Voice Agents Handle Real Calls
Book a free demo or calculate how much you can save with AI voice automation.
const nodeTemplates = [
{ type: "agentNode", label: "Agent", icon: "A" },
{ type: "toolNode", label: "Tool", icon: "T" },
{ type: "conditionNode", label: "Condition", icon: "?" },
{ type: "outputNode", label: "Output", icon: "O" },
];
function NodePalette() {
const onDragStart = (
event: React.DragEvent,
nodeType: string
) => {
event.dataTransfer.setData(
"application/reactflow",
nodeType
);
event.dataTransfer.effectAllowed = "move";
};
return (
<div className="w-48 border-r p-4 space-y-2">
<h3 className="font-semibold text-sm mb-3">Components</h3>
{nodeTemplates.map((tpl) => (
<div
key={tpl.type}
draggable
onDragStart={(e) => onDragStart(e, tpl.type)}
className="flex items-center gap-2 p-2 border rounded-lg
cursor-grab hover:bg-gray-50"
>
<span className="w-7 h-7 bg-gray-100 rounded flex
items-center justify-center text-sm">
{tpl.icon}
</span>
<span className="text-sm">{tpl.label}</span>
</div>
))}
</div>
);
}
Drop Handler on the Canvas
When a node is dropped on the canvas, calculate its position relative to the React Flow viewport and add it to the nodes array.
import { useReactFlow } from "@xyflow/react";
function useDropHandler(
setNodes: React.Dispatch<React.SetStateAction<Node[]>>
) {
const { screenToFlowPosition } = useReactFlow();
const onDrop = (event: React.DragEvent) => {
event.preventDefault();
const type = event.dataTransfer.getData("application/reactflow");
if (!type) return;
const position = screenToFlowPosition({
x: event.clientX,
y: event.clientY,
});
const newNode: Node = {
id: crypto.randomUUID(),
type,
position,
data: { label: `New ${type}`, model: "gpt-4o-mini" },
};
setNodes((nds) => [...nds, newNode]);
};
const onDragOver = (event: React.DragEvent) => {
event.preventDefault();
event.dataTransfer.dropEffect = "move";
};
return { onDrop, onDragOver };
}
Serializing the Workflow
The workflow must be saved to a backend. Serialize the nodes and edges into a JSON structure that your agent runtime can interpret.
interface SerializedWorkflow {
version: string;
nodes: Array<{
id: string;
type: string;
config: Record<string, unknown>;
}>;
edges: Array<{
source: string;
target: string;
condition?: string;
}>;
}
function serializeWorkflow(
nodes: Node[],
edges: Edge[]
): SerializedWorkflow {
return {
version: "1.0",
nodes: nodes.map((n) => ({
id: n.id,
type: n.type || "agentNode",
config: n.data as Record<string, unknown>,
})),
edges: edges.map((e) => ({
source: e.source,
target: e.target,
condition: e.label as string | undefined,
})),
};
}
FAQ
How do I add a configuration panel that opens when a node is clicked?
Listen for the onNodeClick event on the ReactFlow component. Store the selected node ID in state and conditionally render a side panel with form fields for that node's configuration (model, system prompt, tools, temperature). Update the node's data in the nodes array when the form changes.
How do I validate the workflow before saving?
Check that all nodes have at least one incoming or outgoing edge (except the start node). Verify there are no cycles if your agent runtime does not support them. Ensure every condition edge has a non-empty label. Run these validations before serialization and highlight invalid nodes with a red border.
Can I undo and redo changes in the editor?
Yes. Maintain a history stack of { nodes, edges } snapshots. Push a new snapshot on every meaningful change (node add, delete, move, connect). Pop from the stack on undo. Use a separate redo stack that gets cleared when a new change is made after an undo.
#DragAndDrop #VisualEditor #ReactFlow #TypeScript #WorkflowBuilder #AgenticAI #LearnAI #AIEngineering
CallSphere Team
Expert insights on AI voice agents and customer communication automation.
Try CallSphere AI Voice Agents
See how AI voice agents work for your industry. Live demo available -- no signup required.