data/skills/n8n-code-javascript/ERROR_PATTERNS.md
Complete guide to avoiding the most common Code node errors.
This guide covers the top 5 error patterns encountered in n8n Code nodes. Understanding and avoiding these errors will save you significant debugging time.
Error Frequency:
Frequency: Most common error (38% of all validation failures)
What Happens:
// ❌ ERROR: No code at all
// (Empty code field)
// ❌ ERROR: Code executes but doesn't return anything
const items = $input.all();
// Process items
for (const item of items) {
console.log(item.json.name);
}
// Forgot to return!
// ❌ ERROR: Early return path exists, but not all paths return
const items = $input.all();
if (items.length === 0) {
return []; // ✅ This path returns
}
// Process items
const processed = items.map(item => ({json: item.json}));
// ❌ Forgot to return processed!
// ✅ CORRECT: Always return data
const items = $input.all();
// Process items
const processed = items.map(item => ({
json: {
...item.json,
processed: true
}
}));
return processed; // ✅ Return statement present
// ✅ CORRECT: Return empty array if no items
const items = $input.all();
if (items.length === 0) {
return []; // Valid: empty array when no data
}
// Process and return
return items.map(item => ({json: item.json}));
// ✅ CORRECT: All code paths return
const items = $input.all();
if (items.length === 0) {
return [];
} else if (items.length === 1) {
return [{json: {single: true, data: items[0].json}}];
} else {
return items.map(item => ({json: item.json}));
}
// All paths covered
[{json: {...}}])Frequency: 8% of validation failures
What Happens:
n8n has TWO distinct syntaxes:
{{ }} - Used in OTHER nodes (Set, IF, HTTP Request){{ }})Many developers mistakenly use expression syntax inside Code nodes.
// ❌ WRONG: Using n8n expression syntax in Code node
const userName = "{{ $json.name }}";
const userEmail = "{{ $json.body.email }}";
return [{
json: {
name: userName,
email: userEmail
}
}];
// Result: Literal string "{{ $json.name }}", NOT the value!
// ❌ WRONG: Trying to evaluate expressions
const value = "{{ $now.toFormat('yyyy-MM-dd') }}";
// ✅ CORRECT: Use JavaScript directly (no {{ }})
const userName = $json.name;
const userEmail = $json.body.email;
return [{
json: {
name: userName,
email: userEmail
}
}];
// ✅ CORRECT: JavaScript template literals (use backticks)
const message = `Hello, ${$json.name}! Your email is ${$json.email}`;
return [{
json: {
greeting: message
}
}];
// ✅ CORRECT: Direct variable access
const item = $input.first().json;
return [{
json: {
name: item.name,
email: item.email,
timestamp: new Date().toISOString() // JavaScript Date, not {{ }}
}
}];
| Context | Syntax | Example |
|---|---|---|
| Set node | {{ }} expressions | {{ $json.name }} |
| IF node | {{ }} expressions | {{ $json.age > 18 }} |
| HTTP Request URL | {{ }} expressions | {{ $json.userId }} |
| Code node | JavaScript | $json.name |
| Code node strings | Template literals | `Hello ${$json.name}` |
// WRONG → RIGHT conversions
// ❌ "{{ $json.field }}"
// ✅ $json.field
// ❌ "{{ $now }}"
// ✅ new Date().toISOString()
// ❌ "{{ $node['HTTP Request'].json.data }}"
// ✅ $node["HTTP Request"].json.data
// ❌ `{{ $json.firstName }} {{ $json.lastName }}`
// ✅ `${$json.firstName} ${$json.lastName}`
Frequency: 5% of validation failures
What Happens:
Code nodes MUST return:
json property// ❌ WRONG: Returning object instead of array
return {
json: {
result: 'success'
}
};
// Missing array wrapper []
// ❌ WRONG: Returning array without json wrapper
return [
{id: 1, name: 'Alice'},
{id: 2, name: 'Bob'}
];
// Missing json property
// ❌ WRONG: Returning plain value
return "processed";
// ❌ WRONG: Returning items without mapping
return $input.all();
// Works if items already have json property, but not guaranteed
// ❌ WRONG: Incomplete structure
return [{data: {result: 'success'}}];
// Should be {json: {...}}, not {data: {...}}
// ✅ CORRECT: Single result
return [{
json: {
result: 'success',
timestamp: new Date().toISOString()
}
}];
// ✅ CORRECT: Multiple results
return [
{json: {id: 1, name: 'Alice'}},
{json: {id: 2, name: 'Bob'}},
{json: {id: 3, name: 'Carol'}}
];
// ✅ CORRECT: Transforming array
const items = $input.all();
return items.map(item => ({
json: {
id: item.json.id,
name: item.json.name,
processed: true
}
}));
// ✅ CORRECT: Empty result
return [];
// Valid when no data to return
// ✅ CORRECT: Conditional returns
if (shouldProcess) {
return [{json: {result: 'processed'}}];
} else {
return [];
}
[...]json property[{json: {...}}] or [{json: {...}}, {json: {...}}]{json: {...}} (missing array wrapper)[{...}] (missing json property)// Scenario 1: Single object from API
const response = $input.first().json;
// ✅ CORRECT
return [{json: response}];
// ❌ WRONG
return {json: response};
// Scenario 2: Array of objects
const users = $input.all();
// ✅ CORRECT
return users.map(user => ({json: user.json}));
// ❌ WRONG
return users; // Risky - depends on existing structure
// Scenario 3: Computed result
const total = $input.all().reduce((sum, item) => sum + item.json.amount, 0);
// ✅ CORRECT
return [{json: {total}}];
// ❌ WRONG
return {total};
// Scenario 4: No results
// ✅ CORRECT
return [];
// ❌ WRONG
return null;
Frequency: 6% of validation failures
What Happens:
This error typically occurs when:
// ❌ WRONG: Unescaped quote in string
const message = "It's a nice day";
// Single quote breaks string
// ❌ WRONG: Unbalanced brackets in regex
const pattern = /\{(\w+)\}/; // JSON storage issue
// ❌ WRONG: Multi-line string with quotes
const html = "
<div class="container">
<p>Hello</p>
</div>
";
// Quote balance issues
// ✅ CORRECT: Escape quotes
const message = "It\\'s a nice day";
// Or use different quotes
const message = "It's a nice day"; // Double quotes work
// ✅ CORRECT: Escape regex properly
const pattern = /\\{(\\w+)\\}/;
// ✅ CORRECT: Template literals for multi-line
const html = `
<div class="container">
<p>Hello</p>
</div>
`;
// Backticks handle multi-line and quotes
// ✅ CORRECT: Escape backslashes
const path = "C:\\\\Users\\\\Documents\\\\file.txt";
| Character | Escape As | Example |
|---|---|---|
| Single quote in single-quoted string | \\' | 'It\\'s working' |
| Double quote in double-quoted string | \\" | "She said \\"hello\\"" |
| Backslash | \\\\ | "C:\\\\path" |
| Newline | \\n | "Line 1\\nLine 2" |
| Tab | \\t | "Column1\\tColumn2" |
// ✅ BEST: Use template literals for complex strings
const message = `User ${name} said: "Hello!"`;
// ✅ BEST: Use template literals for HTML
const html = `
<div class="${className}">
<h1>${title}</h1>
<p>${content}</p>
</div>
`;
// ✅ BEST: Use template literals for JSON
const jsonString = `{
"name": "${name}",
"email": "${email}"
}`;
Frequency: Very common runtime error
What Happens:
// ❌ WRONG: No null check - crashes if user doesn't exist
const email = item.json.user.email;
// ❌ WRONG: Assumes array has items
const firstItem = $input.all()[0].json;
// ❌ WRONG: Assumes nested property exists
const city = $json.address.city;
// ❌ WRONG: No validation before array operations
const names = $json.users.map(user => user.name);
// ✅ CORRECT: Optional chaining
const email = item.json?.user?.email || '[email protected]';
// ✅ CORRECT: Check array length
const items = $input.all();
if (items.length === 0) {
return [];
}
const firstItem = items[0].json;
// ✅ CORRECT: Guard clauses
const data = $input.first().json;
if (!data.address) {
return [{json: {error: 'No address provided'}}];
}
const city = data.address.city;
// ✅ CORRECT: Default values
const users = $json.users || [];
const names = users.map(user => user.name || 'Unknown');
// ✅ CORRECT: Try-catch for risky operations
try {
const email = item.json.user.email.toLowerCase();
return [{json: {email}}];
} catch (error) {
return [{
json: {
error: 'Invalid user data',
details: error.message
}
}];
}
// Pattern 1: Optional chaining (modern, recommended)
const value = data?.nested?.property?.value;
// Pattern 2: Logical OR with default
const value = data.property || 'default';
// Pattern 3: Ternary check
const value = data.property ? data.property : 'default';
// Pattern 4: Guard clause
if (!data.property) {
return [];
}
const value = data.property;
// Pattern 5: Try-catch
try {
const value = data.nested.property.value;
} catch (error) {
const value = 'default';
}
// Webhook data requires extra safety
// ❌ RISKY: Assumes all fields exist
const name = $json.body.user.name;
const email = $json.body.user.email;
// ✅ SAFE: Check each level
const body = $json.body || {};
const user = body.user || {};
const name = user.name || 'Unknown';
const email = user.email || 'no-email';
// ✅ BETTER: Optional chaining
const name = $json.body?.user?.name || 'Unknown';
const email = $json.body?.user?.email || 'no-email';
// ❌ RISKY: No length check
const items = $input.all();
const firstId = items[0].json.id;
// ✅ SAFE: Check length
const items = $input.all();
if (items.length > 0) {
const firstId = items[0].json.id;
} else {
// Handle empty case
return [];
}
// ✅ BETTER: Use $input.first()
const firstItem = $input.first();
const firstId = firstItem.json.id; // Built-in safety
// ❌ RISKY: Direct access
const config = $json.settings.advanced.timeout;
// ✅ SAFE: Step by step with defaults
const settings = $json.settings || {};
const advanced = settings.advanced || {};
const timeout = advanced.timeout || 30000;
// ✅ BETTER: Optional chaining
const timeout = $json.settings?.advanced?.timeout ?? 30000;
// Note: ?? (nullish coalescing) vs || (logical OR)
Frequency: The most common "this worked yesterday in old n8n" error after upgrading to v2.0+
What Happens:
UnsupportedFunctionError: The function "helpers.httpRequestWithAuthentication" is not supported in the Code Nodehelpers.requestWithAuthenticationPaginatedSince n8n v2.0, Code nodes execute in the task runner sandbox which deliberately blocks the auth helpers. The legacy vm2 sandbox used to bind them, which is why old forum posts and tutorials show them working. n8n's source comment explains why: the Code node has no credential of its own, so the helper had nothing to authenticate against — it was always semantically broken, just not always loud about it.
// ❌ BLOCKED in task runner sandbox (default since v2.0)
const data = await $helpers.httpRequestWithAuthentication.call(
this,
'baseLinkerApi',
{ url: '...', method: 'POST' }
);
There is no env flag to re-enable these in the runner — the deny-list is compiled-in. Pick one of:
Option A — Replace the Code node with an HTTP Request node (best):
The HTTP Request node natively supports credential attachment with full expression support for URL/body/headers. Most "Code-node-makes-an-API-call" patterns are leftovers from before HTTP Request had pagination and expression support.
Option B — Sub-workflow with HTTP Request node (when you need code-level logic before/after):
// Parent Code node — prepare payloads, then delegate
return $input.all().map(i => ({ json: {
url: 'https://api.example.com/things',
method: 'POST',
body: { sku: i.json.sku }
}}));
Then wire to Execute Workflow → child workflow with Execute Workflow Trigger → HTTP Request node using ={{ $json.url }}, ={{ $json.body }}, with the credential attached natively.
Option C — Token as runtime data (only when the token genuinely flows through the workflow):
// ✅ Works — manual auth header, token came from upstream
const token = $('Get Token').first().json.access_token;
const data = await $helpers.httpRequest({
url: 'https://api.example.com/data',
headers: { 'Authorization': `Bearer ${token}` }
});
| Need | Use |
|---|---|
| Single authenticated API call | HTTP Request node directly |
| Many API calls + pre/post processing | Sub-workflow pattern (Option B) |
| Token already in the data flow | Manual $helpers.httpRequest() with header |
httpRequestWithAuthentication | Doesn't work — pick A, B, or C above |
Frequency: Common in hardened production instances
What Happens:
$env is not defined or ReferenceError: $env is not defined$env access is gated by the N8N_BLOCK_ENV_ACCESS_IN_NODE environment variable. When set to true (a common production hardening setting), $env is removed from the Code node sandbox entirely. This is increasingly the default in security-conscious deployments.
// ❌ Throws if N8N_BLOCK_ENV_ACCESS_IN_NODE=true
const apiKey = $env.API_KEY;
Treat secrets as a credential concern, not a Code-node concern:
// ✅ Token arrives as data from an upstream node that used a credential
const apiKey = $('Set Secret').first().json.apiKey;
// Or: secret was attached server-side by an HTTP Request node with the credential
// — your Code node never sees the raw secret, which is the whole point
For values you genuinely need to inject from outside the workflow (config, not secrets), use:
$secrets) if your edition supports it.Skills and tutorials written before 2024 routinely use $env.API_KEY because it was the path of least resistance. Modern n8n setups block it because letting Code nodes read arbitrary env vars is a privilege escalation surface — any user with workflow-edit access could exfiltrate DB_PASSWORD, N8N_ENCRYPTION_KEY, etc. Don't fight the restriction; route secrets through credentials.
Use this checklist before deploying Code nodes:
[...]json property: {json: {...}}[{json: {...}}]{{ }} expression syntax (use JavaScript)`${variable}`.body| Error Message | Likely Cause | Fix |
|---|---|---|
| "Code cannot be empty" | Empty code field | Add meaningful code |
| "Code must return data" | Missing return statement | Add return [...] |
| "Return value must be an array" | Returning object instead of array | Wrap in [...] |
| "Each item must have json property" | Missing json wrapper | Use {json: {...}} |
| "Unexpected token" | Expression syntax {{ }} in code | Remove {{ }}, use JavaScript |
| "Cannot read property X of undefined" | Missing null check | Use optional chaining ?. |
| "Cannot read property X of null" | Null value access | Add guard clause or default |
| "Unmatched expression brackets" | Quote/bracket imbalance | Check string escaping |
| "UnsupportedFunctionError ... httpRequestWithAuthentication" | Auth helper blocked in task runner | Use HTTP Request node + credential, or sub-workflow pattern (Error #6) |
| "$env is not defined" | N8N_BLOCK_ENV_ACCESS_IN_NODE=true | Route secrets through credentials, not $env (Error #7) |
| "Cannot find module 'crypto'" | require() allowlist not set | Move logic out of Code node, or set N8N_RUNNERS_ALLOWED_BUILT_IN_MODULES |
const items = $input.all();
console.log('Items count:', items.length);
console.log('First item:', items[0]);
// Check browser console (F12) for output
// Debug by returning current state
const items = $input.all();
const processed = items.map(item => ({json: item.json}));
// Return to see what you have
return processed;
try {
// Your code here
const result = riskyOperation();
return [{json: {result}}];
} catch (error) {
// See what failed
return [{
json: {
error: error.message,
stack: error.stack
}
}];
}
const items = $input.all();
// Check what you received
console.log('Input structure:', JSON.stringify(items[0], null, 2));
// Then process
Top 7 Errors to Avoid:
{{ }} (8%) - Use JavaScript, not expressions[{json: {...}}]?.httpRequestWithAuthentication blocked - Use HTTP Request node + credential$env blocked - Route secrets through credentials, not env accessQuick Prevention:
[{json: {...}}] format{{ }} expressionsSee Also: