第 20 章

Code node:在 workflow 裡寫幾行 JavaScript

現成節點做得到 90% 的事,剩下 10% 的怪需求——複雜資料轉換、多來源合併邏輯、動態產生內容——n8n 給你一個逃生口:Code node。這章教你在 workflow 裡塞幾行 JavaScript 補洞。新手警告:這章有 JS,看不懂完全沒關係,直接 copy 我給的 pattern 改參數就好;真的跟不上,直接跳到第 21 章 AI Agent 也完全 OK,多數業務場景根本用不到 Code node。

為什麼要 Code node

前面 19 章講完,你會發現八成情境用 core node 加 SaaS 整合節點 就搞得定:拉資料、過濾、發訊息、寫資料庫。Set 節點幫你整形,Expression 幫你動態填空,Merge/Split 幫你重組。

但總有那 10% 現成節點做不到的怪需求:

  • 從 CRM 拉出來的訂單,要按「客戶等級 × 商品類別」兩個維度重新分組——Set 節點做不到,因為那要跑 loop。
  • API 回來的資料塞在 { result: { data: { items: [...] } } } 巢狀三層,你只要 items 陣列——Expression 一行寫得出來,但邏輯稍微多一點就變一坨。
  • Shopify 給你 20 筆訂單,你要算「金額前三大訂單佔總營收幾 %」——這需要 sort、slice、reduce 一起用。
  • 要把 100 筆資料轉成 Slack Block Kit 的 JSON 結構——一堆巢狀 { type, elements: [...] },用 Set 節點會瘋掉。

這時候就是 Code node 上場。它給你一個空白 JavaScript 編輯器,讓你寫幾行 code 把 items 進來、items 出去。不需要你會寫應用程式——大多場景抄下面四個 pattern 改參數就夠用。

AI HA 管家 18 節點 workflow 含 Code 節點
圖 20-1實戰範例:AI HA 管家 18 節點 workflow,中間用 Code 節點做內建節點做不到的資料整形與邏輯處理。
新手警告:這章跟前面 19 章不一樣,會出現真的 JavaScript code。看不懂完全 OK——實務上業務/行銷同仁很少需要自己寫 Code node,能認出「這裡有一段程式碼在做 X」就夠了。真的跟不上,直接翻第 21 章 AI Agent 跟第 17 章錯誤處理,完全不影響。

先問:能用 Set 就別用 Code

看到 Code node 很酷不代表要濫用。能用 Set/IF/Switch 解決的,就別開 Code node——因為 Code node 用 JavaScript,日後別人(或未來的你)維護時要重讀 code、還要懂 JS,門檻比視覺化節點高很多。

你要做的事優先用什麼時候升級到 Code
把 firstName 跟 lastName 合成 fullName Set / Edit Fields 節點——用 Expression {{ $json.firstName + ' ' + $json.lastName }} 一行搞定,視覺化最快。 不需要升級。
金額 > 1000 才走 A 路徑,否則走 B IF 節點——兩條分支明確畫在畫布上,一眼看懂邏輯。 不需要升級。
按「訂單狀態」分成三個路徑 Switch 節點——多路分支的原生節點。 不需要升級。
把 items 過濾成「金額 > 1000」的那幾筆 Filter 節點(新版有這個),或 IF 節點單邊接。 條件牽扯多個欄位交叉判斷、或要動態算條件,升 Code。
對每筆 item 做「加欄位、算新值」 Set 節點 + Expression——大多能搞定。 要跑 loop、或條件轉換超過 3-4 個 case 時升 Code。
把所有 items 加總、算平均、group by Aggregate 節點(有內建),或 Summarize 節點。 要算「前 N 名」、多維度分組、比較複雜的統計時升 Code。
把巢狀 API response 拆解、重新組合 JSON 結構 Set 節點 加 Expression 通常撐得住。 三層以上巢狀、或需要跑 map/reduce 才升 Code。
提示:判斷公式——「這個邏輯用 3 個 Set 節點做得到嗎?」做得到就用 Set,做不到再開 Code。Set 節點雖然多,但下次別人打開 workflow 一眼看得懂,這比省 2 個節點但塞一堆 code 划算太多。

Code node 有兩種語言可以選

打開 Code node,右上角 Language 有兩個選項:JavaScript 跟 Python。實務上 99% 情況選 JavaScript,理由如下。

