Skip to main content
The board saves two graphs. IDs must match across data.nodes, parsedData, trigger, and every edge source / target.

Canvas data (React Flow toObject())

Root nodes:
Sub-nodes are siblings in data.nodes, not nested. They carry parentId. Positions are relative to the parent, not canvas coordinates. They are not draggable — the parent moves them.

Layout via API (autoLayout)

Send autoLayout: true on graph create and PUT. The server runs the same two board operations and does not store the flag:
  1. Sub-nodes — draggable: false, extent: "parent", position.x = 138, position.y from the GUI stack (answers, then fallback/timeout).
  2. Roots — dagre left-to-right (ranksep ~120, nodesep ~60), same as the board Clean button. Children keep the relative positions from step 1.
Agents should still set parentId on buttons. You do not need to compute canvas x/y for roots or buttons. autoClean: true is accepted as an alias.
Edges:
When a node has sub-nodes, do not draw a root out edge from that parent. Execution continues from the sub-node connections.out.

Execution parsedData + trigger

transformReactFlowOutput builds this from the canvas:
  • The trigger is removed from parsedData and stored as workflow.trigger.
  • triggerType is trigger.data.eventType.
  • parsedData is parent nodes only. Sub-nodes hang off parent.subNodes[].
  • Each node has connections: { out: string[] } — target ids only. No handle ids. Branching is “which sub-node’s connections.out”.
  • Runtime starts at trigger.connections.out[0].
Parent title is usually the NodeType string. Sub-node title is the canvas data.label. If/Else matches title.toLowerCase() === "true" / "false".

Mapping checklist

How to wire a graph

Build the canvas first, then copy each edge into execution connections.out. IDs must be identical in data.nodes, data.edges[].source / target, parsedData, trigger, and every connections.out entry.

1. Give every node a stable id

Use short unique strings (trig_1, wa_menu, ans_tours). Reuse the same string everywhere. Sub-nodes live in data.nodes as siblings with parentId set to the parent’s id.

2. Draw canvas edges (data.edges)

Every edge:
Do not also draw a root "out" edge from a parent that has sub-nodes. The engine ignores parent connections.out once subNodes exist and continues from the chosen child’s connections.out. Fallback must not point at its parent. Never add a canvas edge fallback → parent and never put the parent id in fallback connections.out. Two modes (mutually exclusive — if shouldSendReplyMessage is true, runtime ignores connections.out and re-queues the same message node after sending the text):

3. Copy each edge into execution

For every canvas edge source → target, find that source in trigger, parsedData[], or parent.subNodes[] and append target to connections.out (array of target ids only — no handles). Runtime starts at trigger.connections.out[0]. If that array is missing, the run never starts.

4. Sanity check

  • Every source / target / connections.out id exists.
  • Trigger is not in parsedData.
  • Sub-nodes are not extra parsedData items; they hang off parent.subNodes[].
  • Parent with answers / true-false / cases has no root out edge.
Paired snippets: Examples.
Last modified on September 4, 2026