{"record":{"id":"78222326faa4c9c2","repo":"n8n-io/n8n","slug":"not-a-expression-statement","errorCode":null,"errorMessage":"Not a expression statement","messagePattern":"Not a expression statement","errorType":"exception","errorClass":"SyntaxError","httpStatus":null,"severity":"error","filePath":"packages/@n8n/tournament/src/ExpressionBuilder.ts","lineNumber":223,"sourceCode":"\t\tchunks.length > 2 ||\n\t\tchunks[0].text !== '' ||\n\t\t// This is a blank expression. It should just return an empty string\n\t\t(chunks[0].text === '' && chunks.length === 1)\n\t) {\n\t\tlet parts: ExpressionKind[] = [];\n\t\tfor (const chunk of chunks) {\n\t\t\t// This is just a text chunks, push it up as a literal.\n\t\t\tif (chunk.type === 'text') {\n\t\t\t\tparts.push(b.literal(chunk.text));\n\t\t\t\t// This is a code chunk so do some magic\n\t\t\t} else {\n\t\t\t\tconst fixed = fixStringNewLines(chunk.parsed);\n\t\t\t\tfor (const hook of hooks.before) {\n\t\t\t\t\thook(fixed, dataNode);\n\t\t\t\t}\n\t\t\t\tconst parsed = jsVariablePolyfill(fixed, dataNode)?.[0];\n\t\t\t\tif (!parsed || parsed.type !== 'ExpressionStatement') {\n\t\t\t\t\tthrow new SyntaxError('Not a expression statement');\n\t\t\t\t}\n\n\t\t\t\tfor (const hook of hooks.after) {\n\t\t\t\t\thook(parsed, dataNode);\n\t\t\t\t}\n\n\t\t\t\tconst functionBody = buildFunctionBody(parsed.expression);\n\n\t\t\t\tif (shouldWrapInTry(parsed)) {\n\t\t\t\t\t// Wraps the body of our expression function in a try/catch\n\t\t\t\t\t// to match tmpl\n\t\t\t\t\tfunctionBody.body = [\n\t\t\t\t\t\twrapInErrorHandler(functionBody.body[0]),\n\t\t\t\t\t\t// This is for tmpl compat. It puts a ; after the try/catch\n\t\t\t\t\t\t// creating an empty statement. emptyStatement is just printed\n\t\t\t\t\t\t// to nothing so we use an expression statement with a blank\n\t\t\t\t\t\t// identifier.\n\t\t\t\t\t\tb.expressionStatement(b.identifier('')),","sourceCodeStart":205,"sourceCodeEnd":241,"githubUrl":"https://github.com/n8n-io/n8n/blob/5ac6606e81f67bb9534255570cd4e86fd8101eee/packages/@n8n/tournament/src/ExpressionBuilder.ts#L205-L241","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\n{{ let x = $json.value + 1; x * 2 }}\n// after\n{{ ($json.value + 1) * 2 }}\n","handlingStrategy":"validation","validationCode":"// Reject statement keywords inside a multi-chunk expression before evaluating.\nconst stmtRe = /^\\\\s*(let|const|var|if|for|while|return|function|class|do|switch|throw)\\\\b/;\nif (stmtRe.test(codeChunk.trim())) {\n  throw new Error('expression chunks must be a single expression, not a statement');\n}\n","typeGuard":"import { namedTypes } from 'ast-types';\nfunction isExpressionStatement(\n  node: unknown,\n): node is namedTypes.ExpressionStatement {\n  return !!node && (node as any).type === 'ExpressionStatement';\n}\n","tryCatchPattern":"try {\n  const compiled = buildExpression(chunks, hooks);\n} catch (e) {\n  if (e instanceof SyntaxError && /Not a expression statement/.test(e.message)) {\n    throw new UserError('Each {{ }} must contain a single expression; use a Code node for statements.');\n  }\n  throw e;\n}\n","preventionTips":["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."],"tags":["n8n","expression","typescript","ast","syntax"],"backgroundTag":null,"analyzedSha":"5ac6606e81f67bb9534255570cd4e86fd8101eee","analyzedAt":"2026-08-12T05:26:35.080Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}