- Two forms of a node
- BuilderNodeSchema (registry form)
- InputParam (the heart of the model)
- Anchors and outputs
- The initNode transformation
- BuilderNodeData (live canvas form)
- Handle-id grammar
- The conditional visibility engine
- Edges
- FlowDocument (the saved artifact)
- The
{{ref}}convention (connected inputs) - Extending the model
This is the normative spec of flowkit’s graph model: what a node is, how a registry
schema becomes a live canvas node, the handle-id grammar, the visibility engine, the
edge model, and the serialized flow document. Types quoted are from
packages/flow-builder/src/types.ts.
A node exists in two forms, linked by initNode:
BuilderNodeSchema initNode() BuilderNodeData
(registry/palette form) ───────────────▶ (live canvas form)
backend or static list embedded in the React Flow nodeWhat registry.list() returns per palette entry. JSON-serializable; unknown extra
fields are tolerated ([key: string]: unknown).
| Field | Type | Description |
|---|---|---|
|
string |
Unique machine name ( |
|
string |
Human name shown in palette and on the card. |
|
string |
Palette grouping. Convention: |
|
number |
Schema version; drives outdated-node warnings and sync flows. |
|
string |
Palette subtitle. |
|
|
Chip on the palette entry. |
|
string |
Icon URL (img fallback when |
|
string |
Base color for card tint and edge gradients. |
|
string[] |
Type signature of the node’s own output when no |
|
boolean |
No output anchors (terminal/sink nodes). |
|
boolean |
No target handle (source/start nodes). |
|
Raw input definitions — split by |
|
|
InputParam & \{ credentialNames?: string[] } |
Credential slot; unshifted to the top of the form, stored on |
|
Declared outputs (multi-anchor or options-group, per strategy). |
|
|
string |
Reserved canvas-type hint — |
One object drives both forms and ports. All fields optional except label/name/type.
| Field | Type | Description |
|---|---|---|
|
string |
Display label, storage key in |
|
string |
Handle/param id — assigned by |
|
unknown |
Initial value; also seeds visibility evaluation. |
|
string |
Shown as an info tooltip next to the label. |
|
boolean |
Omits the red |
|
string |
Input placeholder. |
|
number |
Multiline text (adds the expand-dialog affordance in Flowise’s string renderer). |
|
boolean |
Anchor accepts multiple incoming edges (validation: |
|
boolean |
Field accepts |
|
|
Choices for |
|
|
Conditional visibility maps — see The conditional visibility engine. |
|
boolean |
Runtime flag set by the visibility engine ( |
|
boolean |
Always hidden from the inspector (internal params). |
|
boolean |
Render read-only. |
|
string |
Method name for |
|
boolean |
Selecting an async option reveals a nested config form (Flowise’s |
|
|
Item template for the |
|
number |
Minimum items before the array delete button hides. |
|
|
Tab definitions for the |
|
string |
Persistence key prefix for the selected tab. |
|
|
Column defs for the |
|
string |
|
|
string |
CodeMirror language for the |
|
boolean |
|
|
string |
Accept filter for the |
type |
Renderer | Value shape in data.inputs[name] |
|---|---|---|
|
single-line text (variable-aware) |
string |
|
masked input w/ reveal |
string |
|
numeric input |
number |
|
switch |
boolean |
|
single-select dropdown |
string (option |
|
multi-select dropdown |
JSON-array string |
|
server-fed dropdown |
string |
|
server-fed multi dropdown |
JSON-array string |
|
JSON editor (variable-aware) |
JSON string |
|
CodeMirror editor |
string |
|
file picker |
base64/string content |
|
date picker |
date string |
|
time picker |
|
|
weekday multi-select |
string |
|
day-of-month multi-select |
string |
|
editable grid |
JSON string of row objects |
|
nested item groups |
array of objects |
|
tab strip of nested params |
selected tab name (under |
Anything not in this table (e.g. Flowise’s credential, conditionFunction) falls
through to the default renderer (plain text) unless the consumer registers one —
see extending.
- InputAnchor — a connection port on the input side
-
{ id?, label, name, type, list?, optional?, hidden? }wheretypeis a|-separated list of accepted types ("BaseLanguageModel|BaseChatModel"or"Record[]"). - OutputOption — one selectable output on a multi-output anchor
-
{ id, name, label, description?, type?, isAnchor?, hidden? } - OutputAnchor — the node’s output side
-
{ id?, name, label, type?, description?, options?: OutputOption[], default? }
initNode(schema, newNodeId, { isConnectionParam?, outputStrategy? }) mutates the
schema into canvas data (packages/flow-builder/src/utils/graph.ts):
-
Split — each raw
inputs[i]getsid = {nodeId}-input-{name}-{type}and goes to:-
inputParamswhenisConnectionParam(param)is false (default: type is inFORM_PARAM_TYPES— the 18 form types above +credential,conditionFunction,folder); -
inputAnchorsotherwise (anything that looks like a port type, e.g.BaseMemory,Record[]).
-
-
Credential —
schema.credentialis unshifted ontoinputParams. -
Outputs — two strategies:
-
multi(default, v2 style): one anchor per declared output ({nodeId}-output-{index}), or a single{nodeId}-output-{name}when none declared. -
standard(v1 style, for typed ports): anoptionsoutput anchor whose options carry typed ids{nodeId}-output-{name}-{BaseClasses|Joined}; with no declared outputs, a single{nodeId}-output-{name}-{baseClasses.join('|')}anchor.
-
-
Defaults —
data.inputs = { [param.name]: param.default ?? '' }. -
Visibility pass —
showHideInputParams/showHideInputAnchorsevaluateshow/hideagainst the seeded defaults. -
Housekeeping —
outputs = {}when none,credential = ''(the id slot),id = newNodeId.
The React Flow node’s data:
interface BuilderNodeData {
// mirrored schema fields
name: string; label: string; category?: string
version?: number; description?: string; badge?: string
icon?: string; color?: string; baseClasses?: string[]
hideOutput?: boolean; hideInput?: boolean; hint?: string
credential?: InputParam & { credentialNames?: string[] }
// runtime
id: string
inputs: Record<string, unknown> // live values, keyed by param name
inputAnchors: InputAnchor[] // rendered as target handles
inputParams: InputParam[] // rendered in the inspector form
outputAnchors: OutputAnchor[] // rendered as source handles
outputs?: Record<string, unknown>
// UI state
selected?: boolean // drives the selection ring
status?: BuilderNodeStatus // consumer-driven badge: INPROGRESS/FINISHED/ERROR/TERMINATED/STOPPED...
error?: string
}handle-id = node-id "-" direction "-" param-name "-" type-signature node-id = name "_" index ; e.g. llmAgentflow_0 direction = "input" | "output" param-name = <input param or anchor name> type-signature = type *( "|" type ) ; e.g. BaseLanguageModel|BaseChatModel
-
Parsing:
parseHandleTypes(id)= last--segment, split on|, trimmed. -
Anchor-less nodes (v2 style) use the bare node id as the target handle id (
id={data.id}) — such connections carry no type signature and validate structurally only. -
Edge ids:
{source}-{sourceHandle}-{target}-{targetHandle}(unique per connection).
Params declare show/hide maps keyed by a path into nodeData.inputs:
{ "label": "Token", "name": "token", "type": "password",
"show": { "auth": ["bearer", "apikey"] } }Evaluation (showHideInputs in utils/visibility.ts), per param:
-
Reset
display = true. -
For each
showrule: setdisplay = falseunless the comparison matches. For eachhiderule: setdisplay = falsewhen the comparison matches. -
Params with
display === falseare not rendered, and their stored value is deleted frominputs(keeps saved flows clean of irrelevant values).
Comparison semantics (ground = current sibling value, comparison = rule value):
| Types | Rule |
|---|---|
string vs string |
exact match OR the rule as a regular expression matches ( |
array rule |
ground must be one of the rule values |
ground is array |
match when any element matches (intersection; JSON-string arrays like |
boolean / number |
strict equality |
object |
deep equality (lodash |
path contains |
substituted with the array item index (used inside |
options params additionally filter their options list by each option’s own
show/hide. applyVisibleInputDefaults re-applies defaults only to visible params
(used when values change in the inspector).
{
id: '{source}-{sourceHandle}-{target}-{targetHandle}',
source, sourceHandle, target, targetHandle,
type: 'builderEdge', // config.edgeType
data: {
sourceColor: string, // resolved via config.resolveNodeColor
targetColor: string, // → the SVG gradient stroke
edgeLabel?: string // e.g. condition index, gateway 'yes'/'no'
},
zIndex?: 9999 // when both ends share a group parent
}The default edge renders an SVG gradient (sourceColor → targetColor), a wide
invisible hover selector, a mid-line delete button on hover, and the optional
EdgeLabelRenderer label.
interface FlowDocument {
nodes: Node<BuilderNodeData>[] // full data snapshots (schema + inputs)
edges: Edge[]
viewport?: { x: number; y: number; zoom: number }
}-
Produced by
config.serialize(default: React FlowtoObject()withdata.selectedstripped). Consumed byloadFlow/initialFlow. -
Self-contained by design: because every node embeds its schema snapshot, a saved flow renders even if the registry no longer offers that node (the version-warning system handles drift — see
resolveNodeWarning). -
Flowise additionally hoists the credential id to
data.credentialand strips passwords/files on export — consumerserializeoverrides.
A consumer may record "this input is fed by node X" by writing a reference string into
inputs (Flowise: {{nodeId.data.instance}}, via config.onConnectInput). Rules:
-
Plain (non-list) anchor input: the whole value is the ref.
-
listanchor: the value is an array of refs. -
acceptVariableparam: the ref may be embedded in free text.
On node/edge deletion the provider’s default disconnect logic:
-
list anchor → filters refs containing the deleted source id
-
acceptVariable→ regex-removes{{sourceId.*}}from the string -
otherwise → clears the value
duplicateNode clears all refs on the copy (a duplicate must not alias the original’s
connections). Consumers replacing this wholesale implement config.refScrubber.
-
New param types — invent a
typestring; either register a renderer (fieldRenderers) or let it fall back todefault. If it should be a port instead of a form field, ensureisConnectionParamexcludes it fromFORM_PARAM_TYPES(overrideconfig.isConnectionParam). -
Richer nodes — add fields freely (
[key: string]: unknownon schema and data); render them viarenderNodeSummary,nodeRenderers, or custom fields. -
Versioned evolution — bump
version, tagbadge: 'DEPRECATING'withdeprecateMessage; surface viaresolveNodeWarning; migrate with a sync routine (Flowise’supdateOutdatedNodeData/Edgepattern). -
Custom canvas types —
schema.type+config.resolveNodeType
config.nodeRenderers(BPMN shapes in the playground are the template).