n8n-io/n8n · error · Error

Named object syntax does not support .onDone() - branches ar

Error message

Named object syntax does not support .onDone() - branches are configured in the constructor

What it means

Thrown by NamedSplitInBatchesBuilderImpl.onDone(). Symmetric to onEachBatch: the named-object builder variant fixes the done branch in the constructor and does not accept the fluent .onDone() method, which would mutate the done target after construction.

Source

Thrown at packages/@n8n/workflow-sdk/src/workflow-builder/control-flow-builders/split-in-batches.ts:550

			// expressed in the connection target (e.g., each: sibNode or each: processNode.to(sibNode))
			// The workflow-builder's _hasLoop handling is only for the fluent API's .loop() method
		}
	}

	/**
	 * Fluent API: Not supported for named syntax builder (use constructor branches instead)
	 */
	onEachBatch(_target: BranchTarget): this {
		throw new Error(
			'Named object syntax does not support .onEachBatch() - branches are configured in the constructor',
		);
	}

	/**
	 * Fluent API: Not supported for named syntax builder (use constructor branches instead)
	 */
	onDone(_target: BranchTarget): this {
		throw new Error(
			'Named object syntax does not support .onDone() - branches are configured in the constructor',
		);
	}

	getAllNodes(): Array<NodeInstance<string, string, unknown>> {
		return [this.sibNode, ...this._doneNodes, ...this._eachNodes];
	}

	getDoneNodes(): Array<NodeInstance<string, string, unknown>> {
		return this._doneNodes;
	}

	getEachNodes(): Array<NodeInstance<string, string, unknown>> {
		return this._eachNodes;
	}

	hasLoop(): boolean {
		return this._hasLoop;

View on GitHub (pinned to 5ac6606e81)

Solutions

  1. Provide the done branch in the constructor object: splitInBatches('sib', { each, done: endNode }).
  2. Switch to the fluent builder variant if you need .onDone().
  3. Decide on one style (named vs fluent) per SplitInBatches and stay with it.

Example fix

// before
const sib = splitInBatches('sib', { each: processNode });
sib.onDone(endNode); // throws
// after
const sib = splitInBatches('sib', { each: processNode, done: endNode });
Defensive patterns

Strategy: type-guard

Validate before calling

function isNamedSyntaxBuilder(b: unknown): boolean {
  return b instanceof NamedSplitInBatchesBuilderImpl;
}

if (isNamedSyntaxBuilder(builder)) {
  throw new Error('Use constructor branches { each, done }, not .onDone()');
}

Type guard

function supportsFluentBranches(b: SplitInBatchesBuilder): boolean {
  return !Object.is(b.onDone, NamedSplitInBatchesBuilderImpl.prototype.onDone);
}

Try / catch

try {
  builder.onDone(target);
} catch (e) {
  if (e instanceof Error && /Named object syntax does not support/.test(e.message)) {
    // reconfigure via constructor branches instead
  }
  throw e;
}

Prevention

When it happens

Trigger: Constructing with named syntax and then calling .onDone(target) on the returned builder.

Common situations: Mixing styles; refactoring a fluent-style chain onto a named-syntax builder; appending a done handler after the fact.

Related errors


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