n8nのCodeノード:JavaScriptとPythonで足りないところを書く

公開 書いた人 大林 諒

Codeノードは、ノードと式で書けない処理をJavaScriptかPythonで埋めるノードです。ここでは「問い合わせ受付」に電話番号の整形・重複の判定・本文からの抜き出しを足しながら、2つの実行モード、JavaScriptとPythonの違い、セルフホストで外部ライブラリを使う設定、やってはいけないことまでを書きます。前提は「n8nの使い方入門:最初のワークフローを作って動かす」の問い合わせ受付です。版は2026年10月時点の安定版 2.44.7 で確かめています。

この記事の内容
  1. 先に決めること
  2. 使い分けと設定値の一覧
  3. 手順
  4. 外部ライブラリを使う(セルフホスト)
  5. やってはいけないこと
  6. 応用
  7. できないことと、その代わり
  8. よく詰まるところ
  9. 次にやること

先に決めること

Codeで書くか、ノードと式で済ませるか

Codeノードは最後の手段です。式と違ってプレビューが出ず、値は動かして確かめるしかありません。公式も式で書けるところは式でと勧めています。それに、直せる人が限られます。実際に本番で動かしていると、Codeは書いた人が異動したあと誰も触れなくなり、止まったときに調べが一番遅れる場所になります。

1つの値を整えるなら式、項目を足すならEdit Fields (Set)、件数を絞る・まとめるなら専用のノードを使い、ノードを3つも4つも重ねないと書けないときだけCodeにします。式の書き方は「n8nの式(Expression)とデータの扱い:$json・ノードの参照・日付・JSON」、条件で分ける話は「n8nの分岐とループ:IF・Switch・Merge・Loop Over Items」に分けています。

JavaScriptか、Pythonか

迷ったらJavaScriptです。2.44.7のPythonは、n8nの組み込み変数が入力の _items と _item しか使えず、標準ライブラリの re(正規表現)も既定ではimportできません。Pythonを選ぶのは、保守する人がPythonしか書けないときと、セルフホストでpandasなどを入れて使うときです。

どこで動かしているか

2.xのCodeノードは、n8n本体とは別の「task runner」(コードを隔離して動かす仕組み)の上で動きます。本体の子プロセスで動かすのが内部モード、別のコンテナで動かすのが外部モードで、Cloud・手元のPC・セルフホストで使える範囲が違います。「n8nをセルフホストで導入する:Docker ComposeでVPSに立て、独自ドメインとHTTPSで使う」の構成は外部モードで、task-runners コンテナが n8nio/runners のイメージで動いています。

使い分けと設定値の一覧

Codeの前に試すノードです。

やりたいこと先に試すものCodeが要るとき
項目を足す・名前を変えるEdit Fields (Set)ほぼ要らない
空白を落とす、小文字にそろえる式(.trim() など)同じ整形を何項目にもかける
条件に合う件数だけ残すFilter条件が式で読めないほど多い
前に処理した問い合わせを除くRemove Duplicates「24時間以内」のように期間で区切る
配列を分ける・まとめるSplit Out・Aggregate分け方が中身で変わる
ハッシュや署名の計算Crypto計算した値をさらに比べる
外部のAPIを呼ぶHTTP RequestCodeでは呼べない

2つの実行モードの違いです。

Run Once for All ItemsRun Once for Each Item
動く回数入力が何件でも1回(既定)入力1件ごとに1回
入力の取り方(JavaScript)$input.all()$input.item、$json
入力の取り方(Python)_items_item
返すものitemの配列item 1つ
件数を変えられるか変えられる1件から1件
向いている処理重複判定、集計、並べ替え整形、本文からの抜き出し

動かす場所ごとの違いです。

