n8n-io/n8n · error · UnsupportedNodeError

Unsupported syntax: '${node.type}: ${FORBIDDEN_NODE_TYPES[no

Error message

Unsupported syntax: '${node.type}: ${FORBIDDEN_NODE_TYPES[node.type]}' is not allowed in SDK code

What it means

Thrown by validateNodeType when the AST node type is listed in FORBIDDEN_NODE_TYPES. Each forbidden type carries a remediation string (e.g., ArrowFunctionExpression → 'Use fromAi() directly instead of ($) => $.fromAi()'). The message includes both the node type and the remediation. This is the primary static-rejection gate for unsupported JavaScript constructs.

Source

Thrown at packages/@n8n/workflow-sdk/src/ast-interpreter/validators.ts:263

	ThrowStatement: 'Throw statements are not allowed in SDK code',
	WithStatement: 'With statements are not allowed in SDK code',
	UpdateExpression: 'Update expressions (++, --) are not allowed in SDK code',
	NewExpression: 'new expressions are not allowed. Use SDK factory functions instead.',
	ImportDeclaration: 'Import declarations are not allowed in SDK code',
	ImportExpression: 'Dynamic imports are not allowed in SDK code',
	ExportNamedDeclaration: 'Named exports are not allowed. Use export default only.',
	ExportAllDeclaration: 'Re-exports are not allowed in SDK code',
	AwaitExpression: 'Await expressions are not allowed in SDK code',
	YieldExpression: 'Yield expressions are not allowed in SDK code',
};

/**
 * Check if a node type is allowed.
 * @throws UnsupportedNodeError if the node type is not allowed
 */
export function validateNodeType(node: Node, sourceCode: string): void {
	if (FORBIDDEN_NODE_TYPES[node.type]) {
		throw new UnsupportedNodeError(
			`${node.type}: ${FORBIDDEN_NODE_TYPES[node.type]}`,
			node.loc ?? undefined,
			sourceCode,
		);
	}

	if (!ALLOWED_NODE_TYPES.has(node.type)) {
		throw new UnsupportedNodeError(node.type, node.loc ?? undefined, sourceCode);
	}
}

/**
 * Check if an identifier is a dangerous global.
 * @throws SecurityError if the identifier is dangerous
 */
export function validateIdentifier(
	name: string,
	_allowedVariables: Set<string>,

View on GitHub (pinned to 5ac6606e81)

Solutions

  1. Replace arrow functions / callbacks with the direct SDK helper (e.g., use fromAi() instead of ($) => $.fromAi()).
  2. Replace loops with declarative data: build arrays via array literals, not iteration.
  3. Replace `new X(...)` with the corresponding SDK factory function (workflow/node/trigger/etc.).
  4. Remove try-catch, throw, await, yield — SDK builder code is synchronous and side-effect-free.
  5. Use a single `export default` — remove named exports and re-exports.

Example fix

// before
export default workflow().add(
  node('LLM').parameters({ prompt: ($) => $.fromAi('text') })
);

// after
export default workflow().add(
  node('LLM').parameters({ prompt: fromAi('text') })
);
Defensive patterns

Strategy: validation

Validate before calling

import { parseSDKCode } from '@n8n/workflow-sdk/ast-interpreter/parser';
import { FORBIDDEN_NODE_TYPES } from '@n8n/workflow-sdk/ast-interpreter/validators';

function findForbiddenNodes(code: string): { type: string; remedy: string; line?: number }[] {
  const hits: { type: string; remedy: string; line?: number }[] = [];
  const ast = parseSDKCode(code);
  JSON.stringify(ast, (k, v) => {
    if (v && typeof v === 'object' && 'type' in v && FORBIDDEN_NODE_TYPES[(v as { type: string }).type]) {
      const t = (v as { type: string; loc?: { start: { line: number } } }).type;
      hits.push({ type: t, remedy: FORBIDDEN_NODE_TYPES[t], line: (v as { loc?: { start: { line: number } } }).loc?.start.line });
    }
    return v;
  });
  return hits;
}

Type guard

import { FORBIDDEN_NODE_TYPES } from '@n8n/workflow-sdk/ast-interpreter/validators';

function isForbiddenNode(type: string): boolean {
  return type in FORBIDDEN_NODE_TYPES;
}

Try / catch

import { interpretSDKCode } from '@n8n/workflow-sdk/ast-interpreter/interpreter';
import { UnsupportedNodeError } from '@n8n/workflow-sdk/ast-interpreter/errors';

try {
  interpretSDKCode(code, sdkFunctions);
} catch (e) {
  if (e instanceof UnsupportedNodeError) {
    // e.message contains the node type + remediation string from FORBIDDEN_NODE_TYPES
  }
  throw e;
}

Prevention

When it happens

Trigger: Using arrow functions (ArrowFunctionExpression), function declarations/expressions, classes, for/while/do-while/for-in/for-of loops, try-catch, throw, update expressions (++/--), new expressions, import/export declarations (except default), await, or yield in SDK code.

Common situations: Pasting a standard JS function into SDK code; using `($) => $.fromAi()` instead of `fromAi()`; writing loops to build node arrays; using `++` in an index; `new Array(...)`; `await` in async-flavored code; named exports.

Understand the failure class

Related errors


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