n8nの式(Expression)とデータの扱い:$json・ノードの参照・日付・JSON

公開 更新 書いた人 大林 諒

n8nで組んでいて手が止まるのは、ノードの設定より「前のノードの値をどう書いて取り出すか」のほうです。この記事では、Webサイトの問い合わせをGoogleスプレッドシート「問い合わせ一覧」に残し、Slackの #問い合わせ に知らせる「問い合わせ受付」ワークフローを例に、データの形、式の書き方、日付・文字列・数値・配列の変換、Edit Fields (Set) での整え方を通します。前提は「n8nの使い方入門:最初のワークフローを作って動かす」の3ノードが動いていて、入口が「n8nで外部サービスとつなぐ:Webhook・HTTP Request・API」のWebhookになっていることです。版は2026年10月時点の安定版2.44.7で書いています。

この記事の内容
  1. 先に決めること
  2. n8nのデータの形
  3. 式で使うものの一覧
  4. 手順:届いた問い合わせを Edit Fields (Set) で整える
  5. 日付を扱う(Luxon)
  6. 文字列・数値・配列・JSONのよく使う書き方
  7. 応用
  8. できないことと、その代わり
  9. よく詰まるところ
  10. 次にやること

先に決めること

値を整える場所を1か所に寄せる

式はどのノードのどの欄にも書けます。便利な反面、Google Sheetsの列に .trim()、Slackの本文にも .trim() と散らすと、直すたびに全部のノードを開いて回ることになります。公式のドキュメントも、変換だけが目的なら Edit Fields (Set) にまとめるよう勧めています。線引きは次のとおりです。

やること置き場所
空白を落とす、形式を揃える、名前を付け直すトリガーの直後に置く Edit Fields (Set) 1つ
1つの欄にだけ値をはめる(Slackの本文、メールの件名)その欄の式
配列を1件ずつに分ける、まとめる、並べ替えるSplit Out・Aggregate・Sort などの専用ノード
複数のitemを見比べる、外部のライブラリを使うCodeノード

タイムゾーンを Asia/Tokyo に揃えておく

$now などの日付は、ワークフローのタイムゾーンで計算されます。何も設定しないとアメリカ東部時間(America/New_York)です。セルフホストは環境変数 GENERIC_TIMEZONE、n8n Cloudは管理画面の時刻の設定、ワークフローごとは「Settings」の「Timezone」で揃えます。手順は「n8nの使い方入門:最初のワークフローを作って動かす」、Codeノードの中まで揃える話は「n8nをセルフホストで導入する:Docker ComposeでVPSに立て、独自ドメインとHTTPSで使う」に書きました。ここが揃っていないと、この記事の日付の式はすべて13〜14時間ずれます。

ノード名を先に決める

式は $('受付データを整える') のようにノード名で前のノードを呼びます。同じワークフローの中で名前を変えれば、n8nが参照している式も一緒に書き換えます。ただし、ノードを別のワークフローへコピーしたときは書き換わらず、参照が切れます。ノード名は役割が分かる日本語にして、作ったあとはなるべく変えません。

n8nのデータの形

ノードの間を流れるデータは、いつも「itemの配列」です。1つのitemの中に、普通の値を入れる json と、ファイルがあればその中身を入れる binary があります。OUTPUTを「JSON」の見え方にすると、json の中身だけが配列で並んで見えます。

自社サイトのフォームからWebhookに届いた問い合わせは、こう見えます。

[
  {
    "headers": { "content-type": "application/json" },
    "params": {},
    "query": {},
    "body": {
      "company": " 株式会社サンプル ",
      "name": "山田\t太郎",
      "email": "Yamada@Example.com",
      "category": "お見積もり",
      "employees": "30",
      "message": "料金表を送ってください。\n導入時期は来月です。",
      "contact": { "tel": "03-1234-5678", "department": "総務部" },
      "services": ["問い合わせ受付", "日報集計"],
      "sentAt": "2026-10-26T05:05:12.000Z"
    },
    "webhookUrl": "https://n8n.example.co.jp/webhook-test/inquiry",
    "executionMode": "test"
  }
]
部分中身式での取り方
配列全体ノードが出した結果の全部$input.all()、$('ノード名').all()
item配列の1つ。後ろのノードはitemごとに1回動く$input.item、$('ノード名').item
jsonitemの値。入れ子(オブジェクト)や配列も入る$json.body.company
binary添付ファイルや画像。data という名前で入ることが多い$binary.data.fileName
対応の印(pairedItem)どの入力itemから作られたか。画面には出ない.item が前のノードを辿るのに使う