語言怎麼跑優點缺點建議
JavaScript(預設) 透過 n8n 內建 sandbox 執行;自架時也可切到task runners(獨立子行程,較新的官方推薦架構)。 官方主打;helper API 完整;範例最多;跑得快。 預設 sandbox 不能 require 外部 npm 套件(要開 NODE_FUNCTION_ALLOW_EXTERNAL)。 預設選這個。沒有特別理由就用 JS。
Python n8n 2 起改用 native Python task runner(1.111.0 引進、n8n 2 stable);早期用的 Pyodide(WebAssembly)已標為 legacy、n8n 2 不再支援。 有 Python 現成邏輯要搬;能 pip install 標準庫 + 第三方套件(自架設好 task runner 才行)。 native Python 只吃括號存取(_input.item['json'],不能寫 .json);Cloud 版是否啟用要看方案;社群範例仍以 JS 為主。 只在有現成 Python 邏輯要移植時才選。純粹「我比較會 Python」不是好理由——因為社群跟範例都在 JS 那邊。

本章接下來所有範例都用 JavaScript。Python 語法差在:變數改成底線開頭(_input、_json),而且 native Python runner 只吃括號存取(_input.item['json']['amount'])——舊 Pyodide 允許的 _input.item.json.amount 點語法已不能用。需要時參考官方文件 docs.n8n.io/build/code-in-n8n/using-the-code-node/。

兩種執行模式:一次全部 vs 逐筆

打開 Code node,右側 Parameters 面板最上方(在 Language 選項旁)有一個 Mode 下拉——官方文件的原話是「Choose a mode」。兩個選項差很大,選錯會造成資料重複或漏掉。

Mode行為什麼時候用常見場景
Run Once for All Items(預設) 整份 items 陣列一次進來,你的 code 跑一次,吐出新的 items 陣列。 要看到全部資料才能做決定:聚合、排序、group、算前 N 名。 「算總營收」「取前 5 大客戶」「按類別 group」「合併兩個上游」。
Run Once for Each Item 對每個 item 各跑一次你的 code,就像 for loop 幫你跑好。 只需看單筆資料就能決定,且轉換規則對每筆都一樣。 「幫每筆訂單加 tax」「每筆訊息重新格式化」「單筆呼叫 API 補資料」。
注意:Mode 選不同,code 寫法也不一樣:
  • All Items:用 $input.all() 拿全部;return 要回傳陣列 [{ json: {} }, ...]。
  • Each Item:用 $input.item.json 拿當前那筆;return 回一個 { json: {} }。
搞混會噴 Items to return were not valid 或吐出空 output。本章範例默認 All Items 模式,除非特別註明。

Pattern 1:過濾(filter)

從一堆 items 挑出符合條件的那幾筆。All Items 模式。

// 只留下金額大於 1000 的訂單
return $input.all().filter(item => item.json.amount > 1000);

白話拆解:

  • $input.all():拿到全部進來的 items(陣列)。
  • .filter(item => ...):JavaScript 陣列的內建方法,對每筆跑那個判斷式,只留下判斷是 true 的。
  • item.json.amount > 1000:判斷式本體。把這裡改成你的條件就好。
  • return ...:吐回去給下游。

Copy 這段、把 amount > 1000 換成你要的條件(item.json.status === 'paid'、item.json.tags.includes('VIP') 都可以),就完成了。多條件用 &&(且)跟 ||(或)串:

// 金額大於 1000 且客戶等級是 gold
return $input.all().filter(item =>
  item.json.amount > 1000 && item.json.tier === 'gold'
);
提示:Filter node 也能做這件事、還不用寫 code。但當條件牽扯多個欄位交叉判斷、或條件本身要動態算,Code node 更清楚。

Pattern 2:映射(map / 轉換每筆)

對每筆 item 做轉換:加欄位、改欄位、算新值。All Items 模式。

// 把 firstName + lastName 合成 fullName,只保留 fullName 跟 age
return $input.all().map(item => ({
  json: {
    fullName: item.json.firstName + ' ' + item.json.lastName,
    age: item.json.age
  }
}));

白話拆解:

  • .map(item => ({...})):對每筆 item 跑那個函式,回傳「新的 item」。
  • { json: {...} }:n8n item 的規定格式——每個 item 都要包一層 json。這個很容易忘,忘了就會噴 Items to return were not valid。
  • fullName: ...、age: ...:你想輸出的欄位。想加什麼欄位就寫什麼。

如果你只是要在原本 item 上「加一個欄位」而不是重建,用 spread 語法 ...item.json 保留原本所有欄位再加新的:

// 保留原本所有欄位,再多加 taxAmount 跟 total
return $input.all().map(item => ({
  json: {
    ...item.json,                      // 展開原本欄位
    taxAmount: item.json.amount * 0.05,
    total: item.json.amount * 1.05
  }
}));

...item.json 三個點是 JavaScript 的 spread——「把 item.json 所有欄位攤平在這裡」。這種寫法可以避免手動列所有欄位。

Pattern 3:聚合(aggregate)

把一堆 items 縮成一筆總計。All Items 模式。

// 把所有訂單金額加總,算出總營收跟筆數
const total = $input.all().reduce((sum, item) => sum + item.json.amount, 0);
const count = $input.all().length;

return [{
  json: {
    total: total,
    count: count,
    average: total / count
  }
}];

白話拆解:

  • .reduce((sum, item) => sum + item.json.amount, 0):JavaScript 的 reduce,用來「把陣列一路加起來」。sum 從 0 開始(第二個參數),每次把 item.json.amount 加上去,最後回傳總和。
  • $input.all().length:陣列長度就是筆數。
  • return [{ json: {...} }]:注意還是要包成陣列,就算只吐一筆——[{ json: {} }]。忘了外層 [] 就會爆。

要 group by(例:按客戶分別加總),用 reduce 搭配物件累積:

// 按客戶名字加總金額
const groups = $input.all().reduce((acc, item) => {
  const key = item.json.customer;
  acc[key] = (acc[key] || 0) + item.json.amount;
  return acc;
}, {});

// 轉成 items 陣列吐出去
return Object.entries(groups).map(([customer, total]) => ({
  json: { customer, total }
}));

這段做的事:把所有訂單按 customer 分堆、每堆加總、最後每個客戶吐一筆。這是 Code node 最常見的「Set 做不到但 Code 一段就搞定」的例子。

Pattern 4:從 Code node 呼叫外部 API(很少用)

Code node 內可以用 await 直接呼叫外部 API,但通常用 HTTP Request 節點更好維護——它有 UI 幫你設 headers、auth、retry。真的要在 Code 內呼叫的話:

// 呼叫外部 API,把回應塞成一筆 item
const response = await this.helpers.httpRequest({
  method: 'GET',
  url: 'https://api.example.com/x',
  headers: { 'Accept': 'application/json' }
});

return [{ json: response }];

this.helpers.httpRequest(...) 是 n8n 內建的 helper,等同 HTTP Request 節點但寫在 code 裡。使用時機:要在同一段邏輯裡連續呼叫 N 個 API、或依照某筆資料的欄位動態決定要不要打 API。純粹「呼叫一次 API」的話拉個 HTTP Request 節點就好。

提示:Code node sandbox 有限制,不是所有 Node.js API 都能用。this.helpers.httpRequest(跟其他 this.helpers.* 例如 getBinaryDataBuffer)、crypto(部分)、內建陣列/字串/JSON 方法都可以;fs、外部 require、直接 fetch 預設不行。要用 npm 套件請看官方文件的環境變數 NODE_FUNCTION_ALLOW_EXTERNAL(僅 self-host 有效,Cloud 無法)。

常用 helper API 速查

Code node 有一堆 n8n 幫你注入的 helper($ 開頭的變數)。90% 時候只會用到下面幾個。

寫法是什麼能不能在哪個 mode 用
$input.all() 當前節點吃到的全部 items 陣列。 All Items 模式主用;Each Item 也能用但少見。
$input.item 當前這筆 item(完整物件,含 json 跟 binary)。 只在 Each Item 模式有意義。
$input.item.json 當前 item 的 json 部分(=Expression 裡的 $json)。 Each Item 模式常用。
$('Node Name').all() 抓指定上游節點的全部 items(不限直接上游)。 兩個 mode 都能用。
$('Node Name').first() / .last() 抓指定上游節點的第一筆 / 最後一筆。 兩個 mode 都能用。
$now 當前時間(Luxon DateTime 物件)。 兩個 mode 都能用。
$today 今天 00:00(Luxon)。 兩個 mode 都能用。
$workflow.id / $workflow.name 當前 workflow 的 id 跟名稱。 兩個 mode 都能用。
$execution.id 當前這次執行的 id(Executions 頁面能查)。 兩個 mode 都能用。
console.log(x) 印到Executions 頁面該節點的 log 分頁。 兩個 mode 都能用(debug 神器)。
this.helpers.httpRequest({...}) 呼叫外部 API(等同 HTTP Request 節點)。 兩個 mode 都能用,需 await。

