n8n-code-nodes
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节点处理了本可以用表达式实现的逻辑,工作流的可读性、调试难度和可维护性都会下降。此外,两者之间还存在明显的性能差距: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节点是最后选择。决策优先级:表达式()→ Edit Fields中的箭头函数 → Code节点。前两种方式可以覆盖大多数"转换数据"的任务。只有在处理多源聚合、外部库调用,以及下文所述的特定模式时,才应该使用Code节点。
{{...}} -
默认使用JavaScript。除非用户明确要求使用Python(例如"这里用Python"、"我们是Python技术栈"、粘贴了Python代码),否则一律使用JavaScript。n8n的其他所有部分(表达式、Edit Fields)都使用JavaScript,并且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.md需要自定义逻辑?
├── 是否是对一两个字段的转换?
│ └── 表达式:{{ $json.foo.toUpperCase() }} 或 {{ $json.items.map(item => item.name).join(', ') }}
│ 大多数"只需转换这个数据"的场景都属于此类。
│
├── 是否是多行代码,但仅用于数据格式化(map、filter、reduce、条件判断)?
│ └── 使用带箭头函数表达式的Edit Fields。详见 references/ARROW_FUNCTIONS_IN_EDIT_FIELDS.md
│
├── 是否需要完整语句、多数据源或外部库?
│ ├── 你确定前两种方式都不适用吗?
│ │ └── 重新阅读上文。使用Code节点的门槛很高。
│ └── 是的,确实需要
│ └── Code节点。详见 references/JAVASCRIPT_PATTERNS.md
│
└── 是否实际上是两个独立的转换步骤拼接而成?
└── 使用两个节点(Edit Fields → Edit Fields,或Edit Fields → IF)。组合性优于单个庞大的Code块。如需包含各分支示例的完整决策逻辑,请查看 。
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常见的本应使用表达式却选择Code节点的场景:
ts
// ❌ Code节点
return { name: $input.first().json.name.toUpperCase() }
// ✅ Edit Fields中的表达式,"name"字段
{{ $json.name.toUpperCase() }}ts
// ❌ Code节点
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节点
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-expressionsWhat 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节点即可实现大多数多行逻辑:
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节点才是正确选择
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 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节点是常见错误。接下来的两个小节会介绍具体的陷阱。
Cryptographic operations: use the Crypto node, not Code
加密操作:使用Crypto节点,而非Code节点
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节点
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字符串作为邮件正文 | Email节点的正文字段支持表达式 | 将表达式内联到Email节点中 |
Code节点使用 | 不如Luxon清晰 | 在表达式中使用Luxon。详见 |
| Set节点 + Code节点组合(Set构建输入,Code进行转换) | 用两个节点实现单个Edit Fields就能完成的功能 | 合并为一个带箭头函数的Edit Fields |
| 在Code节点文本中粘贴凭证/令牌 | 与文本字段一样存在泄露风险 | 使用凭证,而非Code节点 |