フォームの値は body の下、contact はさらにその下、services は配列です。employees の30は数字に見えますが、"30" と引用符が付いているので文字列です。この入れ子と型の違いが、この記事で扱うつまずきのほとんどです。

itemの数はノードごとに変わります。フォーム1回のWebhookは1件ですが、Google Sheetsで未対応の行を10行読めば10件になり、後ろのSlackは10通送ります。1通にまとめる書き方は応用で扱います。

WebhookノードのOUTPUTをJSON表示にし、body の下に company・contact・services が見えている状態
WebhookノードのOUTPUTをJSON表示にし、body の下に company・contact・services が見えている状態

式で使うものの一覧

式は欄に {{ }} で書き、中身はJavaScriptです(Pythonは式では使えません)。欄の上で「Fixed」(固定値)と「Expression」(式)を切り替え、Expressionにすると欄の下に「Result」(計算結果)が出ます。値が無いと [undefined]、空の文字なら [empty] と出ます。

書き方何を指すか使う場面
$json直前のノードから来た、いま処理中のitemの値直前のノードの値を使う
$('ノード名').item.json指定したノードの、いまのitemの元になったitem2つ以上前のノードの値を使う
$('ノード名').first().json指定したノードの先頭のitem何件あっても先頭だけで足りるとき
$('ノード名').all()指定したノードの全item全件を並べる、数える
$('ノード名').isExecutedそのノードが動いたか(true/false)分岐の片側だけ動くとき
$input.item $input.first() $input.all()直前のノードのitem・先頭・全件$json と同じ。全件は $input.all()
$now $todayいまの日時 / 今日の0時(Luxonの日時)受付日時、期限の計算
$itemIndexいま処理中のitemが何番目か(0から)番号を振る、.all()[$itemIndex]
$execution.id $workflow.name実行のIDとワークフロー名エラー通知に添える
$vars.名前Variables(変数)の値送り先などの共通の値
$ifEmpty(値, 代わり)値が空なら代わりを返す未入力の補い

$vars のVariablesは、n8n CloudのPro・Enterprise、セルフホストのBusiness・Enterpriseで使える機能です(2026年10月時点)。値はすべて文字列になります。

どれを使うかは、次の順で決めます。

  1. 直前のノードの値なら $json
  2. 2つ以上前なら $('ノード名').item.json
  3. .item でエラーになる場所(分岐の合流やCodeノードの後ろ)なら $('ノード名').first().json か $('ノード名').all()[$itemIndex].json
  4. 全件を1つの文にまとめるなら $input.all() と、ノードの「Settings」の「Execute Once」

式は手で打たずに、INPUTから欄へドラッグして作るのが確実です。INPUTには前にあるノードが一覧で並び、Webhookのような2つ以上前のノードも開いて項目をドラッグできます(TableやJSONの表示では、上のノード名の選択欄で切り替えます)。直前のノードからなら {{ $json.company }}、それより前なら {{ $('Webhook').item.json.body.company }} の形で入ります。項目名が日本語や記号を含むときは $json['会社名'] のように角かっこで入ります。

SlackノードのMessage Textの欄に $('Webhook').item.json.body.company が入り、欄の下に結果の「株式会社サンプル」が出ている状態
SlackノードのMessage Textの欄に $(‘Webhook’).item.json.body.company が入り、欄の下に結果の「株式会社サンプル」が出ている状態

手順:届いた問い合わせを Edit Fields (Set) で整える

届いたJSONの道筋を確かめる

Webhookノードで「Listen for test event」を押し、上のJSONの body の中身をcurlで送ります。送り方は「n8nで外部サービスとつなぐ:Webhook・HTTP Request・API」と同じです。OUTPUTをJSONの見え方にし、body の中に値が入っていることを確かめてから次へ進みます。

Edit Fields (Set) を置いて項目を作る

先に「問い合わせ一覧」の1行目に「電話番号」「従業員数」「希望サービス」の3列を足しておきます。Edit Fields (Set) で作る項目名をシートの見出しと同じにしておくと、後ろのGoogle Sheetsで「Map Automatically」(項目名と見出しが同じ列に自動で入れる)が使え、列ごとに式を書かずに済みます。

  1. Webhookの右の + を押し、「Edit Fields (Set)」を追加する
  2. ノード名を「受付データを整える」に変える
  3. 「Mode」は「Manual Mapping」のままにする
  4. INPUTの body を開き、company を「Fields to Set」の「Drag input fields here」へドラッグする
  5. 名前を「会社名」に直し、値の式の末尾に .trim() を足す
  6. 「Add Field」で残りの項目を足し、下の表のとおり名前・型・値を入れる
  7. 「Include Other Input Fields」はオフのままにする(公式ドキュメントでは旧表記の「Keep Only Set Fields」「Include in Output」で書かれている)
  8. 「Execute step」を押し、OUTPUTに10項目だけが並んでいることを確かめる
名前型値
受付日時String{{ $now.toFormat('yyyy-MM-dd HH:mm') }}
会社名String{{ $json.body.company.trim() }}
お名前String{{ $json.body.name.replace(/[\t\r\n]+/g, ' ').trim() }}
メールアドレスString{{ $json.body.email.trim().toLowerCase() }}
種類String{{ $ifEmpty($json.body.category, 'その他') }}
内容String{{ $json.body.message.trim() }}
電話番号String{{ $json.body.contact?.tel ?? '' }}
従業員数Number{{ $json.body.employees }}
希望サービスString{{ ($json.body.services ?? []).join('、') }}
対応状況String未対応(Fixedのまま)

「Include Other Input Fields」をオフにすると、出力はここで作った項目だけになり、headers などの要らない値が後ろへ流れません。届いた値も全部残したいときだけオンにします。オフのまま後ろのノードで $json.body.company と書くと空になります。そのときは $('Webhook').item.json.body.company で取りに行きます。

Edit Fields (Set) の Fields to Set に10項目が並び、各行の型(String・Number)と値の式が見えている状態
Edit Fields (Set) の Fields to Set に10項目が並び、各行の型(String・Number)と値の式が見えている状態

型を選ぶ

各項目の左にある型は「String」「Number」「Boolean」「Array」「Object」「Binary Data」から選べ、選んだ型に変換して出力します。2.44.7で試した結果は次のとおりです。

届いた値選んだ型結果
"30"Number30(数値になる)
"1,200,000"Numberエラー('予算' expects a number but we got '1,200,000')
""(空)Number0 になる
"true"Booleantrue
"はい"Booleanエラー
"03-1234-5678"Numberエラー。ハイフンを落としても先頭の0が消えるのでStringにする

カンマ付きの金額は、{{ $json.body.budget.replaceAll(',', '').toNumber() }} のように式でカンマを落としてからNumberにします。空欄が0になるのは見落としやすく、「従業員数0」の行がシートに並びます。未入力を区別したい項目はStringのまま残します。エラーにせず型の変換だけゆるめたいときは、「Add option」から「Ignore Type Conversion Errors」を足します。

入れ子のJSONを平らにする

Google Sheetsの列やSlackの本文は、入れ子を受け付けません。body.contact.tel のように奥にある値は、表の「電話番号」のように1段の項目へ取り出します。これが「平らにする」作業です。取り出し方は値の形で決まります。

届いた形やり方例
オブジェクトの中の値項目を1つ作り、パスで取り出す{{ $json.body.contact.tel }}
文字や数字の配列区切り文字でつないで1つの文字にする{{ $json.body.services.join('、') }}
オブジェクトの配列(明細など)「Split Out」の「Fields To Split Out」に配列の項目名を入れ、1件ずつのitemにするbody.items
項目が多いオブジェクト「Mode」を「JSON」にして形をまとめて書く下の例

JSONモードではこう書きます。

{
  "会社名": {{ JSON.stringify($json.body.company.trim()) }},
  "電話番号": {{ JSON.stringify($json.body.contact.tel) }},
  "部署": {{ JSON.stringify($json.body.contact.department) }},
  "内容": {{ JSON.stringify($json.body.message) }}
}