JavaScriptPython
n8n Cloud使える。crypto と moment だけ読み込める使える。importは一切できない
手元の npx n8n など内部モード使える。モジュールは既定で読み込めない公式は外部モードだけで使える扱い。npxでは「Python runner unavailable」で止まる
セルフホストの外部モード既定で crypto と moment を許可済み。ほかは設定で足す既定でimport不可。標準ライブラリは設定で、外部パッケージはイメージを足して使う
使える組み込み変数$input $json $('ノード名') $now DateTime など_items _item だけ

セルフホストで触る設定です。置き場所を間違えると効きません。

設定既定置き場所(外部モード)
NODE_FUNCTION_ALLOW_BUILTIN(Node.jsの組み込み)cryptorunnersの設定ファイル
NODE_FUNCTION_ALLOW_EXTERNAL(npm)momentrunnersの設定ファイル
N8N_RUNNERS_STDLIB_ALLOW(Python標準)空runnersの設定ファイル
N8N_RUNNERS_EXTERNAL_ALLOW(Python外部)空runnersの設定ファイル
N8N_RUNNERS_TASK_TIMEOUT(実行時間の上限)300秒n8n本体

手順

Codeノードを置いて、雛形を読む

  1. 問い合わせ受付の「問い合わせフォーム」と「問い合わせ一覧に追加」の間の線にカーソルを合わせ、+ を押す
  2. ノードパネルの検索欄に code と打ち、「Code」を選ぶ
  3. 並んだ「Code in JavaScript」「Code in Python」のうち、「Code in JavaScript」を選ぶ
  4. 開いた詳細画面で、「Mode」が「Run Once for All Items」、「Language」が「JavaScript」になっていることを確かめる
ノードパネルで「Code」を選んだあと、「Code in JavaScript」「Code in Python」が並んでいるところ
ノードパネルで「Code」を選んだあと、「Code in JavaScript」「Code in Python」が並んでいるところ

最初から入っている雛形は、All Itemsのモードだと次のとおりです。

// Loop over input items and add a new field called 'myNewField' to the JSON of each one
for (const item of $input.all()) {
  item.json.myNewField = 1;
}

return $input.all();

$input.all() が届いたitemの配列です。「Mode」を「Run Once for Each Item」に替えると、雛形は1件だけを扱う形に変わります。

// Add a new field called 'myNewField' to the JSON of the item
$input.item.json.myNewField = 1;

return $input.item;

返す形は、All Itemsなら [{ json: { ... } }] の配列、Each Itemなら { json: { ... } } を1つです。json で包まずに返してもn8nが包み直しますが、json と別の項目を同じ階層に混ぜると「Invalid output format」で止まります。

Codeノードの設定画面。Mode・Language の欄と、雛形が入ったコード欄
Codeノードの設定画面。Mode・Language の欄と、雛形が入ったコード欄

試すときは「Execute step」を押します。前のノードも一緒に動くので、一度フォームから送ったら「問い合わせフォーム」のOUTPUTで「Pin data」を押して固定します。Codeだけを何度でも試せ、固定したデータは本番の実行には使われません。

例1:電話番号の表記をそろえる(Each Item)

電話番号は全角、+81 始まり、かっこ付きとばらばらに届くので、検索できる形にそろえます。

  1. 「問い合わせフォーム」に「電話番号」の欄を足し、項目名を phone にする
  2. Codeノードの名前を「電話番号を整える」に変える
  3. 「Mode」を「Run Once for Each Item」にする
  4. コード欄を次に置き換える
// 全角を半角にし、+81 を 0 に戻してから数字だけ残す
let tel = String($json.phone ?? '').normalize('NFKC');
tel = tel.replace(/^\+81\s*(\(0\))?/, '0').replace(/\D/g, '');

// 携帯とIP電話だけはハイフンを入れる
let formatted = tel;
if (/^(050|070|080|090)\d{8}$/.test(tel)) {
  formatted = `${tel.slice(0, 3)}-${tel.slice(3, 7)}-${tel.slice(7)}`;
}

