Skip to content
Learn Agentic AI
Learn Agentic AI13 min read2 views

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.

flowchart LR
    INPUT(["User intent"])
    PARSE["Parse plus<br/>classify"]
    PLAN["Plan and tool<br/>selection"]
    AGENT["Agent loop<br/>LLM plus tools"]
    GUARD{"Guardrails<br/>and policy"}
    EXEC["Execute and<br/>verify result"]
    OBS[("Trace and metrics")]
    OUT(["Outcome plus<br/>next action"])
    INPUT --> PARSE --> PLAN --> AGENT --> GUARD
    GUARD -->|Pass| EXEC --> OUT
    GUARD -->|Fail| AGENT
    AGENT --> OBS
    style AGENT fill:#4f46e5,stroke:#4338ca,color:#fff
    style GUARD fill:#f59e0b,stroke:#d97706,color:#1f2937
    style OBS fill:#ede9fe,stroke:#7c3aed,color:#1e1b4b
    style OUT fill:#059669,stroke:#047857,color:#fff
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.

Hear it before you finish reading

Talk to a live CallSphere AI voice agent in your browser — 60 seconds, no signup.

Try Live Demo →
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.

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.

Still reading? Stop comparing — try CallSphere live.

CallSphere ships complete AI voice agents per industry — 14 tools for healthcare, 10 agents for real estate, 4 specialists for salons. See how it actually handles a call before you book a demo.

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

Share

Try CallSphere AI Voice Agents

See how AI voice agents work for your industry. Live demo available -- no signup required.