n8n-expression-syntax

n8n-expression-syntax

熱門

驗證 n8n 表達式語法並修正常見錯誤。在撰寫 n8n 表達式、使用 {{}} 語法、存取 $json/$node 變數、排解表達式錯誤、在節點之間對應資料,或在工作流程中引用 webhook 資料時使用。每當設定節點欄位需要引用來自前一個節點的資料時,請使用此技能——表達式是 n8n 在節點之間傳遞資料的方式,而語法錯誤是工作流程錯誤最常見的來源。當被問及複雜表達式是否會影響效能時,也請使用此技能。

5825星標
981分支
更新於 2026/7/16
SKILL.md
唯讀
名稱
n8n-expression-syntax
描述

驗證 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 屬性下,以保留標頭、參數和查詢參數。


常見模式

存取巢狀欄位

// 簡單巢狀
{{$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 憑證系統,不要使用表達式

轉換守門員

在新增任何節點(或撰寫任何程式碼)來轉換資料之前,請依序檢查以下項目,並停在第一個符合的項目:

  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/letif/switchtry/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),並讓下游節點按名稱引用它:

分支 A ──┐
           ├─→ [NoOp: Combine Inputs] ──→ 下游使用 $('Combine Inputs').item.json.x
分支 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 節點的文字欄位中

新表單提交!

姓名:{{$json.body.name}}
Email:{{$json.body.email}}
訊息:{{$json.body.message}}

範例 2:HTTP Request 到 Email

HTTP Request 回傳

{
  "data": {
    "items": [
      {"name": "Product 1", "price": 29.99}
    ]
  }
}

在 Email 節點中(引用 HTTP Request):

產品:{{$node["HTTP Request"].json.data.items[0].name}}
價格:${{$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 執行個體上測量,一個複雜的表達式(sqrtsplitreduce、算術)每個項目的成本與簡單的 {{ $json.x > 50 }} 相同——大約 ~0.2 ms/項目,因為約 90% 的成本來自 n8n 建立每個項目的評估上下文,而不是執行你的表達式。

這在實務上的意義:

  • 不要為了「速度」而將一個可運作的表達式拆成一串節點。 每個額外節點會重新評估每個項目並重新複製所有項目;一個節點搭配一個較豐富的表達式,勝過三個節點搭配簡單的表達式。
  • 一個表達式(~0.2 ms/項目)比「每個項目執行一次」模式的 Code 節點(~0.6 ms/項目)便宜約 3 倍——但「所有項目執行一次」模式的 Code 節點更便宜(~0.02 ms/項目),因為它只跨越項目邊界一次,而不是 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()

日期時間(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 驗證工具檢查你的表達式。