n8n-io/n8n · error · TypeError

Cannot pass an OutputSelector to .${method}(). ${sourceName}

Error message

Cannot pass an OutputSelector to .${method}(). ${sourceName}.output(${value.outputIndex}) by itself does not connect anything; chain `.to(target)` on the selector first to produce a connection. Example: .add(${sourceName}.output(${value.outputIndex}).to(targetNode)) — not .add(${sourceName}.output(${value.outputIndex})).to(targetNode).

What it means

An OutputSelector (the object returned by node.output(n)) does not itself perform a connection — it must be terminated with .to(target) to produce a connection. Passing a bare OutputSelector to workflow.add() or workflow.to() is a no-op that the builder explicitly rejects, because the resulting workflow would silently lack the intended wire. The error shows the correct form: chain .to(target) on the selector before passing it to .add().

Source

Thrown at packages/@n8n/workflow-sdk/src/workflow-builder.ts:1277

			// Note: Composites are handled by tryPluginDispatch at the entry point
			const alreadyPresent = nodes.has(branch.name);
			const actualKey = this.addNodeWithSubnodes(nodes, branch);
			if (!alreadyPresent) {
				this.addSingleNodeConnectionTargets(nodes, branch);
			}
			// If the node was renamed, track it and return the actual key
			if (actualKey && actualKey !== branch.name) {
				effectiveNameMapping.set(branch.id, actualKey);
			}
			return actualKey ?? branch.name;
		}
	}
}

function assertNotOutputSelector(value: unknown, method: 'add' | 'to'): void {
	if (!isOutputSelector(value)) return;
	const sourceName = value.node.name;
	throw new TypeError(
		`Cannot pass an OutputSelector to .${method}(). ` +
			`${sourceName}.output(${value.outputIndex}) by itself does not connect anything; ` +
			'chain `.to(target)` on the selector first to produce a connection. ' +
			`Example: .add(${sourceName}.output(${value.outputIndex}).to(targetNode)) ` +
			`— not .add(${sourceName}.output(${value.outputIndex})).to(targetNode).`,
	);
}

/**
 * Helper to check if options is a WorkflowBuilderOptions object
 */
function isWorkflowBuilderOptions(
	options: WorkflowSettings | WorkflowBuilderOptions | undefined,
): options is WorkflowBuilderOptions {
	if (!options) return false;
	// WorkflowBuilderOptions has 'settings' or 'registry' as keys
	// WorkflowSettings has keys like 'timezone', 'executionOrder', etc.
	return 'settings' in options || 'registry' in options;

View on GitHub (pinned to 5ac6606e81)

Solutions

  1. Always terminate an OutputSelector with .to(target): workflow.add(myNode.output(0).to(targetNode)).
  2. Do not pass myNode.output(0) as the target of another .to() — selectors are sources, not destinations.

Example fix

// before
workflow.add(myNode.output(0)); // throws
workflow.add(trigger({})).to(myNode.output(0)); // also throws

// after
workflow.add(myNode.output(0).to(targetNode));
Defensive patterns

Strategy: type-guard

Validate before calling

import { isOutputSelector } from 'workflow-sdk';

function isCompleteConnection(v: unknown): boolean {
  return !isOutputSelector(v); // a terminated selector is no longer a bare OutputSelector
}

Type guard

import { isOutputSelector } from 'workflow-sdk';
function isBareOutputSelector(v: unknown): boolean {
  return isOutputSelector(v);
}

Prevention

When it happens

Trigger: Writing workflow.add(myNode.output(0)) without the trailing .to(target), or workflow.add(myNode).to(myNode.output(0)) — both leave an unterminated selector. Also triggered when a helper returns node.output(i) and the caller forgets to chain .to().

Common situations: Misreading the fluent API examples; partial copy-paste that drops the .to(target) tail; refactoring that splits selector creation from connection.

Related errors


AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12). Data as JSON: /api/errors/3195b670ca192927. Report an issue: GitHub.