n8n-io/n8n · error · Error
Imports are not supported
Error message
Imports are not supported
What it means
Error('Imports are not supported') is thrown by jsVariablePolyfill's visitor when it encounters a visitImportExpression, i.e. a dynamic import() call inside an n8n expression. Static import statements would have already failed parsing for an expression context, so this guard specifically catches the dynamic form.
Solutions
- Use a Code node (JavaScript) which supports require() for permitted modules, instead of an inline expression.
- If a helper value is needed in the expression, compute it upstream in a Set/Code node and reference the field via $json / $node[...].
- Remove any import() call from inside {{ }}.
Example fix
// before
{{ (await import('lodash')).get($json, 'a.b') }}
// after
# In a Code node upstream:
# const _ = require('lodash');
# return [{ json: { val: _.get($input.all()[0].json, 'a.b') } }];
# Then in the expression: {{ $json.val }}
Defensive patterns
Strategy: validation
Validate before calling
// Reject dynamic import() in expressions at authoring time.
if (/\\bimport\\s*\\(/.test(codeChunk)) {
throw new Error('dynamic import() is not supported in n8n expressions');
}
Type guard
import { namedTypes } from 'ast-types';
function isImportExpression(node: unknown): node is namedTypes.ImportExpression {
return !!node && (node as any).type === 'ImportExpression';
}
Try / catch
try {
const compiled = buildExpression(chunks, hooks);
} catch (e) {
if (e instanceof Error && /Imports are not supported/.test(e.message)) {
throw new UserError('import() is blocked in expressions; use a Code node with require().');
}
throw e;
}
Prevention
- Lint for 'import(' in user expressions before save.
- Use a Code node for any module loading.
- Surface a helpful message pointing to the Code node alternative.
- Educate users that expressions are pure, module-less evaluators.
When it happens
Trigger: An n8n expression chunk contains a dynamic import, e.g. '{{ import("lodash") }}' or '{{ await import("./helper") }}'.
Common situations: A user expects to load a library on the fly from an expression; a pasted code snippet uses dynamic import for code splitting; a user attempts to reach Node built-ins via import.
Related errors
- Not a expression statement
- Cannot access " " via expression extension due to security…
- Access denied: cannot access
- Access denied: " " is excluded from filesystem reads
- AGENT_STATIC_PROMPT
AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12).
Data as JSON: /api/errors/03e724afdfd8ec3b.
Report an issue: GitHub.
Appendix: source
Thrown at packages/@n8n/tournament/src/VariablePolyfill.ts:128
},
ArrowFunctionExpression(path, parent: namedTypes.ArrowFunctionExpression, dataNode) {
// A concise arrow body that is a bare identifier (`() => process`) must be
// routed through the data context like any other free read. Params are not
// the body, and body identifiers that reference a param are left alone by
// polyfillVar's in-scope check.
if (parent.body === path.node) {
polyfillVar(path, dataNode);
}
},
};
export const jsVariablePolyfill = (
ast: types.namedTypes.File,
dataNode: DataNode,
): StatementKind[] | undefined => {
visit(ast, {
visitImportExpression(_path) {
throw new Error('Imports are not supported');
},
visitIdentifier(path) {
this.traverse(path);
const parent: ParentKind = path.parent.node;
// This is for tmpl compat
if (EXEMPT_IDENTIFIER_LIST.includes(path.node.name)) {
return;
}
switch (parent.type) {
case 'AssignmentPattern':
case 'Property':
case 'MemberExpression':
case 'OptionalMemberExpression':
case 'VariableDeclarator':
case 'ArrowFunctionExpression':
if (!customPatches[parent.type]) {View on GitHub (pinned to 5ac6606e81)