N8n Code Nodes Official
n8nOfficialPublished by n8n
- Skill
- Guidelines
- Automation and integrations
- Apache-2.0
- No issues found
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
The Code node is a powerful tool in n8n, but it is frequently misused for tasks better handled by native nodes or expressions. This guide establishes a decision hierarchy to ensure you only use Code when necessary. By prioritizing expressions and Edit Fields with arrow functions, you can significantly improve workflow performance, readability, and debuggability. Code nodes should be reserved for complex multi-source aggregation, external library usage, or stateful constructs that cannot be achieved through standard n8n components.
What it does
- Provides a decision tree to determine if a task requires a Code node or a simpler alternative.
- Explains how to use Edit Fields with arrow functions for multi-line logic without the overhead of a Code node.
- Identifies common anti-patterns where Code nodes are used unnecessarily for simple transformations.
- Outlines legitimate use cases for Code nodes, such as multi-source aggregation and complex statistical operations.
- Clarifies when to use native nodes like Crypto or XML instead of writing custom JavaScript.
- Offers performance insights comparing the overhead of the sandboxed Code node versus in-process expressions.
When to use it
- When you need to perform multi-source aggregation across multiple upstream nodes.
- When you require complex statistical operations that depend on the entire dataset.
- When you need to utilize external libraries not available in native nodes.
- When you have complex, multi-step logic that cannot be cleanly expressed in an Edit Fields node.
- When you have existing, complex logic that is impractical to refactor into native nodes.
Advantages
- Reduces workflow complexity by favoring native nodes and expressions.
- Improves execution performance by avoiding unnecessary sandbox overhead.
- Increases maintainability by keeping logic visual and modular.
- Ensures better debugging by using native tools that provide clearer error reporting.
- Prevents common pitfalls like manual binary handling or redundant XML parsing.
Usage examples
- “How do I transform a single field value in n8n?”
- The guide directs you to use an expression in an Edit Fields node (e.g., {{ $json.name.toUpperCase() }}) rather than a Code node.
- “I need to aggregate data from three different nodes, should I use a Code node?”
- Yes, this is a valid use case for a Code node as it involves multi-source aggregation across the whole dataset.
- “How do I hash a string in n8n?”
- The guide instructs you to use the native Crypto node instead of writing custom JavaScript with require('crypto').
- “Can I write multi-line logic without a Code node?”
- Yes, you can use an immediately-invoked arrow function inside an Edit Fields node to handle complex logic while remaining in-process.
Install
Download the skill as a ZIP:
https://autogineer.com/agent-skills/n8n-io/n8n-code-nodes-official/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip n8n-code-nodes-official.zip -d .agents/skills
Documentation by n8n
n8n Code Nodes
The Code node is powerful and often the wrong tool. The n8n equivalent of dropping into raw SQL when an ORM would do: real cases exist, but the moment a Code node handles logic an expression could, the workflow is harder to read, debug, and maintain. There's also a real perf gap: Code runs in a sandboxed JS runtime, expressions and Edit Fields run in-process, and the per-invocation overhead can be hundreds of times higher in Code (anecdotally ~2ms vs ~600ms for equivalent logic). For hot paths and large item counts, that compounds.
Strong defaults
Code node is a last resort. Decision order: expression (
{{...}}) → arrow function inside Edit Fields → Code node. The first two paths cover most "transform this data" tasks. Code earns its place for multi-source aggregation, external libraries, and a few specific patterns documented below.Default to JavaScript. Write JS unless the user explicitly asked for Python ("use Python here," "I'm a Python shop," pasted Python code). Everywhere else in n8n (expressions, Edit Fields) is JS, JS has a curated library allowlist (
lodash,crypto,luxon).
Decision tree
Need custom logic?
├── Is it a transformation of one or two fields?
│ └── Expression: {{ $json.foo.toUpperCase() }} or {{ $json.items.map(item => item.name).join(', ') }}
│ Most "just transform this" cases land here.
│
├── Is it multi-line, but pure data shaping (map, filter, reduce, conditional)?
│ └── Edit Fields with arrow function expression. See references/ARROW_FUNCTIONS_IN_EDIT_FIELDS.md
│
├── Does it need full statements, multiple data sources, or external libs?
│ ├── Are you SURE the above two don't work?
│ │ └── Re-read the parent. The bar is high.
│ └── Yes, genuinely needs it
│ └── Code node. See references/JAVASCRIPT_PATTERNS.md
│
└── Is it actually two separate transformations stitched together?
└── Use two nodes (Edit Fields → Edit Fields, or Edit Fields → IF). Composability beats one big Code block.For the full decision logic with examples for each branch, see references/DECISION_TREE.md.
What expressions can do that people forget
Common reaches-for-Code-node that should be expressions:
// ❌ Code node
return { name: $input.first().json.name.toUpperCase() }
// ✅ Expression in Edit Fields, "name" field
{{ $json.name.toUpperCase() }}// ❌ Code node
const items = $input.first().json.items
return { tags: items.map(item => item.tag).filter(tag => tag).join(', ') }
// ✅ Expression
{{ $json.items.map(item => item.tag).filter(tag => tag).join(', ') }}// ❌ Code node
const date = new Date($input.first().json.created_at)
return { formatted: date.toISOString().slice(0, 10) }
// ✅ Expression with n8n's date extension
{{ $json.created_at.toDateTime().format('yyyy-MM-dd') }}For more on what expressions can express, see the n8n-expressions-official skill.
What arrow-functions-in-Edit-Fields can do
Edit Fields assigns field values via expression. Inline arrow functions get you most multi-line logic without the Code node:
// In Edit Fields, "summary" field:
{{ (() => {
const items = $json.items
const total = items.reduce((sum, item) => sum + item.price, 0)
const tax = total * 0.08
return `Total: $${(total + tax).toFixed(2)}`
})() }}Right tool for "logic slightly too gnarly for a one-liner." See references/ARROW_FUNCTIONS_IN_EDIT_FIELDS.md for patterns and formatting.
When the Code node IS the right answer
Real uses exist. Bar is high, not "never." The cases below are legitimate. Build with code without apologizing.
Multi-source aggregation across the whole dataset
When a node needs to:
Read from multiple upstream nodes simultaneously (e.g.,
$('Source A').all(),$('Source B').all(),$('Source C').first().json).Compute statistics or transformations across all items at once (not per-item).
Apply multi-step logic with intermediate data structures (lookup maps, accumulators, ratios).
Most common valid case. Examples:
Analytics rollups: per-group averages, percentile scoring, normalization across categories.
Building lookup tables from one source and joining against rows from another.
Computing offsets/ratios across two reference datasets, then applying to a third.
// Real-world shape: aggregate test results from one source,
// model metadata from another, category mapping from a third,
// and produce per-model-per-category averages.
const testResults = $('Get Test Results').all().map(item => item.json)
const models = $('Get Models').all().map(item => item.json)
const categoryMap = $('Get Category Map').first().json.testCategoryMap
const categoryByTestId = Object.fromEntries(
categoryMap.map(mapping => [mapping.testId, mapping.category])
)
const result = models.map(model => {
const modelTests = testResults.filter(test => test.modelId === model.id)
const stats = modelTests.reduce((acc, test) => {
const cat = categoryByTestId[test.testId]
if (!cat) return acc
acc[cat] ??= { scored: 0, available: 0, count: 0 }
acc[cat].scored += test.pointsScored ?? 0
acc[cat].available += test.pointsAvailable ?? 0
acc[cat].count += 1
return acc
}, {})
const averages = Object.fromEntries(
Object.entries(stats).map(([category, stat]) => [
category,
{ avg: stat.available > 0 ? stat.scored / stat.available : 0, n: stat.count }
])
)
return { modelId: model.id, modelName: model.modelName, ...averages }
})
return result.map(json => ({ json }))Technically expressions can reach across items via $('Node Name').all() and reduce inline, but for anything with this much shape (joins, group-bys, nested aggregation) the result is a one-line megaexpression that's hard to read and impossible to debug. Use Code.
External libraries
JS Code can require from a curated allowlist (lodash, etc.), but expressions can't.
Always check for a native node first. n8n has more native nodes than people realize. Dropping into Code for something with a native node is a recurring mistake. The next two subsections cover the specific traps.
Cryptographic operations: use the Crypto node, not Code
HMAC, signing, hashing, encryption: n8n has a native Crypto node (n8n-nodes-base.crypto). Use it. It handles SHA256, MD5, HMAC, encrypt/decrypt, and random generation, all without writing JavaScript.
// WRONG (recurring AI slip):
const crypto = require('crypto')
const hash = crypto.createHash('sha256').update(buf).digest('hex').slice(0, 12)
// RIGHT: configure the Crypto node with operation: 'hash', type: 'SHA256'
// then read $('Hash').item.json.<output> downstream.The Code-with-require('crypto') pattern is one of the most common false positives for "this needs a Code node." It doesn't. The Crypto node covers it.
Hashing binary, not strings
Don't reach for Code just because you need to hash binary (a PDF, an image, a file buffer). The Crypto node has a binaryPropertyName parameter. Point it at the binary slot key and it hashes the buffer directly. You don't need this.helpers.getBinaryDataBuffer(...) in user code.
// WRONG (recurring AI slip with binary):
const crypto = require('crypto')
const buf = await this.helpers.getBinaryDataBuffer($itemIndex, 'data')
const hash = crypto.createHash('sha256').update(buf).digest('hex')
// RIGHT: Crypto node configured to hash binaryPropertyName='data', SHA256.
// Then $('Crypto').item.json.<output> has the hash; chain a Set node for any field shaping.The remaining valid Code-for-crypto case: a non-standard signing scheme that the Crypto node doesn't expose (e.g., a custom AWS-style signature), AND httpCustomAuth credential doesn't fit either. Rare. Justify explicitly.
XML / SOAP / RSS parsing: use the XML node, not Code
n8n has a native XML node (n8n-nodes-base.xml) with parse and stringify operations. It already converts XML to JSON. Once it has, the result is plain JSON and Edit Fields with arrow function expressions handles all the field extraction, array normalization (Array.isArray(...) ? ... : ...), and link-finding (.find()) you'd reach for Code to do.
// WRONG (recurring AI slip):
// XML node already parsed → another Code node to extract a few fields:
const entry = $('Parse XML').item.json.feed.entry
const firstEntry = Array.isArray(entry) ? entry[0] : entry
return { json: { title: firstEntry.title, url: firstEntry.link.find(link => link.type === 'pdf').href } }
// RIGHT: Edit Fields with arrow function expressions:
// title: ={{ (() => { const entry = $('Parse XML').item.json.feed.entry; return Array.isArray(entry) ? entry[0].title : entry.title; })() }}
// pdfUrl: ={{ $('Parse XML').item.json.feed.entry.link.find(link => link.type === 'pdf')?.href }}If the field-extraction logic is genuinely too gnarly for inline expressions even with multi-line arrow functions, the next stop is Edit Fields with a single multi-line arrow function, NOT a Code node. See references/ARROW_FUNCTIONS_IN_EDIT_FIELDS.md.
What these have in common
Valid cases are about scope: whole-dataset, multiple sources, or stateful constructs single-item tools can't reach. Invalid cases: Code doing what an expression could, OR what a native node already does.
Quick tests:
"Could I describe this Code node's job as 'take this one item and...'?" If yes, wrong tool.
"Is there a native node for this?" Search via
search_nodesfirst. Crypto, XML, JSON parsing, date math (Luxon), HTTP calls, file I/O, regex matching: all have native nodes or expression-level support.
JavaScript Code node specifics
Two modes:
Run Once for All Items (default, use this): runs once with
$input.all(). If you need per-item logic, justfor (const item of $input.all())inside. This is the standard shape and almost always what you want.Run Once for Each Item: runs once per item with
$input.first()(or$input.item).
Common shape:
// Run Once for All Items
const items = $input.all()
const totals = items.map(item => ({
...item.json,
total: item.json.qty * item.json.price,
}))
return totals.map(json => ({ json }))The return must be an array of { json: ... } objects (or { json: ..., binary: ... }), not raw JSON.
For binary handling, error patterns, and Code-node-only bugs, see references/JAVASCRIPT_PATTERNS.md.
Reference files
| File | Read when |
|---|---|
references/DECISION_TREE.md | You're tempted to use a Code node and want to verify the simpler paths really don't work |
references/ARROW_FUNCTIONS_IN_EDIT_FIELDS.md | The transformation is multi-line but pure data shaping |
references/JAVASCRIPT_PATTERNS.md | Code node is genuinely needed and JS is the language |
Anti-patterns
| Anti-pattern | What goes wrong | Fix |
|---|---|---|
Code node doing return { x: $input.first().json.x.toUpperCase() } | Whole node for one expression | Replace with an Edit Fields expression |
| Code node building HTML strings for an email body | The Email node's body field accepts expressions | Inline the expression into the email node |
Code node using new Date() for date formatting | Loses to Luxon's clarity | Use Luxon in expression. See n8n-expressions-official |
| Set node + Code node combo (Set builds inputs, Code transforms) | Two nodes for what should be one Edit Fields | Collapse into one Edit Fields with arrow function |
| Pasting credentials/tokens into Code node text | Same leak as text fields | Use credentials, not Code node |
Files
- ATTRIBUTION.mdAdded by AutoGineer852 B
- LICENSELicence file added by AutoGineer11.0 KiB
- SKILL.md12.0 KiB
- references/ARROW_FUNCTIONS_IN_EDIT_FIELDS.md8.3 KiB
- references/DECISION_TREE.md5.8 KiB
- references/JAVASCRIPT_PATTERNS.md6.0 KiB
references/DECISION_TREE.md
Decision tree
The bar for using a Code node is high. This file walks through verifying that simpler alternatives (expression, Edit Fields arrow function) don't work.
Stage 1: try an expression
For any single-field transform, ask: can this fit in {{...}}?
Expressions express:
Property access (
$json.foo.bar)Method chains (
$json.items.filter(...).map(...).join(...))Ternary conditionals
Arithmetic and string interpolation
Date math via Luxon
Native JS methods on arrays/strings/objects/numbers
If it fits one line, even a long one, do it as an expression. Multi-line indented form is supported. See the n8n-expressions-official skill.
Test: can you describe it as "take A and produce B" without intermediate variables? If yes, expression.
// Expression: "take items, return joined names"
{{ $json.items.map(item => item.name).join(', ') }}
// Expression: "take created_at, return YYYY-MM-DD"
{{ DateTime.fromISO($json.created_at).toFormat('yyyy-MM-dd') }}
// Expression: "take items, return their total"
{{ $json.items.reduce((sum, item) => sum + item.price, 0) }}Stage 2: try Edit Fields with an arrow function
When the transform needs intermediate variables, multi-step logic, or doesn't fit one line, use Edit Fields with an inline arrow function.
Pattern: an immediately-invoked arrow function inside the expression.
{{ (() => {
const items = $json.items
const total = items.reduce((sum, item) => sum + item.price, 0)
const tax = total * 0.08
return `Total: $${(total + tax).toFixed(2)}`
})() }}This buys you:
Multiple statements
Local variables
Early returns
Comments
What you give up vs. a Code node:
No external libraries
No
requiresNo access to
$input.all()(you have$jsonfor the current item, not all items at once)
For more, see ARROW_FUNCTIONS_IN_EDIT_FIELDS.md.
Test: does the logic operate on a single input item ($json)? If yes, Edit Fields. If you need to combine items, see Stage 3.
Stage 3: justify the Code node
Stages 1 and 2 are ruled out. Remaining valid reasons:
Reason A: External libraries
JS Code supports a curated allowlist (lodash, crypto, luxon). If you genuinely need lodash.groupBy or one of the others, Code is necessary. No HTTP client is bundled, use the HTTP Request node for any outbound calls.
Counter-question: do you actually need lodash? Modern JS has Array.prototype.reduce and Object.entries.
Reason B: Multi-source aggregation across the whole dataset
Most common valid case. When a node needs to:
Read from multiple upstream nodes simultaneously.
Compute statistics or transformations across all items at once.
Apply multi-step logic with intermediate data structures.
Code, full stop. Don't split across many Edit Fields.
Splitting an analytics rollup into 10+ smaller nodes is harder to read, debug, and iterate than one 80-line Code node with clear sections.
But: ask if part can be a built-in (Aggregate, Summarize, Sort, Group). Check via search_nodes. If a built-in covers half, do that half there.
Reason B-adjacent: Statistical operations across many items
Median, percentile, ratio-of-ratios, normalized scores: anything needing all items to compute an answer for any one. Single-item tools can't express this.
Reason C: Crypto operations
HMAC signing, JWT verification, custom encryption. JS has require('crypto'). Python has hashlib and hmac.
Counter-question: is this for auth? See n8n-credentials-and-security-official. httpCustomAuth may handle it without code.
Reason D: User has existing JS/Python they want to drop in
Worth pushing back on. n8n workflows are visual, and dropping a 200-line script defeats most of the value. Options:
Refactor into multiple nodes.
Run as a separate service called via HTTP Request.
Decide this case justifies one Code node and document why in the description.
When the user insists on a Code node
Their call. Build it, but:
Comment in the Code node explaining why simpler tools weren't used.
Keep small.
Run the workflow and verify outputs before publishing.
Examples by category
"Format a date"
One format: expression (Luxon).
Multiple formats: Edit Fields with arrow function.
Code node: only if you're computing dates from non-trivial sources (e.g., parsing freeform user input with intent disambiguation).
"Filter items by criteria"
Simple: expression on the previous node's output, or use the IF node.
Conditional with multiple criteria: Edit Fields arrow function.
Code node: only if criteria depend on aggregate stats across all input items.
"Build an email body"
Static template with field substitution: expression in the email node's body field.
Conditional sections: arrow function in the email body field.
Code node: never. Building strings is the most over-Code'd pattern in n8n.
"Transform API response into target shape"
One-to-one field mapping: Edit Fields with expressions.
Map/filter/reduce on arrays in the response: still expressions.
Combining responses from multiple HTTP calls: Merge node + Edit Fields. Code node only if the merge logic is genuinely complex.
"Validate input data"
Use the IF node or built-in validation nodes (
schema validationif available).Edit Fields with arrow function for "compute a
validboolean."Code node: rare, and usually a sign that validation should happen upstream of n8n.
The honest answer
Code feels productive because writing JS is what you know. But the workflow gets read by someone (maybe future-you) who has to understand each node at a glance, modify one piece without breaking others, and debug from runtime errors.
Visual nodes with descriptive names beat a Code block on every one. 30 seconds saved now costs hours later.
When in doubt: not a Code node.
Requirements and compatibility
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Needs: Needs Node.js
Security
No issues found by our static scan· No issues found
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Published by
- n8n
- Publisher
- n8n (Official)
- Source
- n8n-io/skills · skills/n8n-code-nodes-official · commit 27b6b9f
- Licence
- Apache License 2.0 (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by n8n or n8n.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download n8n-code-nodes-official.zip