$input.item.json.phoneNormalized = formatted;
$input.item.json.phoneValid = /^0\d{9,10}$/.test(tel);
return $input.item;

090-1234-5678 と +81 90 1234 5678 はどちらも 090-1234-5678 に、03 (1234) 5678 は 0312345678 になります。固定電話にハイフンを入れないのは、市外局番の桁数が地域で違い、正規表現では正しく切れないからです。スプレッドシートの電話番号の列には {{ $json.phoneNormalized }} を入れます。

電話番号を整えるCodeノードのコード欄と、OUTPUTに phoneNormalized と phoneValid が増えている状態
電話番号を整えるCodeノードのコード欄と、OUTPUTに phoneNormalized と phoneValid が増えている状態

同じ処理をPythonで書くとこうなります。「Language」を「Python」に替えます。re が使えないので、文字の置き換えで組みます。

raw = str(_item["json"].get("phone") or "")
tel = raw.translate(str.maketrans("0123456789+", "0123456789+"))

digits = "".join(ch for ch in tel if ch in "0123456789")
if tel.strip().startswith("+81"):
    digits = "0" + digits[2:]
    if digits.startswith("00"):
        digits = digits[1:]

formatted = digits
if len(digits) == 11 and digits[:3] in ("050", "070", "080", "090"):
    formatted = digits[:3] + "-" + digits[3:7] + "-" + digits[7:]

_item["json"]["phoneNormalized"] = formatted
_item["json"]["phoneValid"] = len(digits) in (10, 11) and digits.startswith("0")
return _item

値は _item["json"]["phone"] と角かっこで取ります。_item.json.phone では動きません。

Language を Python にしたCodeノード。コード欄の下に「_items」「_item」だけが使えるという注意書きが出ているところ
Language を Python にしたCodeノード。コード欄の下に「_items」「_item」だけが使えるという注意書きが出ているところ

例2:同じ人からの二重送信を見分ける(All Items)

送信ボタンの2度押しや、翌朝の送り直しで二重に対応しないよう、24時間以内に同じメールアドレスから来ていたら印を付けます。Remove Duplicatesは「前に見た値か」で除くので、半年ぶりに来たお客さままで消えます。期間で区切るためにCodeを使います。

  1. 「問い合わせフォーム」の後ろにGoogle Sheetsノードを足し、名前を「問い合わせ一覧を読む」、Operationを「Get Row(s)」、Documentを「問い合わせ一覧」にする
  2. 「Settings」タブで「Always Output Data」をオンにする
  3. その後ろにCodeノードを足し、名前を「重複を判定」、Modeを「Run Once for All Items」にする
  4. コード欄を次に置き換える
  5. 後ろにIFノードを置き、{{ $json.duplicate }} が true のときはSlackに「重複の可能性」と知らせる側へ分ける

2番は、スプレッドシートが空のときにCodeが動かなくなるのを防ぐためです。代わりに空のitemが1件流れるので、Codeで除きます。

// 今回の問い合わせ(フォームの値)
const current = $('問い合わせフォーム').first().json;
const email = String(current.email ?? '').trim().toLowerCase();
const receivedAt = DateTime.fromISO(current.submittedAt);

// スプレッドシートの既存の行(空のitemは除く)
const rows = $input.all()
  .map(i => i.json)
  .filter(r => Object.keys(r).length > 0);

// 同じメールアドレスで、24時間以内に受け付けた行を探す
const hit = rows.find(r => {
  if (String(r['メールアドレス'] ?? '').trim().toLowerCase() !== email) return false;
  const at = DateTime.fromFormat(String(r['受付日時'] ?? ''), 'yyyy-MM-dd HH:mm', { zone: 'Asia/Tokyo' });
  return at.isValid && receivedAt.diff(at, 'hours').hours < 24;
});

return [{
  json: {
    ...current,
    duplicate: Boolean(hit),
    duplicateRow: hit ? hit.row_number : null,
  },
  pairedItem: 0,
}];

