n8n-code-nodes-official
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chinesen8n Code Nodes
n8n Code节点
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.
Code node功能强大,但往往不是合适的工具。这就像在可以使用ORM时却直接写原生SQL:确实存在适用场景,但一旦Code node处理了本可以用表达式完成的逻辑,工作流的可读性、调试和维护难度都会上升。同时两者还存在明显的性能差距:Code节点运行在沙箱化的JS运行时中,而表达式和Edit Fields则在进程内运行,Code节点每次调用的开销可能是前者的数百倍(根据经验,等效逻辑下约2ms vs 约600ms)。对于高频路径和大量数据项,这种差距会不断累积。
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
-
Code node是最后的选择。 决策优先级:表达式()→ Edit Fields中的箭头函数 → Code node。前两种方式可以覆盖大多数“转换数据”的任务。只有在需要多源聚合、外部库,以及下文记录的少数特定模式时,才应该使用Code node。
{{...}} -
默认使用JavaScript。 除非用户明确要求使用Python(比如“这里用Python”、“我们是Python技术栈”、粘贴了Python代码),否则都使用JavaScript编写。n8n的其他所有地方(表达式、Edit Fields)都是基于JS的,并且JS有经过筛选的允许库列表(、
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.mdNeed 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.如需查看包含各分支示例的完整决策逻辑,请参阅。
references/DECISION_TREE.mdWhat expressions can do that people forget
人们容易忘记的表达式能力
Common reaches-for-Code-node that should be expressions:
ts
// ❌ Code node
return { name: $input.first().json.name.toUpperCase() }
// ✅ Expression in Edit Fields, "name" field
{{ $json.name.toUpperCase() }}ts
// ❌ 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(', ') }}ts
// ❌ 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 skill.
n8n-expressions-official很多本应使用表达式的场景,人们却选择了Code node:
ts
// ❌ Code node
return { name: $input.first().json.name.toUpperCase() }
// ✅ Edit Fields中的表达式,“name”字段
{{ $json.name.toUpperCase() }}ts
// ❌ Code node
const items = $input.first().json.items
return { tags: items.map(item => item.tag).filter(tag => tag).join(', ') }
// ✅ 表达式
{{ $json.items.map(item => item.tag).filter(tag => tag).join(', ') }}ts
// ❌ Code node
const date = new Date($input.first().json.created_at)
return { formatted: date.toISOString().slice(0, 10) }
// ✅ 使用n8n日期扩展的表达式
{{ $json.created_at.toDateTime().format('yyyy-MM-dd') }}如需了解表达式的更多功能,请参阅技能文档。
n8n-expressions-officialWhat arrow-functions-in-Edit-Fields can do
Edit Fields中的箭头函数能做什么
Edit Fields assigns field values via expression. Inline arrow functions get you most multi-line logic without the Code node:
ts
// 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 for patterns and formatting.
references/ARROW_FUNCTIONS_IN_EDIT_FIELDS.mdEdit Fields通过表达式为字段赋值。内联箭头函数可以让你在不使用Code node的情况下实现大多数多行逻辑:
ts
// 在Edit Fields的“summary”字段中:
{{ (() => {
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)}`
})() }}这适用于“逻辑稍微复杂,无法用单行表达式实现”的场景。如需了解相关模式和格式,请参阅。
references/ARROW_FUNCTIONS_IN_EDIT_FIELDS.mdWhen the Code node IS the right answer
何时Code node才是正确选择
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.
ts
// 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 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.
$('Node Name').all()当节点需要:
- 同时读取多个上游节点的数据(例如、
$('Source A').all()、$('Source B').all())。$('Source C').first().json - 一次性对所有数据项计算统计信息或进行转换(而非逐个处理)。
- 使用中间数据结构(查找映射表、累加器、比率)执行多步骤逻辑。
这是最常见的合理场景。示例:
- 分析汇总:分组平均值、百分位数评分、跨类别归一化。
- 从一个数据源构建查找表,并与另一个数据源的行进行关联。
- 计算两个参考数据集的偏移量/比率,然后应用到第三个数据集。
ts
// 真实场景:从一个源聚合测试结果,
// 从另一个源获取模型元数据,从第三个源获取类别映射,
// 生成每个模型每个类别的平均值。
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 }))从技术上讲,表达式可以通过实现跨数据项的操作,并内联进行reduce,但对于这种包含关联、分组、嵌套聚合的复杂逻辑,最终会得到一行难以阅读且无法调试的巨型表达式。此时应该使用Code node。
$('Node Name').all()External libraries
外部库
JS Code can from a curated allowlist (lodash, etc.), but expressions can't.
requireAlways 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.
JS Code节点可以从经过筛选的允许列表(如lodash等)中库,但表达式不行。
require请先确认是否有原生节点可用。 n8n的原生节点数量比人们想象的多。为已有原生节点支持的功能使用Code node是常见错误。接下来的两个小节会介绍具体的误区。
Cryptographic operations: use the Crypto node, not Code
加密操作:使用Crypto节点,而非Code node
HMAC, signing, hashing, encryption: n8n has a native Crypto node (). Use it. It handles SHA256, MD5, HMAC, encrypt/decrypt, and random generation, all without writing JavaScript.
n8n-nodes-base.cryptots
// 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- pattern is one of the most common false positives for "this needs a Code node." It doesn't. The Crypto node covers it.
require('crypto')HMAC、签名、哈希、加密:n8n有原生的Crypto节点()。 使用它即可。它支持SHA256、MD5、HMAC、加密/解密以及随机数生成,无需编写JavaScript。
n8n-nodes-base.cryptots
// 错误(AI常犯错误):
const crypto = require('crypto')
const hash = crypto.createHash('sha256').update(buf).digest('hex').slice(0, 12)
// 正确:配置Crypto节点,操作选择'hash',类型选择'SHA256'
// 然后在下游读取$('Hash').item.json.<output>使用的Code节点模式是“这需要Code节点”的最常见误判场景之一。实际上不需要,Crypto节点已经覆盖了这些功能。
require('crypto')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 parameter. Point it at the binary slot key and it hashes the buffer directly. You don't need in user code.
binaryPropertyNamethis.helpers.getBinaryDataBuffer(...)ts
// 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 credential doesn't fit either. Rare. Justify explicitly.
httpCustomAuth不要仅仅因为需要哈希二进制数据(PDF、图片、文件缓冲区)就选择Code节点。Crypto节点有一个参数,将其指向二进制槽位的键,它就会直接对缓冲区进行哈希。用户无需在代码中使用。
binaryPropertyNamethis.helpers.getBinaryDataBuffer(...)ts
// 错误(AI处理二进制数据时的常见错误):
const crypto = require('crypto')
const buf = await this.helpers.getBinaryDataBuffer($itemIndex, 'data')
const hash = crypto.createHash('sha256').update(buf).digest('hex')
// 正确:配置Crypto节点,设置binaryPropertyName='data',哈希类型为SHA256。
// 然后$('Crypto').item.json.<output>会包含哈希值;可以链式使用Set节点进行字段整理。唯一合理使用Code节点处理加密的场景是:Crypto节点未提供的非标准签名方案(例如自定义AWS风格的签名),且凭证也不适用。这种情况很少见,需要明确说明理由。
httpCustomAuthXML / SOAP / RSS parsing: use the XML node, not Code
XML / SOAP / RSS解析:使用XML节点,而非Code node
n8n has a native XML node () 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 (), and link-finding () you'd reach for Code to do.
n8n-nodes-base.xmlArray.isArray(...) ? ... : ....find()ts
// 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.mdn8n有原生的XML节点(),支持解析和序列化操作。它可以将XML转换为JSON。转换完成后,结果就是普通的JSON,使用Edit Fields结合箭头函数表达式即可完成所有字段提取、数组归一化()和链接查找()操作,无需使用Code节点。
n8n-nodes-base.xmlArray.isArray(...) ? ... : ....find()ts
// 错误(AI常犯错误):
// XML节点已完成解析 → 再用Code节点提取几个字段:
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 } }
// 正确:使用Edit Fields结合箭头函数表达式:
// 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 }}如果字段提取逻辑确实复杂到即使使用多行箭头函数也无法用内联表达式实现,那么下一步应该是使用Edit Fields结合单个多行箭头函数,而非Code节点。请参阅。
references/ARROW_FUNCTIONS_IN_EDIT_FIELDS.mdWhat 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 first. Crypto, XML, JSON parsing, date math (Luxon), HTTP calls, file I/O, regex matching: all have native nodes or expression-level support.
search_nodes
合理场景都与范围有关:涉及整个数据集、多个数据源,或者单条数据项处理工具无法实现的有状态结构。不合理场景:Code节点处理了本可以用表达式完成的任务,或者原生节点已经支持的功能。
快速测试:
- “我能否将这个Code节点的工作描述为‘处理这一条数据项并...’?” 如果是,那就是用错了工具。
- “是否有原生节点支持这个功能?” 先通过搜索。加密、XML、JSON解析、日期计算(Luxon)、HTTP请求、文件I/O、正则匹配:这些都有原生节点或表达式级别的支持。
search_nodes
JavaScript Code node specifics
JavaScript Code节点细节
Two modes:
- Run Once for All Items (default, use this): runs once with . If you need per-item logic, just
$input.all()inside. This is the standard shape and almost always what you want.for (const item of $input.all()) - Run Once for Each Item: runs once per item with (or
$input.first()).$input.item
Common shape:
ts
// 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 objects (or ), not raw JSON.
{ json: ... }{ json: ..., binary: ... }For binary handling, error patterns, and Code-node-only bugs, see .
references/JAVASCRIPT_PATTERNS.md有两种模式:
- 对所有数据项运行一次(默认,推荐使用): 使用运行一次。如果需要逐个处理数据项,只需在内部使用
$input.all()。这是标准模式,几乎总是你的最佳选择。for (const item of $input.all()) - 对每个数据项运行一次: 对每个数据项运行一次,使用(或
$input.first())。$input.item
常见代码结构:
ts
// 对所有数据项运行一次
const items = $input.all()
const totals = items.map(item => ({
...item.json,
total: item.json.qty * item.json.price,
}))
return totals.map(json => ({ json }))返回值必须是对象的数组(或),不能是原始JSON。
{ json: ... }{ json: ..., binary: ... }如需了解二进制数据处理、错误模式以及Code节点特有的问题,请参阅。
references/JAVASCRIPT_PATTERNS.mdReference files
参考文件
| File | Read when |
|---|---|
| You're tempted to use a Code node and want to verify the simpler paths really don't work |
| The transformation is multi-line but pure data shaping |
| Code node is genuinely needed and JS is the language |
| 文件 | 阅读时机 |
|---|---|
| 你想要使用Code节点,并且想要确认更简单的方式确实不可行时 |
| 转换逻辑是多行的,但仅涉及数据整理时 |
| 确实需要使用Code节点,并且选择JavaScript作为语言时 |
Anti-patterns
反模式
| Anti-pattern | What goes wrong | Fix |
|---|---|---|
Code node doing | 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 | Loses to Luxon's clarity | Use Luxon in expression. See |
| 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 |
| 反模式 | 问题所在 | 修复方案 |
|---|---|---|
Code节点执行 | 用整个节点实现单个表达式的功能 | 替换为Edit Fields中的表达式 |
| Code节点构建HTML字符串作为邮件正文 | 邮件节点的正文字段支持表达式 | 将表达式内联到邮件节点中 |
Code节点使用 | 不如Luxon清晰 | 在表达式中使用Luxon。请参阅 |
| Set节点 + Code节点组合(Set构建输入,Code进行转换) | 用两个节点完成本可以用一个Edit Fields节点实现的功能 | 合并为一个使用箭头函数的Edit Fields节点 |
| 将凭证/令牌粘贴到Code节点文本中 | 与文本字段一样存在泄露风险 | 使用凭证,而非Code节点 |