验证 n8n 表达式语法并修复常见错误。在编写 n8n 表达式、使用 {{}} 语法、访问 $json/$node 变量、排查表达式错误、在节点之间映射数据或在工作流中引用 webhook 数据时使用。每当配置引用前一个节点数据的节点字段时都应使用此技能——表达式是 n8n 在节点之间传递数据的方式,语法错误是工作流错误最常见的来源。当被问及复杂表达式是否影响性能时也应使用。
n8n 表达式语法
在工作流中编写正确 n8n 表达式的专家指南。
表达式格式
n8n 中的所有动态内容使用双花括号:
{{expression}}
示例:
✅ {{$json.email}}
✅ {{$json.body.name}}
✅ {{$node["HTTP Request"].json.data}}
❌ $json.email (无花括号 - 视为纯文本)
❌ {$json.email} (单花括号 - 无效)
核心变量
$json - 当前节点输出
访问当前节点的数据:
{{$json.fieldName}}
{{$json['field with spaces']}}
{{$json.nested.property}}
{{$json.items[0].name}}
$node - 引用其他节点
访问任何前一个节点的数据:
{{$node["Node Name"].json.fieldName}}
{{$node["HTTP Request"].json.data}}
{{$node["Webhook"].json.body.email}}
重要提示:
- 节点名称必须用引号括起来
- 节点名称区分大小写
- 必须与工作流中的确切节点名称匹配
$now - 当前时间戳
访问当前日期/时间:
{{$now}}
{{$now.toFormat('yyyy-MM-dd')}}
{{$now.toFormat('HH:mm:ss')}}
{{$now.plus({days: 7})}}
$env - 环境变量
访问环境变量:
{{$env.API_KEY}}
{{$env.DATABASE_URL}}
警告:某些 n8n 实例启用了 N8N_BLOCK_ENV_ACCESS_IN_NODE,这会完全阻止 $env 访问。如果 $env 返回错误,请使用替代方法:
- 将值存储在凭据中
- 使用 Set 节点手动输入值
- 通过 webhook 查询参数传递值
🚨 关键:Webhook 数据结构
最常见的错误:Webhook 数据不在根层级!
Webhook 节点输出结构
{
"headers": {...},
"params": {...},
"query": {...},
"body": { // ⚠️ 用户数据在这里!
"name": "John",
"email": "john@example.com",
"message": "Hello"
}
}
正确的 Webhook 数据访问
❌ 错误:{{$json.name}}
❌ 错误:{{$json.email}}
✅ 正确:{{$json.body.name}}
✅ 正确:{{$json.body.email}}
✅ 正确:{{$json.body.message}}
原因:Webhook 节点将传入数据包装在 .body 属性下,以保留 headers、params 和 query 参数。
常见模式
访问嵌套字段
// 简单嵌套
{{$json.user.email}}
// 数组访问
{{$json.data[0].name}}
{{$json.items[0].id}}
// 带空格的括号表示法
{{$json['field name']}}
{{$json['user data']['first name']}}
引用其他节点
// 无空格节点
{{$node["Set"].json.value}}
// 带空格节点(常见!)
{{$node["HTTP Request"].json.data}}
{{$node["Respond to Webhook"].json.message}}
// Webhook 节点
{{$node["Webhook"].json.body.email}}
组合变量
// 拼接(自动)
Hello {{$json.body.name}}!
// 在 URL 中
https://api.example.com/users/{{$json.body.user_id}}
// 在对象属性中
{
"name": "={{$json.body.name}}",
"email": "={{$json.body.email}}"
}
何时不使用表达式
❌ Code 节点
Code 节点使用直接的 JavaScript 访问,而不是表达式!
// ❌ 在 Code 节点中错误
const email = '={{$json.email}}';
const name = '{{$json.body.name}}';
// ✅ 在 Code 节点中正确
const email = $json.email;
const name = $json.body.name;
// 或使用 Code 节点 API
const email = $input.item.json.email;
const allItems = $input.all();
❌ Webhook 路径
// ❌ 错误
path: "{{$json.user_id}}/webhook"
// ✅ 正确
path: "user-webhook" // 仅静态路径
❌ 凭据字段
// ❌ 错误
apiKey: "={{$env.API_KEY}}"
// ✅ 正确
使用 n8n 凭据系统,而不是表达式
转换把关者
在添加任何节点或编写任何代码来转换数据之前,请按此顺序检查,并在第一个符合条件的地方停止:
-
表达式(
{{ ... }})在消费字段中。属性访问、方法链(.map().filter().join())、三元运算符、字符串构建、Luxon 日期数学——如果是“取 A,产生 B”且没有中间变量,那就是表达式。这涵盖了大多数“只需转换这个”的情况。 -
箭头函数 IIFE 在 Edit Fields 字段内。 当逻辑需要中间变量、分支或注释但仍对单个项目操作时,将其包装在字段值中的立即调用的箭头函数内:
={{ (() => { const items = $json.line_items; const subtotal = items.reduce((sum, it) => sum + it.price * it.qty, 0); const tax = subtotal * 0.08; return (subtotal + tax).toFixed(2); })() }}外部的
(...)将函数括起来;尾部的()调用它。去掉任何一个,n8n 都会拒绝运行。内部你可以使用完整的表达式作用域($json、$('Node')、$now、Luxon)以及const/let、if/switch、try/catch和正则表达式。不支持require和await。 -
Code 节点——最后的手段。 仅当需要对整个数据集进行多项目聚合(
$input.all())、使用允许列表中的库或异步工作时使用。
为什么顺序很重要。 这不是风格问题——而是可读性和性能问题。Code 节点在沙箱化 VM 中运行,每次调用都有设置和值编组的开销——在逻辑运行之前,冷启动成本可能达到 500–1000 毫秒。(在热运行、高项目数运行时它会摊销,因此将此视为常见情况成本,而非通用常数。)相同的逻辑在表达式或 Edit Fields IIFE 中运行,在进程内只需几毫秒,并完全跳过沙箱。对于纯单项目整形,这是一个巨大的差距,且功能上没有区别,并且在热路径(如每个请求的 webhook)上会累积。表达式也保留在使用它的字段中,而不是隐藏在上游节点中,别人必须打开才能理解。只有当输入或作用域确实需要时,才超越一个阶段。
Set 节点反模式和分支收敛
删除只服务一个消费者的 Set 节点
一个 Set / Edit Fields 节点,其唯一工作是提取一个值并将其传递给一个下游节点,那是多余的。将其表达式内联到消费者处。
❌ Webhook → Set { customer_id: {{ $json.body.customer_id }} } → Postgres: WHERE id = {{ $json.customer_id }}
✅ Webhook → Postgres: WHERE id = {{ $('Webhook').item.json.body.customer_id }}
Set 节点增加了一个跳转、更多的画布杂乱和重构风险,而消费者本身完全可以做到。要使用 n8n_update_partial_workflow 干净地删除它:重新连接(从 Set 的源和目标 removeConnection,直接从源到消费者 addConnection),patchNodeField 消费者的表达式以按节点名称引用原始源,然后 removeNode 该 Set。
快速测试: 计算有多少下游节点引用 Set 产生的每个字段。
- 0 或 1 → 删除,内联到消费者。
- 2+ → 它可能值得保留。
合理的例外——在以下情况下保留 Set:
- 2+ 个消费者读取相同的派生值,且派生过程不简单(名称有助于可读性,且只需计算一次)。
- 它是子工作流的最终 Return 节点,塑造输出契约。这里的“单个消费者”是每个调用者,因此 Set 就是 API 边界——并且使用
Include Other Fields: false它白名单输出形状,这样内部临时字段不会泄露。 - 你正在重命名或白名单字段,并且希望在一个地方可见,而不是分散在消费者表达式中。
分支收敛:使用 NoOp 锚定
当分支收敛时(在 IF/Switch/Merge 之后),$json 变成“最后触发的分支”——非确定性,并且是错误数据的静默来源。在收敛点插入一个 NoOp 节点,描述性地命名(Combine Inputs),并让下游节点按名称引用它:
Branch A ──┐
├─→ [NoOp: Combine Inputs] ──→ downstream uses $('Combine Inputs').item.json.x
Branch B ──┘
NoOp 能经受重构:稍后在其与消费者之间插入转换不会破坏 $('Combine Inputs') 引用。(如果分支产生不同的形状,请使用 Set 节点而不是 NoOp 将两者规范化为一个形状——请参阅上面的例外。)
更广泛地说,在分支流程中,优先使用 $('Node').item.json.x 而不是深层的 $json.x。 $json 在插入中间节点或节点清除项目上下文(Aggregate、Run for All 模式的 Code、分支合并)时立即失效;失败是静默的,下游得到错误数据且没有错误。节点名称引用是明确的,无论源和消费者之间有什么。
验证规则
1. 始终使用 {{}}
表达式必须用双花括号括起来。
❌ $json.field
✅ {{$json.field}}
2. 对空格和特殊字符使用引号
带有空格、变音符号或特殊字符的字段或节点名称需要括号表示法:
❌ {{$json.field name}}
✅ {{$json['field name']}}
❌ {{$node.HTTP Request.json}}
✅ {{$node["HTTP Request"].json}}
// 对于包含特殊字符的键,括号表示法是必需的
✅ {{$json['Gross Price w/o shipment']}}
✅ {{$json['Cena brutto zł']}}
3. 匹配确切的节点名称
节点引用区分大小写:
❌ {{$node["http request"].json}} // 小写
❌ {{$node["Http Request"].json}} // 错误大小写
✅ {{$node["HTTP Request"].json}} // 精确匹配
4. 不要嵌套 {{}}
不要双重包裹表达式:
❌ {{{$json.field}}}
✅ {{$json.field}}
常见错误
完整的错误目录及修复方法,请参见 COMMON_MISTAKES.md
快速修复
| 错误 | 修复 |
|---|---|
$json.field |
{{$json.field}} |
{{$json.field name}} |
{{$json['field name']}} |
{{$node.HTTP Request}} |
{{$node["HTTP Request"]}} |
{{{$json.field}}} |
{{$json.field}} |
{{$json.name}} (webhook) |
{{$json.body.name}} |
'={{$json.email}}' (Code 节点) |
$json.email |
工作示例
真实工作流示例,请参见 EXAMPLES.md
示例 1:Webhook 到 Slack
Webhook 接收:
{
"body": {
"name": "John Doe",
"email": "john@example.com",
"message": "Hello!"
}
}
在 Slack 节点文本字段中:
New form submission!
Name: {{$json.body.name}}
Email: {{$json.body.email}}
Message: {{$json.body.message}}
示例 2:HTTP Request 到 Email
HTTP Request 返回:
{
"data": {
"items": [
{"name": "Product 1", "price": 29.99}
]
}
}
在 Email 节点中(引用 HTTP Request):
Product: {{$node["HTTP Request"].json.data.items[0].name}}
Price: ${{$node["HTTP Request"].json.data.items[0].price}}
示例 3:格式化时间戳
// 当前日期
{{$now.toFormat('yyyy-MM-dd')}}
// 结果:2025-10-20
// 时间
{{$now.toFormat('HH:mm:ss')}}
// 结果:14:30:45
// 完整日期时间
{{$now.toFormat('yyyy-MM-dd HH:mm')}}
// 结果:2025-10-20 14:30
数据类型处理
数组
// 第一个项目
{{$json.users[0].email}}
// 数组长度
{{$json.users.length}}
// 最后一个项目
{{$json.users[$json.users.length - 1].name}}
对象
// 点表示法(无空格)
{{$json.user.email}}
// 括号表示法(有空格或动态)
{{$json['user data'].email}}
字符串
// 拼接(自动)
Hello {{$json.name}}!
// 字符串方法
{{$json.email.toLowerCase()}}
{{$json.name.toUpperCase()}}
数字
// 直接使用
{{$json.price}}
// 数学运算
{{$json.price * 1.1}} // 加 10%
{{$json.quantity + 5}}
高级模式
条件内容
// 三元运算符
{{$json.status === 'active' ? 'Active User' : 'Inactive User'}}
// 默认值
{{$json.email || 'no-email@example.com'}}
日期操作
// 加天数
{{$now.plus({days: 7}).toFormat('yyyy-MM-dd')}}
// 减小时
{{$now.minus({hours: 24}).toISO()}}
// 设置特定日期
{{DateTime.fromISO('2025-12-25').toFormat('MMMM dd, yyyy')}}
字符串操作
// 子字符串
{{$json.email.substring(0, 5)}}
// 替换
{{$json.message.replace('old', 'new')}}
// 分割和连接
{{$json.tags.split(',').join(', ')}}
性能:表达式复杂性(几乎)免费
一个常见的担忧是复杂的 {{ }} 很慢。事实并非如此——成本在于 n8n 评估表达式的次数,而不是每个表达式有多复杂。
在 n8n 2.x 实例上测量,一个复杂的表达式(sqrt、split、reduce、算术)每个项目的成本与一个简单的 {{ $json.x > 50 }} 相同——大约 ~0.2 毫秒/项目,因为约 90% 的成本是 n8n 构建每个项目的评估上下文,而不是运行你的表达式。
这在实践中意味着:
- 不要为了“速度”而将一个有效的表达式拆分成一串节点。 每个额外的节点会重新评估每个项目并重新复制所有项目;一个节点带一个更丰富的表达式胜过三个节点带简单的表达式。
- 一个表达式(~0.2 毫秒/项目)比“每个项目运行一次”模式的 Code 节点(~0.6 毫秒/项目)便宜约 3 倍——但“所有项目运行一次”模式的 Code 节点更便宜(~0.02 毫秒/项目),因为它只跨越每个项目边界一次而不是 N 次。
- 这仅在数千个项目时才有影响;低于此值,成本低于 100 毫秒。n8n Code JavaScript 技能有完整的每个项目边界模型。
调试表达式
在表达式编辑器中测试
- 点击包含表达式的字段
- 打开表达式编辑器(点击“fx”图标)
- 查看结果的实时预览
- 检查以红色高亮显示的错误
常见错误消息
"Cannot read property 'X' of undefined"
→ 父对象不存在
→ 检查你的数据路径
"X is not a function"
→ 尝试在非函数上调用方法
→ 检查变量类型
表达式显示为纯文本
→ 缺少 {{ }}
→ 添加花括号
表达式辅助方法
可用方法
字符串:
.toLowerCase()、.toUpperCase().trim()、.replace()、.substring().split()、.includes()
数组:
.length、.map()、.filter().find()、.join()、.slice()
日期时间(Luxon):
.toFormat()、.toISO()、.toLocal().plus()、.minus()、.set()
数字:
.toFixed()、.toString()- 数学运算:
+、-、*、/、%
最佳实践
✅ 应该做
- 始终对动态内容使用 {{ }}
- 对包含空格的字段名称使用括号表示法
- 从
.body引用 webhook 数据 - 使用 $node 获取其他节点的数据
- 在表达式编辑器中测试表达式
❌ 不应该做
- 不要在 Code 节点中使用表达式
- 不要忘记对包含空格的节点名称使用引号
- 不要用额外的 {{ }} 双重包裹
- 不要假设 webhook 数据在根层级(它在 .body 下!)
- 不要在 webhook 路径或凭据中使用表达式
相关技能
- n8n MCP Tools Expert:学习如何使用 MCP 工具验证表达式
- n8n Workflow Patterns:在真实工作流示例中查看表达式
- n8n Node Configuration:了解何时需要表达式
总结
基本规则:
- 将表达式包裹在 {{ }} 中
- Webhook 数据在
.body下 - Code 节点中不要使用 {{ }}
- 对包含空格的节点名称使用引号
- 节点名称区分大小写
最常见的错误:
- 缺少 {{ }} → 添加花括号
- webhook 中
{{$json.name}}→ 使用{{$json.body.name}} - Code 中
{{$json.email}}→ 使用$json.email {{$node.HTTP Request}}→ 使用{{$node["HTTP Request"]}}
更多详情,请参见:
- COMMON_MISTAKES.md - 完整错误目录
- EXAMPLES.md - 真实工作流示例
需要帮助? 参考 n8n 表达式文档或使用 n8n-mcp 验证工具检查你的表达式。






