n8n-io/n8n · error · SyntaxError
Not a expression statement
Error message
Not a expression statement
What it means
SyntaxError('Not a expression statement') is thrown in the multi-chunk branch of ExpressionBuilder when jsVariablePolyfill returns no statement or a statement whose type is not 'ExpressionStatement'. n8n expressions in a templated string must each be a single expression; a statement (declaration, if, for, return) is not allowed.
Source
Thrown at packages/@n8n/tournament/src/ExpressionBuilder.ts:223
chunks.length > 2 ||
chunks[0].text !== '' ||
// This is a blank expression. It should just return an empty string
(chunks[0].text === '' && chunks.length === 1)
) {
let parts: ExpressionKind[] = [];
for (const chunk of chunks) {
// This is just a text chunks, push it up as a literal.
if (chunk.type === 'text') {
parts.push(b.literal(chunk.text));
// This is a code chunk so do some magic
} else {
const fixed = fixStringNewLines(chunk.parsed);
for (const hook of hooks.before) {
hook(fixed, dataNode);
}
const parsed = jsVariablePolyfill(fixed, dataNode)?.[0];
if (!parsed || parsed.type !== 'ExpressionStatement') {
throw new SyntaxError('Not a expression statement');
}
for (const hook of hooks.after) {
hook(parsed, dataNode);
}
const functionBody = buildFunctionBody(parsed.expression);
if (shouldWrapInTry(parsed)) {
// Wraps the body of our expression function in a try/catch
// to match tmpl
functionBody.body = [
wrapInErrorHandler(functionBody.body[0]),
// This is for tmpl compat. It puts a ; after the try/catch
// creating an empty statement. emptyStatement is just printed
// to nothing so we use an expression statement with a blank
// identifier.
b.expressionStatement(b.identifier('')),View on GitHub (pinned to 5ac6606e81)
Solutions
- Rewrite the chunk as a single expression: use an IIFE '{{ (() => { let x = 1; return x; })() }}' if local variables are truly needed, or inline the logic.
- Move complex logic out of the expression and into a Code node / Set node, then reference the result from the expression.
- Remove statement keywords (let/const/return/if/for) from inside {{ }}.
- If parsing fails entirely, check for unbalanced braces or stray characters inside the chunk.
Example fix
// before
{{ let x = $json.value + 1; x * 2 }}
// after
{{ ($json.value + 1) * 2 }}
Defensive patterns
Strategy: validation
Validate before calling
// Reject statement keywords inside a multi-chunk expression before evaluating.
const stmtRe = /^\\s*(let|const|var|if|for|while|return|function|class|do|switch|throw)\\b/;
if (stmtRe.test(codeChunk.trim())) {
throw new Error('expression chunks must be a single expression, not a statement');
}
Type guard
import { namedTypes } from 'ast-types';
function isExpressionStatement(
node: unknown,
): node is namedTypes.ExpressionStatement {
return !!node && (node as any).type === 'ExpressionStatement';
}
Try / catch
try {
const compiled = buildExpression(chunks, hooks);
} catch (e) {
if (e instanceof SyntaxError && /Not a expression statement/.test(e.message)) {
throw new UserError('Each {{ }} must contain a single expression; use a Code node for statements.');
}
throw e;
}
Prevention
- Validate user expressions before saving: parse each code chunk and assert type === 'ExpressionStatement'.
- Use a Code node for any logic needing declarations or control flow.
- Show the offending chunk in the UI so the user can locate the statement keyword.
- Lint for 'let/const/return/if' inside {{ }} at authoring time.
When it happens
Trigger: An n8n expression code chunk (inside {{ }}) contains something that is not a single expression: e.g. '{{ let x = 1 }}', '{{ if (true) {1} }}', '{{ return 5 }}', '{{ const y = 2 }}', or a chunk that fails to parse entirely so parsed is undefined.
Common situations: A user writes JS control-flow or variable declarations inside an n8n expression expecting it to behave like a script; a pasted code snippet includes statements; a migration from another templating engine brought in non-expression syntax.
Related errors
- Imports are not supported
- MISSING_EXPRESSION_PREFIX
- intersection(): expected array arg, e.g. .intersection([1, 2
- isBetween(): expected exactly two args
- Unsupported unit '${String(errorUnit)}'. Supported: ${durati
AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12).
Data as JSON: /api/errors/78222326faa4c9c2.
Report an issue: GitHub.