文字の値を JSON.stringify() で包むのは、" や改行でJSONが崩れるのを防ぐためです。2.44.7のEdit Fieldsは多少崩れても直して読みますが、HTTP Requestの本文では「The value in the “JSON Body” field is not valid JSON」で止まるので、どこでも包む癖をつけておきます。

名前に . を入れると入れ子になる点にも気をつけます。名前を 連絡先.電話番号 にすると、{"連絡先": {"電話番号": ...}} という入れ子が出力されます。点を含む名前のまま出したいときは「Add option」から「Support Dot Notation」を足してオフにします。

後ろのノードから使う

  1. 「問い合わせ一覧に追加」(Google Sheets)を開き、「Mapping Column Mode」を「Map Automatically」に変える
  2. 「Execute step」を押し、シートの10列に値が入ったことを確かめる
  3. Slackの「Message Text」を下に変える
新しい問い合わせが届きました
会社名: {{ $('受付データを整える').item.json['会社名'] }}
お名前: {{ $('受付データを整える').item.json['お名前'] }}
種類: {{ $('受付データを整える').item.json['種類'] }}
希望: {{ $('受付データを整える').item.json['希望サービス'] || '(未選択)' }}

Slackの直前はGoogle Sheetsなので、$json ではなくノード名で「受付データを整える」を指名します。

SlackノードのMessage Textに $('受付データを整える') の式が入り、Resultに整った値が出ている状態
SlackノードのMessage Textに $(‘受付データを整える’) の式が入り、Resultに整った値が出ている状態

日付を扱う(Luxon)

n8nの日付は、Luxonという日付ライブラリの DateTime で扱います。$now と $today は最初から DateTime です。ノードからノードへ渡るときは文字列になるので、前のノードの日付で計算するときは、いったん DateTime に読み直します。

やりたいこと書き方結果の例
いまを表示する{{ $now.toFormat('yyyy-MM-dd HH:mm') }}2026-10-26 14:05
曜日を日本語で付ける{{ $now.setLocale('ja').toFormat('M/d(EEE)') }}10/26(月)
ISO形式の文字列を読む{{ DateTime.fromISO($json.body.sentAt) }}ワークフローの時刻に直して読む
書式が決まった文字列を読む{{ DateTime.fromFormat('2026/10/26 14:05', 'yyyy/MM/dd HH:mm') }}2026-10-26T14:05:00.000+09:00
3日後{{ $now.plus(3, 'days').toFormat('yyyy-MM-dd') }}2026-10-29
7日前{{ $today.minus(7, 'days').toFormat('yyyy-MM-dd') }}2026-10-19
月初・月末{{ $now.startOf('month').toFormat('yyyy-MM-dd') }}2026-10-01
2つの日付の差(日数){{ $now.diff(DateTime.fromISO('2026-10-20'), 'days').days }}6.58 など
日本時間に直す{{ $json.body.sentAt.toDateTime().setZone('Asia/Tokyo') }}2026-10-26T14:05:12.000+09:00

書式の記号は大文字と小文字で意味が変わります。MM は月、mm は分、HH は24時間の時、hh は12時間の時です。yyyy-mm-dd と書くと月の位置に分が入り、毎日日付がおかしくなります。

時差では2か所でつまずきます。1つは、末尾が Z(世界標準時)の文字列に .toDateTime() を付けると、世界標準時のまま読まれることです。2.44.7で試すと、"2026-10-26T05:05:12.000Z".toDateTime().toFormat('yyyy-MM-dd HH:mm') は「2026-10-26 05:05」になり、DateTime.fromISO() で読むと「2026-10-26 14:05」になりました。外から届いた日時は DateTime.fromISO() で読むか、.setZone('Asia/Tokyo') を付けてから整形します。

もう1つは、DateTime を文字とつなぐと数字になることです。{{ '受付: ' + $now }} は「受付: 1793012522853」のようなミリ秒の数字になります。文字とつなぐ前に .toFormat() を付けます。

式では format() も使えますが、n8nが式のために足したものでCodeノードでは動きません。両方で通じる toFormat() に揃えておきます。日付だけを扱うなら、式を書かずに「Date & Time」ノード(「Format a Date」「Add to a Date」「Get Time Between Dates」など)を使う手もあります。

