n8n-io/n8n · error · Error

Unable to parse function body

Error message

Unable to parse function body

What it means

Thrown by extractFunctionBody in code-helpers.ts when Function.prototype.toString() of the supplied callback matches neither the arrow-function regex nor the `function(ctx)` regex. The helpers runOnceForAllItems / runOnceForEachItem convert a TS closure into a Code-node source string by stringifying the function and rewriting the ctx. parameter; anything that diverges from the two supported shapes cannot be transformed.

Source

Thrown at packages/@n8n/workflow-sdk/src/utils/code-helpers.ts:48

		return body;
	}

	// Handle regular functions: function(ctx) { ... }
	const funcMatch = fnStr.match(/function\s*\(\s*(\w+)\s*\)\s*\{([\s\S]*)\}$/);
	if (funcMatch) {
		const paramName = funcMatch[1];
		let body = funcMatch[2].trim();

		// Replace parameter name
		body = body.replace(new RegExp(`${paramName}\\.\\$`, 'g'), '$');
		body = body.replace(new RegExp(`${paramName}\\(`, 'g'), '$(');
		body = body.replace(new RegExp(`${paramName}\\.`, 'g'), '');

		return body;
	}

	throw new Error('Unable to parse function body');
}

/**
 * Create a code helper for executing once with access to all items
 *
 * The function receives a context object with:
 * - ctx.$input.all() - get all input items
 * - ctx.$input.first() - get first input item
 * - ctx.$input.last() - get last input item
 * - ctx.$input.itemMatching(i) - get item at index
 * - ctx.$env, ctx.$vars, ctx.$secrets - environment access
 * - ctx.$now, ctx.$today - date helpers
 * - ctx.$execution, ctx.$workflow - metadata
 * - ctx('NodeName') - reference other node outputs
 *
 * @param fn - Function that processes all items and returns an array
 * @returns Code configuration for the Code node
 *

View on GitHub (pinned to 5ac6606e81)

Solutions

  1. Pass an arrow function (ctx) => { ... } or a named function(ctx) { ... } with exactly one parameter.
  2. Avoid .bind() — capture closure variables directly inside the arrow body.
  3. In bundler setups, mark the callback as non-mangled (function names preserved) or inline the source string.

Example fix

// before
runOnceForAllItems(this.processItems.bind(this));
// after
runOnceForAllItems((ctx) => {
  const items = ctx.$input.all();
  return [{ json: { sum: items.length } }];
});
Defensive patterns

Strategy: validation

Validate before calling

function isSupportedCodeHelperFn(fn: unknown): boolean {
  if (typeof fn !== 'function') return false;
  const s = fn.toString();
  return /^\s*\(?\w+\)?\s*=>\s+.+$/s.test(s) || /function\s*\(\s*\w+\s*\)\s*\{[\s\S]*\}$/.test(s);
}

if (!isSupportedCodeHelperFn(cb)) {
  throw new Error('Pass an arrow (ctx) => {...} or function(ctx) {...} with one parameter');
}

Type guard

function isArrowOrNamedFunction(ctx: unknown, fn: unknown): fn is (ctx: any) => any {
  if (typeof fn !== 'function') return false;
  const s = fn.toString();
  return /^\s*\(?\w+\)?\s*=>/.test(s) || /^\s*function\s*\(\s*\w+\s*\)/.test(s);
}

Try / catch

try {
  runOnceForAllItems(cb as (ctx: AllItemsContext) => any);
} catch (e) {
  if (e instanceof Error && e.message === 'Unable to parse function body') {
    throw new Error('Code helper could not stringify the callback; pass an un-bound arrow or function(ctx).');
  }
  throw e;
}

Prevention

When it happens

Trigger: Passing a generator function (function*), an async function whose stringified form carries modifiers the regex skips, a bound function (.bind()), a method reference, or a zero-argument function. Minified/transpiled builds that mangle fn.toString() also fail the match.

Common situations: Production bundlers rename or inline functions so fn.toString() no longer matches `(ctx) => ...`; developer passes a class method `this.handler.bind(this)`; using an async generator for streaming-style code.

Understand the failure class

Related errors


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