mcpField
N
n8n-expression-syntax

n8n-expression-syntax

github.com/czlonkowski开发23.2k85 次安装今天更新
代码仓库

关于 n8n-expression-syntax

为 n8n 工作流编写者提供表达式语法指南,涵盖变量访问、数据转换和常见陷阱,并说明何时应改用 Edit Fields 或 Code 节点。

主要功能

  1. 01表达式格式与变量访问
  2. 02引用当前及其他节点数据
  3. 03处理 Webhook、数组和对象
  4. 04单项转换与节点选择建议
  5. 05常见错误及校验规则

适用场景

  1. 01在字段中插入动态工作流数据
  2. 02格式化日期、字符串和数组
  3. 03排查 Webhook 数据路径错误

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/czlonkowski/n8n-mcp --skill n8n-expression-syntax

安装后在 Claude Code 中输入/n8n-expression-syntax确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

COMMON_MISTAKES.md
EXAMPLES.md
README.md

详细介绍

n8n 表达式语法

在工作流中编写正确 n8n 表达式的专家指南。


表达式格式

n8n 中的所有动态内容都使用双大括号:

{{expression}}

示例:

✅ {{$json.email}}
✅ {{$json.body.name}}
✅ {{$node["HTTP Request"].json.data}}
❌ $json.email  (no braces - treated as literal text)
❌ {$json.email}  (single braces - invalid)

核心变量

$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": {           // ⚠️ USER DATA IS HERE!
    "name": "John",
    "email": "[email protected]",
    "message": "Hello"
  }
}
正确访问 Webhook 数据
❌ WRONG: {{$json.name}}
❌ WRONG: {{$json.email}}

✅ CORRECT: {{$json.body.name}}
✅ CORRECT: {{$json.body.email}}
✅ CORRECT: {{$json.body.message}}

原因:Webhook 节点会将传入数据包装在 .body 属性下,以保留 headers、params 和查询参数。


常见模式

访问嵌套字段
// Simple nesting
{{$json.user.email}}

// Array access
{{$json.data[0].name}}
{{$json.items[0].id}}

// Bracket notation for spaces
{{$json['field name']}}
{{$json['user data']['first name']}}
引用其他节点
// Node without spaces
{{$node["Set"].json.value}}

// Node with spaces (common!)
{{$node["HTTP Request"].json.data}}
{{$node["Respond to Webhook"].json.message}}

// Webhook node
{{$node["Webhook"].json.body.email}}
组合变量
// Concatenation (automatic)
Hello {{$json.body.name}}!

// In URLs
https://api.example.com/users/{{$json.body.user_id}}

// In object properties
{
  "name": "={{$json.body.name}}",
  "email": "={{$json.body.email}}"
}

何时不使用表达式

❌ Code 节点

Code 节点使用直接的 JavaScript 访问方式,而不是表达式!

// ❌ WRONG in Code node
const email = '={{$json.email}}';
const name = '{{$json.body.name}}';

// ✅ CORRECT in Code node
const email = $json.email;
const name = $json.body.name;

// Or using Code node API
const email = $input.item.json.email;
const allItems = $input.all();
❌ Webhook 路径
// ❌ WRONG
path: "{{$json.user_id}}/webhook"

// ✅ CORRECT
path: "user-webhook"  // Static paths only
❌ 凭据字段
// ❌ WRONG
apiKey: "={{$env.API_KEY}}"

// ✅ CORRECT
Use n8n credential system, not expressions

转换把关指南