受付日時の欄に $now.toFormat('yyyy-MM-dd HH:mm') を書き、Resultに日本時間が出ている状態
受付日時の欄に $now.toFormat(‘yyyy-MM-dd HH:mm’) を書き、Resultに日本時間が出ている状態

文字列・数値・配列・JSONのよく使う書き方

n8nはJavaScriptの書き方に加えて、式のための関数を足しています。値の後ろに . を打つと候補が出ます。

やりたいこと(文字列)書き方結果
前後の空白・改行を落とす$json.body.company.trim()株式会社サンプル
タブ・改行を半角スペースに.replace(/[\t\r\n]+/g, ' ')山田 太郎
全部置き換える.replaceAll('-', '')0312345678
小文字に揃える.toLowerCase()yamada@example.com
1行目だけ.split('\n')[0]料金表を送ってください。
メールの形か.isEmail()true
HTMLのタグを消す.removeTags()<p>本文</p> が「本文」に
全角英数字を半角に.normalize('NFKC')ABC123 が ABC123 に
やりたいこと(数値・真偽)書き方結果
文字を数値に$json.body.employees.toNumber()30
カンマ付きを数値に.replaceAll(',', '').toNumber()1200000
3桁区切りで表示(1200000).format('ja-JP')1,200,000
小数第1位で丸める(1.256).round(1)1.3
文字を真偽に$json.body.agree.toBoolean()“true” が true、”false”・”0″・”no” が false
やりたいこと(配列)書き方結果
つないで1つの文字に$json.body.services.join('、')問い合わせ受付、日報集計
件数.length2
含むか.includes('日報集計')true
空を除く.compact()null や “” が消える
重複を除く.unique()同じ値が1つに
オブジェクトの配列から1項目だけ.pluck('company')会社名だけの配列
合計.sum()数値の配列だけ。文字が混ざるとエラー
やりたいこと(オブジェクト・JSON)書き方結果
無いかもしれない値を安全に取る$json.body.contact?.tel ?? ''無ければ空
空なら代わりの値$ifEmpty($json.body.category, 'その他')その他
JSONの文字列にするJSON.stringify($json.body.contact){“tel”:”03-1234-5678″,…}
JSONの文字列を読む$json.raw.parseJson()オブジェクトに戻る
条件で値を変える$json.body.category === 'お見積もり' ? '営業' : 'サポート'営業

オブジェクトをそのまま文字とつなぐと [object Object] になります。Slackの本文に入れ子をそのまま出したいときは JSON.stringify() で文字にします。日本語の全角・半角や文字化けの扱いは「n8nを日本語で使う:画面の言語、日本語化の現状と、日本語データでつまずくところ」に分けました。

応用

毎朝、未対応の件数と会社名を1通で送る

Schedule Triggerで毎朝9時に動かし、Google Sheetsの「Get row(s) in sheet」で「対応状況」が「未対応」の行を読むと、行の数だけitemが出ます。そのままSlackにつなぐと10件なら10通届くので、Slackノードの「Settings」で「Execute Once」をオンにし、本文で全件を読みます。

未対応の問い合わせが {{ $input.all().length }} 件あります
{{ $input.all().map(i => '・' + i.json['会社名'] + '(' + i.json['受付日時'] + ')').join('\n') }}

Execute Onceは最初のitemで1回だけ動く設定で、$input.all() はそのときも全件を返します。定期実行の組み方は「n8nの定期実行とサブワークフロー:Schedule Triggerと部品化」で扱います。

SlackのMessage Textに $input.all() の式を書いた式の編集画面と、Settingsの「Execute Once」をオンにした状態(2枚)
SlackのMessage Textに $input.all() の式を書いた式の編集画面と、Settingsの「Execute Once」をオンにした状態(2枚)
SlackのMessage Textに $input.all() の式を書いた式の編集画面と、Settingsの「Execute Once」をオンにした状態(2枚)

何行もの処理を式に書く

式は1つの値を返す形でしか書けず、const で変数を置くと「invalid syntax」になります。どうしても途中の値を置きたいときは、関数で包んですぐ呼ぶ形(即時実行関数)にします。