完整清單去官方文件 docs.n8n.io/code/builtin/overview/,還有 $jmespath()(JMESPath 查詢)、$max()/$min()、DateTime(直接叫 Luxon 建構器)這些進階的。

Code node 出錯怎麼辦

Code node 是最容易噴 error 的節點類型,因為你在寫程式碼。好消息是 n8n 的 error 訊息通常寫得夠清楚——照下面 4 步走。

  1. 看節點面板下方的紅色 error 訊息

    Code node 執行失敗時,節點本身會變紅、下方會顯示 error 訊息,通常包含行號。例:SyntaxError at line 3——直接跳到第 3 行看是不是引號沒對、括號沒閉。

  2. 去 Executions 頁面看該節點的 log

    左側選單 Executions → 點失敗那筆 → 點 Code node → 上方切到 Logs 分頁。你在 code 裡打的 console.log(x) 全在這裡看得到。dev 期間狂 log 沒關係,沒人會怪你。

  3. 檢查 output 是不是正確格式

    Code node 成功執行但下游拿不到資料?看 output 是不是 [{ json: {...} }] 這個格式——外層要陣列、每筆要包 json。忘了包 json 是最常見的錯,例:直接 return [{ name: 'Elmo' }] 會被 n8n 認為每筆的資料是空的。正確:return [{ json: { name: 'Elmo' } }]。

  4. 用 console.log(JSON.stringify(x, null, 2)) 印巢狀物件

    直接 console.log(item) 有時只印 [object Object] 看不到內容,用 JSON.stringify(item, null, 2) 把物件轉成好讀的 JSON 字串再印,巢狀多層也一目了然。

觀念:Code node 的除錯循環很單純——改 code → 點 Execute step → 看 output / log → 再改。不要寫一大段才第一次執行,寫兩三行就試跑一次,這樣哪一行壞立刻抓出來。這跟前端寫 JS 的心法一模一樣。

常見卡關

  1. SyntaxError: Unexpected token 或 Unexpected identifier

    JavaScript 語法錯,通常是引號不對稱、括號沒閉、分號亂放。error 訊息會告訴你行號,跳過去看那行前後。常見:混用中文引號 「」 跟英文 ''(前者不是有效 JS 引號);花括號 {} 少一邊;=> 打成 ->。

  2. Cannot read properties of undefined (reading 'xxx')

    你要抓的欄位它的父層根本沒東西。例:item.json.customer.email,但 customer 這個欄位不存在。解法:(a)用 optional chaining item.json.customer?.email;(b)加 if 判斷 if (!item.json.customer) return null;;(c)先修上游節點,確認它真的回傳 customer。

  3. Output 空的,下游拿不到資料

    三個常見原因:(a)忘了寫 return——function 沒 return 等於回 undefined;(b)忘了包 { json: {} }——n8n 認不出你的 output;(c)filter 條件太嚴格——沒有任何 item 符合,結果空陣列,正常但要檢查條件是不是打太緊。

  4. Code 執行很慢(>30 秒 timeout)

    通常是大陣列(>10,000 筆)在跑 .map / .filter。解法:上游先接 Split In Batches 節點 分批處理,每批 500-1000 筆送進 Code node。或者檢查 code 有沒有巢狀 loop(.map 內又 .filter)——那是 O(n²),資料一多就爆炸。

  5. Items to return were not valid

    你 return 的東西不是 n8n item 格式。正確格式:[{ json: {...} }]——外層陣列、每個元素是有 json key 的物件。錯誤:return { name: 'Elmo' }(不是陣列)、return [{ name: 'Elmo' }](沒包 json)。Each Item 模式回 一個 { json: {} };All Items 模式回陣列。

  6. ReferenceError: fetch is not defined 或 require is not defined

    Community 版 sandbox 沒開 fetch 跟 require。呼叫 API 改用 await this.helpers.httpRequest({...});想引 npm 套件的話,self-host 才有解——設環境變數 NODE_FUNCTION_ALLOW_EXTERNAL=lodash 才會開放(Cloud 版無法)。

