Heuristic:FlowiseAI Flowise Edge Connection Type Matching
| Knowledge Sources | |
|---|---|
| Domains | Validation, Flow_Editor |
| Last Updated | 2026-02-12 07:30 GMT |
Overview
Type-safe edge connection validation ensuring only compatible node types can be connected in the Flowise canvas editor.
Description
Flowise uses a handle-based type system for validating edge connections between nodes. Each node output handle encodes its type in the handle ID format: `{nodeId}-output-{outputName}-{Type1|Type2}`. Similarly, input handles follow: `{nodeId}-input-{inputName}-{Type1|Type2}`. When a user drags an edge between nodes, the system extracts the type suffix from both handles and checks for intersection. For chatflows, it also enforces single vs. list connection constraints (an input marked as `list` accepts multiple edges; otherwise only one). For agentflow v2, an additional DFS-based cycle detection prevents creating DAG violations.
Usage
Apply this heuristic when building custom nodes for Flowise, debugging connection errors in the canvas, or understanding why certain node types cannot connect. The type matching system determines which integrations are compatible without trial and error.
The Insight (Rule of Thumb)
- Action: When defining custom node outputs, encode the compatible types in the handle ID using the pipe (`|`) separator for multiple types
- Value: Format: `{nodeId}-output-{name}-{BaseType1|BaseType2}` — the last segment after the final dash contains the type(s)
- Trade-off: Overly permissive types (e.g., accepting `BaseChain`) allow connections that may fail at runtime; overly restrictive types reduce composability
- Key Rules:
- Types are extracted from the last dash-separated segment of the handle ID
- Multiple types are separated by the pipe character (`|`)
- A type match occurs when any source type matches any target type
- Non-list inputs accept only one incoming edge; list inputs accept multiple
- AgentFlow v2 additionally rejects self-connections and cycle-creating edges
Reasoning
The type system prevents incompatible connections at design time rather than at runtime. For example, connecting a `ChatOpenAI` (type: `BaseChatModel`) to an input expecting `BaseRetriever` would fail at execution. The handle-based approach embeds type information directly in the ReactFlow handle ID, avoiding the need for a separate type registry lookup. The list vs. single constraint ensures that inputs expecting a single model do not accidentally receive multiple conflicting models.
The agentflow v2 cycle detection uses DFS graph traversal: before adding edge `source -> target`, it checks if a path exists from `target` back to `source` in the existing graph. If such a path exists, adding the edge would create a cycle, which is invalid for a DAG-based execution flow.
Code Evidence
Type extraction and matching from `packages/ui/src/utils/genericHelper.js:414-449`:
export const isValidConnection = (connection, reactFlowInstance) => {
const sourceHandle = connection.sourceHandle
const targetHandle = connection.targetHandle
const target = connection.target
//sourceHandle: "llmChain_0-output-llmChain-BaseChain"
//targetHandle: "mrlkAgentLLM_0-input-model-BaseLanguageModel"
let sourceTypes = sourceHandle.split('-')[sourceHandle.split('-').length - 1].split('|')
sourceTypes = sourceTypes.map((s) => s.trim())
let targetTypes = targetHandle.split('-')[targetHandle.split('-').length - 1].split('|')
targetTypes = targetTypes.map((t) => t.trim())
if (targetTypes.some((t) => sourceTypes.includes(t))) {
// Check single vs list constraint
if ((targetNodeInputAnchor && !targetNodeInputAnchor?.list &&
!reactFlowInstance.getEdges().find((e) => e.targetHandle === targetHandle)) ||
targetNodeInputAnchor?.list) {
return true
}
}
return false
}
Cycle detection for AgentFlow v2 from `packages/ui/src/utils/genericHelper.js:451-506`:
export const isValidConnectionAgentflowV2 = (connection, reactFlowInstance) => {
const source = connection.source
const target = connection.target
// Prevent self connections
if (source === target) { return false }
// Check if this connection would create a cycle in the graph
if (wouldCreateCycle(source, target, reactFlowInstance)) { return false }
return true
}
const wouldCreateCycle = (sourceId, targetId, reactFlowInstance) => {
// Build directed graph from existing edges
const graph = {}
const edges = reactFlowInstance.getEdges()
edges.forEach((edge) => {
if (!graph[edge.source]) graph[edge.source] = []
graph[edge.source].push(edge.target)
})
// DFS: Check if there's a path from target to source
function hasPath(current, destination) {
if (current === destination) return true
if (visited.has(current)) return false
visited.add(current)
for (const neighbor of (graph[current] || [])) {
if (hasPath(neighbor, destination)) return true
}
return false
}
return hasPath(targetId, sourceId)
}