{{ (() => {
  const company = $json.body.company.trim();
  const dept = $json.body.contact?.department ?? '';
  return dept ? `${company}(${dept})` : company;
})() }}

この形が3行を超えたら、Codeノードへ移す合図です。

種類で分けるのは式ではなくIF・Switchで

? : で値を入れ替えるのは式で足ります。通知先を変えるなど処理そのものを分けるときは、IFやSwitchで組んだほうが画面で流れが追えます。組み方は「n8nの分岐とループ:IF・Switch・Merge・Loop Over Items」に書きました。

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

できないこと理由代わりの手
式でPythonを書く式はJavaScriptだけCodeノードで「Python」を選ぶ
式で const などの文を並べる式は値を1つ返す形だけ即時実行関数で包むか、Codeノードへ
前のitemの計算結果を次のitemで使う式はitemごとに毎回まっさらに計算されるAggregate・Summarizeでまとめるか、Codeノード
式で環境変数($env)を読む2.0から既定で止められ、「access to env vars denied」になる共通の値はVariables、秘密の値はCredentialに置く
重い計算を式で回すセルフホストの既定では1回の計算が5秒で打ち切られる(N8N_EXPRESSION_ENGINE_TIMEOUT)Codeノードに移す
Codeノードで format() や plus(3, 'days') を使うn8nが式のために足した書き方で、Codeノードでは使えないtoFormat()、plus({ days: 3 }) と書く

よく詰まるところ

症状原因直し方
Resultが [undefined] で、本番でも空のまま流れる項目名の打ち間違いや大文字小文字の違い。2.44.7では存在しない項目にメソッドを続けてもエラーにならず空になるINPUTからドラッグして作り直す。Webhookなら body の下か確かめる
2つ前のノードの値が取れない$json は直前のノードしか指さない$('ノード名').item.json で指名する
「Referenced node doesn’t exist」ノード名の打ち間違い、または別のワークフローからコピーしてきて名前が違うINPUTからドラッグし直し、式のノード名を今の名前に揃える
「Node ‘〜’ hasn’t been executed」、または「Can’t get data for expression」参照先のノードがまだ動いていない状態で「Execute step」を押したINPUTの「Execute previous nodes」を押すか、前のノードをピン留めする
「Multiple matching items」や「is on another branch」.item で辿る先が複数ある、または別の分岐にある.first()、.last()、.all()[$itemIndex] に変える
Codeノードの後ろで .item がエラーになるCodeノードが新しいitemを作り、元のitemとの対応の印を返していないCodeノードで対応を返すか、.first() に変える。書き方はCodeノードの記事へ
30+1 が 301 になるフォームの値は数字でも文字列.toNumber() を付けるか、Edit Fieldsで型をNumberにする
「〜 expects a number but we got ‘1,200,000’」型をNumberにした項目にカンマや単位が入っている.replaceAll(',', '') で落としてから渡す
未入力のはずの従業員数が0で入る型Numberは空の文字を0にする未入力と区別したい項目はStringにする
受付日時が9時間ずれるZ 付きの日時を .toDateTime() で読んで世界標準時のまま整形したDateTime.fromISO() で読むか .setZone('Asia/Tokyo') を付ける
受付日時が13〜14時間ずれるタイムゾーンが初期値のAmerica/New_YorkGENERIC_TIMEZONE かワークフローの「Timezone」を Asia/Tokyo にする
日付が「1793012522853」のような数字になるDateTime をそのまま文字とつないだ.toFormat('yyyy-MM-dd HH:mm') を付けてからつなぐ
Slackに [object Object] と出るオブジェクトをそのまま文字にした中の値をパスで取り出すか JSON.stringify() で包む
Slackに {{ $json.company }} がそのまま届く欄が「Fixed」のまま式を貼った欄を「Expression」に切り替える

詰まったら、まずOUTPUTをJSONの見え方にして、値がどの階層に、どの型で、何件入っているかを見ます。式の問題の大半は、ここを見れば原因が分かります。

「Multiple matching items」のエラーが出たノードの詳細画面。エラーの説明に .first() などの書き換え案が出ている状態
「Multiple matching items」のエラーが出たノードの詳細画面。エラーの説明に .first() などの書き換え案が出ている状態

次にやること

あわせて読む