在添加任何节点或编写任何代码来转换数据之前,请按以下顺序判断,并在遇到第一个适用选项时停止:

  1. 在使用该值的字段中使用表达式({{ ... }})。属性访问、方法链(.map().filter().join())、三元表达式、字符串构建、Luxon 日期运算——如果只是“取 A,生成 B”,且不需要中间变量,那就是表达式。这适用于大多数“只需转换一下”的情况。

  2. 在 Edit Fields 字段中使用箭头函数 IIFE。当逻辑需要中间变量、分支或注释,但仍只处理单个条目时,可直接在字段值中用立即调用的箭头函数包装:

    ={{ (() => {
        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。

  3. Code 节点——最后手段。 只有在需要跨整个数据集进行多条目聚合($input.all())、使用允许列表中的库或执行异步操作时,才使用它。

为何顺序很重要。 这不只是风格问题,还关乎可读性和性能。Code 节点在沙盒 VM 中运行,每次调用都需要进行初始化和数值封送;在逻辑开始运行前,这项冷启动开销可能达到 500–1000ms。(在高条目数的热运行中,这项开销可以分摊,因此应将其视为常见情况下的开销,而不是适用于所有情况的固定值。)相同逻辑若放在表达式或 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。 一旦插入中间节点,或某个节点清除了条目上下文(Aggregate、使用 Run for All 的 Code、分支合并),$json 就会失效;这种故障不会显现出来,下游会在没有报错的情况下拿到错误数据。无论源节点与消费节点之间有哪些节点,按节点名称引用都不会产生歧义。


验证规则

1. 始终使用 {{}}

表达式必须用双花括号括起来。

❌ $json.field
✅ {{$json.field}}
2. 空格和特殊字符需使用引号

名称中包含空格、变音符号或特殊字符的字段或节点,必须使用方括号表示法:

❌ {{$json.field name}}
✅ {{$json['field name']}}

❌ {{$node.HTTP Request.json}}
✅ {{$node["HTTP Request"].json}}

// Bracket notation is mandatory for keys with special characters
✅ {{$json['Gross Price w/o shipment']}}
✅ {{$json['Cena brutto zł']}}
3. 节点名称必须完全匹配

节点引用区分大小写:

❌ {{$node["http request"].json}}  // lowercase
❌ {{$node["Http Request"].json}}  // wrong case
✅ {{$node["HTTP Request"].json}}  // exact match
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": "[email protected]",
    "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:格式化时间戳
// Current date
{{$now.toFormat('yyyy-MM-dd')}}
// Result: 2025-10-20

// Time
{{$now.toFormat('HH:mm:ss')}}
// Result: 14:30:45

// Full datetime
{{$now.toFormat('yyyy-MM-dd HH:mm')}}
// Result: 2025-10-20 14:30

数据类型处理

数组
// First item
{{$json.users[0].email}}

// Array length
{{$json.users.length}}

// Last item
{{$json.users[$json.users.length - 1].name}}
对象
// Dot notation (no spaces)
{{$json.user.email}}

// Bracket notation (with spaces or dynamic)
{{$json['user data'].email}}
字符串
// Concatenation (automatic)
Hello {{$json.name}}!

// String methods
{{$json.email.toLowerCase()}}
{{$json.name.toUpperCase()}}
数字
// Direct use
{{$json.price}}

// Math operations
{{$json.price * 1.1}}  // Add 10%
{{$json.quantity + 5}}

高级模式

条件内容
// Ternary operator
{{$json.status === 'active' ? 'Active User' : 'Inactive User'}}

// Default values
{{$json.email || '[email protected]'}}
日期处理
// Add days
{{$now.plus({days: 7}).toFormat('yyyy-MM-dd')}}

// Subtract hours
{{$now.minus({hours: 24}).toISO()}}

// Set specific date
{{DateTime.fromISO('2025-12-25').toFormat('MMMM dd, yyyy')}}
字符串处理
// Substring
{{$json.email.substring(0, 5)}}

// Replace
{{$json.message.replace('old', 'new')}}

// Split and join
{{$json.tags.split(',').join(', ')}}

性能:表达式复杂度几乎没有影响

常见的担忧是复杂的 {{ }} 会很慢。其实不会——真正影响性能的是 n8n 计算表达式的次数,而不是每个表达式有多复杂。

在 n8n 2.x 实例上测得,复杂表达式(sqrt、split、reduce、算术运算)与简单的 {{ $json.x > 50 }} 在每个项目上的耗时相同——约 ~0.2 ms/item,因为其中约 90% 的时间花在 n8n 构建每个项目的求值上下文上,而不是运行表达式。

实际应用中,这意味着:

  • 不要为了“提速”而把可用的表达式拆成一串节点。 每增加一个节点,就会对每个项目重新求值并重新复制所有项目;一个包含较丰富表达式的节点,胜过三个表达式简单的节点。
  • 对于相同的逐项检查,表达式(~0.2 ms/item)比处于“Run Once for Each Item”模式的 Code 节点便宜约 3 倍(~0.6 ms/item)——但处于“Run Once for All Items”模式的 Code 节点更省时(~0.02 ms/item),因为它只跨越逐项处理边界一次,而不是 N 次。
  • 只有在数千个项目时,这才会造成明显影响;数量更少时,耗时低于 100 ms。n8n Code JavaScript 技能详细介绍了逐项处理边界模型。

调试表达式

在表达式编辑器中测试
  1. 点击包含表达式的字段
  2. 打开表达式编辑器(点击“fx”图标)
  3. 查看结果的实时预览
  4. 检查以红色突出显示的错误
常见错误消息

“Cannot read property 'X' of undefined” → 父对象不存在 → 检查数据路径

“X is not a function” → 尝试在非函数上调用方法 → 检查变量类型

表达式显示为纯文本 → 缺少 {{ }} → 添加花括号


表达式辅助方法

可用方法

字符串:

  • .toLowerCase()、.toUpperCase()
  • .trim()、.replace()、.substring()
  • .split()、.includes()

数组:

  • .length、.map()、.filter()
  • .find()、.join()、.slice()

DateTime(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:了解何时需要表达式

总结

基本规则:

  1. 用 {{ }} 包裹表达式
  2. Webhook 数据位于 .body 下
  3. Code 节点中不要使用 {{ }}
  4. 给包含空格的节点名称加引号
  5. 节点名称区分大小写

最常见的错误:

  • 缺少 {{ }} → 添加花括号
  • 在 webhook 中使用 {{$json.name}} → 改用 {{$json.body.name}}
  • 在 Code 中使用 {{$json.email}} → 改用 $json.email
  • 使用 {{$node.HTTP Request}} → 改用 {{$node["HTTP Request"]}}

更多详情,请参阅:


需要帮助? 请参考 n8n 表达式文档,或使用 n8n-mcp 验证工具检查表达式。

评论

支持 Markdown