驗證 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 憑證系統,不要使用表達式
轉換守門員
在新增任何節點(或撰寫任何程式碼)來轉換資料之前,請依序檢查以下項目,並停在第一個符合的項目:
-
表達式(
{{ ... }})在消費欄位中。屬性存取、方法鏈(.map().filter().join())、三元運算子、字串建構、Luxon 日期運算——如果是「取得 A,產生 B」且沒有中間變數,就是表達式。這涵蓋了大多數「只是轉換這個」的情況。 -
在 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。 -
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 執行個體上測量,一個複雜的表達式(sqrt、split、reduce、算術)每個項目的成本與簡單的 {{ $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 技能有完整的每個項目邊界模型。
除錯表達式
在表達式編輯器中測試
- 點擊包含表達式的欄位
- 開啟表達式編輯器(點擊「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 驗證工具檢查你的表達式。




