Concepts

How Wire thinks

Five primitives describe the whole library. Internalise them once and the API maps cleanly.

json
{
  "version": 1,
  "id": "support-agent",
  "title": "Support agent",
  "layout": "LR",
  "nodes": [
    { "id": "webhook", "kind": "trigger", "title": "Ticket webhook" },
    { "id": "plan",    "kind": "ai",      "title": "Plan answer",
      "from": "webhook", "model": "gpt-5.4-mini" },
    { "id": "respond", "kind": "action",  "title": "Send response",
      "from": "plan",   "tone": "success" }
  ],
  "edges": []
}
KindJSX equivalentPurpose
trigger<TriggerNode>Entry point of a flow (webhook, schedule, manual).
action<ActionNode>Side-effect step — call an API, send a notification.
ai<AINode>LLM call. Carries `model`, `prompt`, optional `tools`.
tool<ToolNode>Tool invocation referenced by `ref` (e.g. `zendesk.update_ticket`).
condition<ConditionNode>Branching node. Requires `branches: string[]`.
human<HumanNode>Manual approval / human-in-the-loop.
memory<MemoryNode>Read/write to a persistent store.
retrieval<RetrievalNode>RAG / vector lookup step.
guardrail<GuardrailNode>Policy/safety check before downstream steps run.
end<EndNode>Terminal node — explicit flow end.
note<Note>Annotation; uses `attachedTo` for visual association.
group<Group>Container; wraps children via `parent` linkage.
ts
type WireAction =
  | { type: "node.add"; node: WireNode }
  | { type: "node.patch"; id: string; patch: Partial<WireNode> }
  | { type: "node.remove"; id: string }
  | { type: "node.move"; id: string; position: Point }
  | { type: "node.resize"; id: string; size: Size }
  | { type: "edge.connect"; edge: WireEdge }
  | { type: "edge.patch"; id: string; patch: Partial<WireEdge> }
  | { type: "edge.disconnect"; from: string; to: string }
  | { type: "edge.remove"; id: string }
  | { type: "diagram.patch"; patch: Partial<WireDiagram> }
  | { type: "metadata.patch"; patch: Record<string, unknown> }
  | { type: "layout.apply"; layout: "LR" | "TB" }
  | { type: "group.add"; id: string; children: string[] }
  | { type: "group.ungroup"; id: string }
  | { type: "note.add"; note: WireNote };
ts
type WireEvent =
  | { type: "node.click";       source: WireEventSource; nodeId: string }
  | { type: "node.inspect";     source: WireEventSource; nodeId: string }
  | { type: "edge.click";       source: WireEventSource; edgeId: string }
  | { type: "selection.change"; source: WireEventSource; selection: WireSelection }
  | { type: "pane.click";       source: WireEventSource };

type WireEventSource =
  | "canvas" | "node-card" | "node-list"
  | "option-panel" | "validation-panel" | "workspace" | "api";
CodeSeverityMeaning
schema.*errorZod schema rejection (top-level shape, missing required fields, etc.).
node.duplicate-iderrorTwo nodes share the same id.
node.attached-to-missingerror`attachedTo` points to a missing node.
node.parent-missingerror`parent` points to a missing node.
node.parent-not-grouperror`parent` points to a node that isn't a `group`.
condition.no-brancheserrorCondition node has no `branches`.
condition.duplicate-brancherrorRepeated branch name on a condition.
edge.from-missingerrorSource node referenced by an edge does not exist.
edge.to-missingerrorTarget node referenced by an edge does not exist.
edge.branch-from-non-conditionerror`id.branch` syntax used from a non-condition source.
edge.unknown-brancherrorBranch name not declared on the source condition.
node.orphanwarningNode has no incoming or outgoing edges.
edge.self-loopwarningNode references itself.
ts
const options: WireOptionCatalog = {
  "*":     [{ key: "owner", storage: "data" }],
  ai:      [
    { key: "model",       storage: "node", type: "select",
      options: ["gpt-5.4-mini", "gpt-4.1-mini", "o4-mini"] },
    { key: "temperature", type: "number",  min: 0, max: 2, step: 0.1 },
    { key: "maxSteps",    type: "number",  min: 1, max: 20 }
  ],
  retrieval: [
    { key: "index", type: "text" },
    { key: "topK",  type: "number", min: 1, max: 20 }
  ]
};
tsx
function Card(ctx: WireNodeRenderContext) {
  return (
    <div aria-selected={ctx.selected}>
      <span className="kind">{ctx.kind}</span>
      <strong>{ctx.node.title}</strong>
      {ctx.optionSpecs.map((spec) => (
        <span key={spec.key}>{spec.label}: {String(ctx.options[spec.key])}</span>
      ))}
    </div>
  );
}

<WireWorkspace renderNodeCard={Card} renderGroup={GroupFrame} />;
NextCustomize cards