常見問題

我完全不會 JavaScript,一定要學嗎?
不用。本章教的四個 pattern(filter / map / aggregate / http)抄起來、把條件跟欄位名改成你的就 80% 夠用。實務上業務/行銷同仁很少需要自己寫 Code node——如果真的遇到,把需求丟給工程師或 AI 助理,附上「上游 output 長這樣、我要輸出這樣」的範例,10 分鐘就能生出 code。這章看不懂完全 OK,直接跳到第 21 章 AI Agent 也不影響後面的學習。
Python 好還是 JavaScript 好?
優先 JavaScript。理由:(a)官方主打 JS,helper API 最完整;(b)社群範例 90% 以上是 JS;(c)n8n 2 起 Python 走 native task runner(自架要另外開 runner;先前的 Pyodide/WASM 版本在 n8n 2 已 legacy/不再支援);(d)native Python 只吃括號存取(_input.item['json']['x']),從 JS 範例改寫要注意。什麼時候用 Python:你有現成 Python 邏輯(例:某個字串處理 function、公司內部工具的 Python 片段)要直接搬過來、又能 pip install 需要的套件。純粹「我比較會 Python」不是好理由。
Code node 可以用 npm 套件(例:lodash、axios)嗎?
Cloud 版跟預設的 Community 版:不行,sandbox 只開內建 modules(crypto、querystring 這種)。Self-host 可以,但要在 n8n container 的環境變數設 NODE_FUNCTION_ALLOW_EXTERNAL=lodash,axios(用逗號列你要開的套件名),還要 npm install 進 container。Cloud 用戶想要類似效果,把邏輯拆成 sub-workflow(第 19 章)或改用 HTTP Request 呼叫外部服務。
兩個 Code node 之間可以共用變數嗎?
不能。每個 Code node 是獨立 sandbox,一個 Code node 裡宣告的 const x = 5 在下一個 Code node 看不到。要在節點之間傳資料,就走 n8n 的正規做法:把資料塞進 output 的 json,下一個節點用 $('前一個 Code').item.json.x 抓。想全 workflow 共用常數的話用 Set 節點存或 workflow variables(Enterprise 才有 $vars)。
Code node 可以 await 呼叫其他 workflow 嗎?
不能直接呼叫其他 workflow——那是 Execute Workflow 節點的工作。Code node 內能 await 的東西是 this.helpers.httpRequest(呼叫外部 API)跟其他回 Promise 的內建 helper。想在 Code node 裡跑 sub-workflow 的話,改成:Code node 準備好資料 → Execute Workflow 節點 → 下一個 Code node 接續處理。這樣分工也比較清楚。
ChatGPT 生的 Code node code 能不能直接貼?
可以,但要驗證兩件事:(a)它用的變數是正確的 n8n helper——常見錯誤是 AI 生 items[0].json(舊 v0 寫法)而不是 $input.all()[0].json(新寫法),跑起來會 items is not defined;(b)output 有沒有包 { json: {} } 格式。給 AI 生 code 時明確告訴它「n8n Code node、JavaScript、Run Once for All Items 模式、用 $input.all()」,會生對的機率高很多。生完先小資料量試跑一次再上線。
Code node 跟 Expression 差在哪?
Expression 是「單一欄位的動態填空」,只能寫一行、不能宣告變數、不能 loop、不能 await。Code node 是完整節點,可以寫很多行、能宣告變數、能跑 loop、能 await、能 console.log。判斷公式:邏輯一行 {{ }} 塞得下就用 Expression;一行塞不下(要 if/else 多分支、要 for loop、要暫存中間值)就用 Code node。Expression 適合「動態填值」,Code node 適合「處理邏輯」。
怎麼知道我寫的 code 在 Cloud 版能不能跑?
最快的方法:直接在 Cloud 版試跑。sandbox 限制的東西執行時會噴 error(fetch is not defined、require is not defined、Module not found)。原則上:(a)內建 JS 語法全部 OK(陣列方法、字串方法、JSON、Math、Date);(b)n8n helper($input、$now、this.helpers.httpRequest)OK;(c)npm 套件、fs 檔案系統、直接 fetch 通常不行。有疑慮先做 sandbox test workflow 跑幾行試水溫。