入力は全行、出力は1件と件数が変わるのでAll Itemsで書きます。最後の pairedItem: 0 は、出力したitemがどの入力から来たかを伝える印です。件数を変えるCodeでこれがないと、後ろの $('問い合わせフォーム').item が「Paired item data … is unavailable」で止まります。受付日時の列は入門記事どおり yyyy-MM-dd HH:mm の日本時間で書いている前提で、書式を変えたら fromFormat も合わせます。

問い合わせフォーム → 問い合わせ一覧を読む → 重複を判定 → IF → 電話番号を整える → 問い合わせ一覧に追加 とつながったキャンバス
問い合わせフォーム → 問い合わせ一覧を読む → 重複を判定 → IF → 電話番号を整える → 問い合わせ一覧に追加 とつながったキャンバス
Google Sheetsノードの「Settings」タブ。Always Output Data がオンになっているところ
Google Sheetsノードの「Settings」タブ。Always Output Data がオンになっているところ

行が数千件を超えたら、Get Row(s)の「Filters」でメールアドレスを絞ってから渡します。ほかの重複の防ぎ方は「n8nとGoogleスプレッドシート連携:読み書き・更新・重複を防ぐ」にあります。

例3:本文から見積番号と人数を抜き出す(Each Item)

本文に書かれた見積番号(例: Q-202601)や利用人数を、一覧の列に抜き出します。

  1. 「電話番号を整える」の後ろにCodeノードを足し、名前を「本文から抜き出す」にする
  2. Modeを「Run Once for Each Item」にする
  3. コード欄を次に置き換える
  4. スプレッドシートに「見積番号」「人数」の列を足し、{{ $json.quoteNo }} と {{ $json.headcount }} を入れる
// 全角を半角にしてから探す
const text = String($json.message ?? '').normalize('NFKC');

const quote = text.match(/Q-\d{6}/);
const people = text.match(/(\d{1,5})\s*(名|人)/);
const urls = text.match(/https?:\/\/[^\s<>"()]+/g) ?? [];

$input.item.json.quoteNo = quote ? quote[0] : '';
$input.item.json.headcount = people ? Number(people[1]) : null;
$input.item.json.urls = urls.join('\n');
return $input.item;

「先日いただいたQ-202601の見積について、社員50名で利用予定です」なら、quoteNo に Q-202601、headcount に 50 が入ります。日本語の問い合わせは全角が混ざるので、先に normalize('NFKC') で半角にしないと取りこぼします。

本文から抜き出すCodeノードのコード欄と、OUTPUTに quoteNo・headcount・urls が並んでいる状態
本文から抜き出すCodeノードのコード欄と、OUTPUTに quoteNo・headcount・urls が並んでいる状態

書いたコードの確かめ方

途中の値は console.log()(Pythonは print())で出します。出力先はブラウザの開発者ツールのコンソールです。

外部ライブラリを使う(セルフホスト)

セルフホストの外部モード構成が前提です。許可は task-runners コンテナの中の /etc/n8n-task-runners.json の env-overrides に書きます。公式もこのファイルに書くよう明記していて、n8n本体の environment に足しても効きません。

標準ライブラリだけ許可する(イメージはそのまま)

Pythonで re や datetime を使いたいだけなら、設定ファイルを差し替えるだけで済みます。

  1. compose.yaml がある作業フォルダで、既定の設定ファイルを取り出す
docker run --rm --entrypoint cat n8nio/runners:2.44.7 /etc/n8n-task-runners.json > n8n-task-runners.json
chmod 644 n8n-task-runners.json
  1. n8n-task-runners.json の Python側の env-overrides を書き換える
"env-overrides": {
  "N8N_RUNNERS_STDLIB_ALLOW": "re,datetime,unicodedata,json",
  "N8N_RUNNERS_EXTERNAL_ALLOW": ""
}
  1. compose.yaml の task-runners にファイルを差し込む行を足す
  task-runners:
    image: n8nio/runners:2.44.7
    restart: always
    volumes:
      - ./n8n-task-runners.json:/etc/n8n-task-runners.json:ro
  1. sudo docker compose up -d で作り直す

取り出したファイルは env-overrides の中だけを書き換えます。command や args まで消すとランナーが起動しません。

npmやPyPIのパッケージを足す(イメージを作る)

外部パッケージは、runnersのイメージに入れないと使えません。例はJavaScriptの電話番号ライブラリ libphonenumber-js と、Pythonの pandas です。

  1. 作業フォルダに runners フォルダを作り、上で取り出した n8n-task-runners.json を入れる
  2. JavaScript側の env-overrides を次のように書き換える
"env-overrides": {
  "NODE_FUNCTION_ALLOW_BUILTIN": "crypto",
  "NODE_FUNCTION_ALLOW_EXTERNAL": "moment,libphonenumber-js",
  "N8N_RUNNERS_HEALTH_CHECK_SERVER_HOST": "0.0.0.0"
}
  1. Python側は "N8N_RUNNERS_EXTERNAL_ALLOW": "pandas" にし、"N8N_RUNNERS_ALLOW_TRANSITIVE_IMPORTS": "true" を足す(pandasが中で読み込む別のパッケージまで並べずに済む)
  2. runners/Dockerfile を作る
ARG N8N_VERSION=2.44.7
FROM n8nio/runners:${N8N_VERSION}
USER root
RUN cd /opt/runners/task-runner-javascript && pnpm add libphonenumber-js
RUN cd /opt/runners/task-runner-python && uv pip install pandas
COPY n8n-task-runners.json /etc/n8n-task-runners.json
USER runner
  1. compose.yaml の task-runners の image の行を、次の2行に替える
    build: ./runners
    image: n8n-runners-custom:2.44.7
  1. sudo docker compose build task-runners を実行し、続けて sudo docker compose up -d

Codeノードでは require で読み込みます。import 文は使えません。

const { parsePhoneNumberFromString } = require('libphonenumber-js');

const n = parsePhoneNumberFromString(String($json.phone ?? '').normalize('NFKC'), 'JP');
$input.item.json.phoneNormalized = n && n.isValid() ? n.formatNational() : '';
return $input.item;

固定電話も 03-1234-5678 や 0120-123-456 と正しく区切れます。

runnersの版はn8n本体と同じでなければいけないので、本体を上げたら ARG N8N_VERSION と image の行の版も同じ番号にして作り直します。アップデートの手順は「n8nを止めずに運用する:アップデート・バックアップ・エラー通知・脆弱性」にあります。

やってはいけないこと

APIキーやパスワードをコードに書く

Codeの中身はワークフローを開ける人なら誰でも読め、書き出したJSONにも入ります。CodeはCredential(認証情報)を読めない作りなので、認証が要る呼び出しはHTTP Requestに任せます。Credentialの扱いは「n8nの認証情報(Credential):OAuth2・APIキー・共有と権限」にあります。

環境変数を読もうとする

$env.SLACK_TOKEN のような読み出しは2.0から既定で止まり、「access to env vars denied」になります。Pythonの os.environ も別の設定 N8N_BLOCK_RUNNER_ENV_ACCESS(runnersの設定ファイルの env-overrides 側)で止まっています。$env は N8N_BLOCK_ENV_ACCESS_IN_NODE=false で戻せますが、どちらも編集できる人全員に環境変数を見せるので戻しません。設定値の共有は「Variables」($vars、CloudはPro以上・セルフホストはBusiness以上)を使います。

重い処理を載せる・安全対策を外す

Code 1回の実行は、既定で300秒を超えると「Task execution timed out after 300 seconds」で打ち切られ、3.0ではこれが60秒に縮む予告です。大きな集計はn8nの外で済ませるか、「Loop Over Items」で小分けにします。N8N_RUNNERS_INSECURE_MODE=true でランナーの安全対策を外すのと、内部モードのまま本番を回すのはやめます。公式は、内部モードだと編集できる人がDB・暗号鍵・Credentialまで読めるおそれがあるとしています。

応用

実行をメールアドレスで探せるようにする

$execution.customData.set('email', $json.email) と書くと、実行一覧をその値で絞り込めます。n8n CloudはPro以上、セルフホストは無料登録したCommunity以上で使えます。前回の実行の値を残す $getWorkflowStaticData は、テスト実行では保存されない実験的な機能なので、新しく作るなら「Data tables」に書きます。

振り分けはAIに任せる

「急ぎかどうか」「お見積もりかサポートか」のように書き方が決まっていないものは、正規表現では拾えません。「Text Classifier」や「Information Extractor」のノードを使います。つなぎ方は「n8nでAIエージェントを作る:ChatGPT・Claude・Geminiをつなぐ」にあります。

できないことと、その代わり

できないこと代わりの手
CodeからHTTP通信をする(fetch も使えない)HTTP Requestノードで呼び、結果をCodeで加工する
Codeからファイルを読み書きするファイルを読み書きするノードを使う
CodeでCredentialを読むCredentialを付けたHTTP Requestに分ける
n8n Cloudで外部パッケージを読み込む処理を外のAPIに切り出すか、セルフホストにする
Pythonで $('ノード名') や $now を使うJavaScriptで書くか、前のEdit Fields (Set)で値をそろえる
Pythonで class の定義、type()、open() を使う既定で禁止。関数と辞書で書く
Codeの中で $evaluateExpression() を使う前のEdit Fields (Set)で式を評価して渡す
セルフホストでCodeの「Ask AI」タブを使うCloudだけの機能で、3.0で外れる予告もある。自分で書く

よく詰まるところ

症状原因直し方
「Code doesn’t return items properly」All Itemsで配列ではなく値やオブジェクトを返した[{ json: { ... } }] の配列で返す
「Code doesn’t return a single object」Each Itemで配列を返した1件だけ返すか、Modeを「Run Once for All Items」にする
「A ‘json’ property isn’t an object」json に配列や文字列を入れたjson には必ずオブジェクトを入れる
「Module ‘…’ is disallowed」許可の一覧に無いrunnersの設定ファイルの env-overrides に足す
「Cannot find module ‘…’」イメージに入っていないrunnersのイメージを作り直す
Pythonで「Import of standard library module ‘re’ is disallowed」Pythonは既定でimportが止まっているN8N_RUNNERS_STDLIB_ALLOW に足す
「Python runner unavailable」npx n8n など内部モードで動かしている外部モードで立てるか、JavaScriptで書く
「access to env vars denied」$env を読もうとしたCredentialか $vars に替える
後ろの式で「Paired item data … is unavailable」件数を変えたCodeに pairedItem が無い返すitemに pairedItem を付ける
スプレッドシートが空だとCodeが動かない前のノードの出力が0件前のノードの「Always Output Data」をオンにする
Pythonで 'dict' object has no attribute 'json'_item.json と書いた_item["json"] と書く
Codeの中の日時が13〜14時間ずれるtask-runners にタイムゾーンが無いGENERIC_TIMEZONE を足す(JavaScriptの $now・Luxonに効く。TZ は設定ファイルの allowed-env に足さないと渡らない)

次にやること

  • n8nの式(Expression)とデータの扱い:$json・ノードの参照・日付・JSON
  • n8nの分岐とループ:IF・Switch・Merge・Loop Over Items
  • n8nをセルフホストで導入する:Docker ComposeでVPSに立て、独自ドメインとHTTPSで使う
  • n8nを止めずに運用する:アップデート・バックアップ・エラー通知・脆弱性

あわせて読む