mcpField
N
n8n-expressions-official

n8n-expressions-official

github.com/n8n-io開発5582.0k 回インストール2 週間前に更新
リポジトリ

n8n-expressions-official について

n8n のワークフロー作成者向けに、JavaScript 式、ノード間参照、日付処理、データ整形を解説するガイドです。安定した参照方法と不要なノードの削減を重視します。

主な機能

  1. 01ノード名でデータを参照
  2. 02Luxon による日付処理
  3. 03インライン式でノードを削減
  4. 04JavaScript の配列メソッド
  5. 05複数行ロジックの整形とコメント

活用シーン

  1. 01上流ノードの特定フィールドを参照
  2. 02フィールド内で日付や形式を計算
  3. 03ノード出力のフィルタリングと集約
  4. 04Set ノードの必要性を判断

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/n8n-io/skills --skill n8n-expressions-official

インストール後、Claude Code で/n8n-expressions-officialを入力して読み込まれたか確認します。

詳細

n8n Expressions

n8n の式言語は、{{...}} ブロック内に埋め込まれた JavaScript です。式は一度に 1 つのアイテム($json)に対して同期的に実行され、上流ノード($('Name'))、日付用の Luxon、そしてネイティブ JavaScript のほとんどの機能を利用できます。

絶対に守ること

データは $json ではなく、ノード名で参照してください。 $('Node Name').item.json.field(または .first().json.field)を使います。$json も機能しますが、ノードによってアイテムのコンテキストがクリアされた場合(Aggregate、Run for All モードの Code、分岐のマージなど)や、リファクタリングで中間ノードが追加された場合に壊れます。失敗しても通知されず、下流にはエラーなしで誤ったデータが渡されます。ノード名による参照は安定しています。

基本方針

  • 単一の下流フィールドに値を渡すことだけが目的の Set ノードは作らないでください。 式は利用側に直接記述してください。同じ導出値を 2 つ以上の箇所で使う場合、導出処理が複雑な場合、または Set がサブワークフローの最終的な返却値を整形する場合は、Set を使う価値があります。下記の「Set ノードのアンチパターン」を参照してください。
  • 日付には DateTime ノードではなく Luxon を使ってください。 日付の計算、フォーマット、解析はすべて式で実行できます。例:{{ DateTime.now().minus({ days: 7 }).toISO() }}。DateTime ノードは初心者のユーザーにとってキャンバス上で見つけやすいですが、ユーザーから明示的に求められない限り、使用は避けてください。
  • 一般に、余分なノードを追加するより式を使ってください。 メール本文はメールノードの本文フィールドで作成し、URL は HTTP Request の URL フィールドで計算してください。変換処理を再利用する場合、またはセクションの主な目的である場合に、追加のノードを使ってください。
  • 複数行の式にはインデントとコメントを付けてください。 式が複数行にわたる場合は、通常のコードのように整形してください。n8n ユーザーの多くはコーディングをしないため、簡潔なインラインコメントでコードを説明してください。

$json.x よりノード名で参照する($('Name').item.json.x)理由

$json は「このノードに流れ込む現在のアイテム」を意味します。1 つのソースの直後にノードがあり、アイテムのコンテキストをクリアするノード(Aggregate、Run for All を使う Code、分岐のマージなど)がなければ問題ありません。

次の場合に問題が起きます。

  • ソースと利用側の間にノードを挿入する(利用側が 3 つ前のノードの $json.x を読んでいたのに、新しい中間ノードが $json の参照先になる)。
  • ノードがコンテキストをクリアする(Aggregate、一部のマージ、データの形を維持しない Code ノード)。
  • 分岐が Merge 経由で合流する。$json は最後に実行された分岐の値になるため、結果が一定しません。

$('Get User').item.json.id なら参照先が明確です。途中に何があっても、常に名前を指定したノードの最初のアイテムの JSON を参照できます。

このルールを適用する例外:

分岐が合流し、安定した参照点が必要な場合は、合流地点に NoOp ノードを挿入してください。内容が分かる名前を付けます(例:Combine Inputs)。

Branch A ──┐
           ├─→ [NoOp: Combine Inputs] ──→ Downstream nodes use $('Combine Inputs').item.json.x
Branch B ──┘

NoOp はリファクタリング後も維持されます。Combine Inputs と利用側の間に変換処理を挿入しても、$('Combine Inputs') の参照は壊れません。

中間処理によってコンテキストがクリアされるノードのデータを下流ノードが必要とする場合、このパターンは必須です。

分岐によって出力のデータ構造が異なる場合は、NoOp ではなく Set ノードを使ってください。 NoOp は受け取ったデータ構造をそのまま渡すため、下流側ではどの分岐が実行されたかを把握する必要があります。Set ノードなら両方の分岐を同じデータ構造に正規化でき、下流では統一されたフィールドを参照できます。

// Set node: "Normalize Inputs"
name: `={{ $('Lookup by Email').item.json.name || $('Lookup by ID').item.json.full_name }}`
email: `={{ $('Lookup by Email').item.json.email || $('Lookup by ID').item.json.contact_email }}`

どちらの分岐がデータを出力したかにかかわらず、下流ノードは $('Normalize Inputs').item.json.name を参照します。

Setノードのアンチパターン

AIエージェントがよく生成するパターン:

Webhook → Set: { customer_id: $json.body.customer_id, amount: $json.body.amount }
       → Postgres: WHERE id = {{ $json.customer_id }}
       → Email: Total is {{ $json.amount }}

Setノードは役に立つ処理をしていません。後続の各ノードは、Webhookから直接読み取れます。

Webhook → Postgres: WHERE id = {{ $('Webhook').item.json.body.customer_id }}
       → Email: Total is {{ $('Webhook').item.json.body.amount }}

Setノードを使う意味があるのは、次の場合です。

  • 導出した値を複数の後続コンシューマーで使用する(導出が単純ではない)。
  • 導出ロジックが複雑で、名前を付けると読みやすくなる。
  • 複数の分岐で同じデータ構造が必要で、共通の上流参照にしたほうがすっきりする。
  • サブワークフローの最後のノードとして、戻り値の契約を整える。 明示的な例外です。「単一のコンシューマー」とはすべての呼び出し元を指すため、SetがAPIの境界になります。サブワークフローでは任意ですが、使用を推奨します。また、直前のノードにノイズとなるフィールドが含まれる場合は、必要になることもあります。n8n-subworkflows-officialを参照してください。
  • Include Other Fields: falseを設定して、アイテムからフィールドを削除する必要がある。 Setは出力のデータ構造をホワイトリスト化するのに最適な方法です。これは、上記のサブワークフローの戻り値を整える項目の根底にある仕組み(内部の一時フィールドが呼び出し元に漏れないようにすること)ですが、後続で整ったデータ構造が必要なあらゆる場面で使えます。
  • フィールド名を変更する必要がある。 Setを使うと、すべてのコンシューマーの式に分散させず、ひとつの場所で名前の変更を確認できます。

「リクエストボディからフィールドをひとつ取り出して一度だけ使う」場合は、Setノードを使いません。式は、そのフィールドを使用するノードに記述します。

「一度取り出して、複数の後続処理で使う」場合は、Setノードを使うのが妥当です。使用するコンシューマーがひとつだけなら、Setは負債です(上記の戻り値を整える場合を除きます)。

Setノードが必要かどうかをすばやく判断する

各フィールドを参照している後続ノードはいくつありますか?

  • 0個または1個 → 削除して、式をインライン化します。
  • 2個以上 → 特に処理が単純でない場合は、使う意味がある可能性があります。

Setノードが連続して複数ある場合は、ほぼ間違いなく抽出しすぎです。まとめてください。

式でできること

単一フィールドの変換
{{ $json.name.toUpperCase() }}
{{ $json.email.toLowerCase().trim() }}
{{ $json.items.length }}
{{ $json.user.first_name + ' ' + $json.user.last_name }}
{{ `(${$json.user.phone.slice(0, 3)}) ${$json.user.phone.slice(3, 6)}-${$json.user.phone.slice(6, 10)}` }}
メソッドチェーン:.map()、.filter()、.find()、.reduce()

配列メソッドは、式で使える最も便利なツールの一部です。数十個のノードの代わりになります。

{{ $json.tags.filter(tag => tag.active).map(tag => tag.name).join(', ') }}
{{ Object.values($json.scores).reduce((sum, score) => sum + score, 0) }}

// Find one matching item from another node's output
{{ $('Get Models').all().find(model => model.json.id === $json.modelId).json.modelName }}

// Filter array, then check shape
{{
  $('Get User\'s Entries').all()
    .map(item => item.json)
    .filter(entry => entry.prize_eligible === 'eligible')
    .length > 0
}}
複数ステップのチェーンには必ずインデントを付け、コメントを追加する

チェーンにメソッド呼び出しが2回以上ある場合や、フィルターロジックがひと目でわからない場合は、複数行に整形してコメントを付けます。読む人が作成者とは限らないため、コメントがあれば、技術者でない読者にも意図が伝わります。

{{
  // Find all entries that are still processing AFTER 1 hour
  // (used to allow re-submission since something likely went wrong)
  $('Get User\'s Entries').all()
    .map(item => item.json)
    .filter(entry =>
      entry.prize_eligible === 'processing' &&
      $now.diffTo(entry.created_at, 'minutes') > 60
    )
    .length > 0
}}

この種のロジックは、ルーティングノード(Switch、IF)でよく使われます。コメントがないと、ほとんどのユーザーには理解できません。

.all().map()を使うと「一度だけ実行するか」という検討が必要になります

$('Source Node').all().map(...)(または.filter()、.reduce())を使ってデータセット全体を処理する場合、式自体が反復処理を行います。ノードがデフォルトのアイテムごとの実行モードだと、入力アイテムごとに一度実行されますが、そのたびに.all()による集約全体を実行します。これは無駄な処理であり、誤った結果につながる可能性もあります。

次の場合はノードを一度だけ実行するように設定します。

  • 式で.all().map()/.all().filter()/.all().reduce()を使用している。
  • 出力がアイテムごとではなく、集約した単一の結果であるべき。

ノードではexecuteOnce: trueを設定します。ほとんどのノードでこの設定が使えます。

const aggregateNode = node({
    type: 'n8n-nodes-base.set',
    config: {
        executeOnce: true,            // important when using .all() in expressions
        parameters: {
            assignments: {
                assignments: [
                    {
                        name: 'totalEligible',
                        value: `={{
                            $('Get Entries').all()
                                .map(item => item.json)
                                .filter(entry => entry.eligible)
                                .length
                        }}`,
                        type: 'number',
                    },
                ],
            },
        },
    },
})

executeOnceの設定を忘れても動作することはよくありますが、アイテムがN個あると処理がN回実行されます。さらに、後続ノードがアイテムをひとつだけ想定している場合、N個のアイテムが渡されます。

反例:集約ではなく、アイテムごとの検索に.all()を使う場合。 .all()で別のノードを読み取り、現在のアイテムの識別情報でフィルタリングするなら、アイテムごとに実行する必要があります。反復ごとに異なる結果が得られるため、無駄な処理ではありません。

// Workflow: Get Tags (200 items) → Search Posts (10 items) → this Set Fields node.
// Each post carries a `tag_ids` array. Set Fields runs per-item (10 times)
// and resolves each post's tag_ids into the full tag objects.
tags: ={{
  $('Get Tags').all()
    .filter(tag => $('Search Posts').item.json.tag_ids.includes(tag.json.id))
}}

ここでexecuteOnce: trueを設定すると、10個の出力がひとつにまとめられてしまいます。

両者の違いは次のとおりです。

  • $source.all()のみ(データセット全体を集約する)→ executeOnce: true。
  • $source.all().filter(... matches $other.item.json.x)(現在のアイテムに対応する値を検索する)→ executeOnceをオフのままにします。

簡易チェック: 式で、別のノードの.itemと組み合わせずに.all()を使用していますか? その場合、ノードはexecuteOnce: trueにすべき可能性が高いです。

反復処理と明示的なループ処理について詳しくは、n8n-loops-officialスキルを参照してください。

条件式
{{ $json.status === 'active' ? 'Active' : 'Inactive' }}
{{ $json.amount >= 100 ? 'Large' : ($json.amount >= 10 ? 'Medium' : 'Small') }}
日付の計算(Luxon)
{{ DateTime.now().toISO() }}
{{ DateTime.fromISO($json.created_at).toFormat('yyyy-MM-dd') }}
{{ DateTime.now().minus({ days: 7 }).startOf('day').toISO() }}
{{ DateTime.fromISO($json.due).diffNow('days').days }}    // days from now (negative if past)
ノード間参照($json より推奨)
{{ $('Webhook Trigger').item.json.body.customer_id }}
{{ $('Lookup customer').item.json.email }}
{{ $('Combine Inputs').item.json.coupon_code }}    // NoOp convergence point

単一アイテムを扱うノードでは、.item と .first() はほぼ同じなので、どちらかを選んでください。.first() はより明示的で、.item はより簡潔です。

IIFE のアロー関数を使った複数行のロジック

1 行に収まらない複雑なロジックでも、単一アイテムを処理する場合は、即時実行アロー関数で囲みます。

{{ (() => {
    // Compute total including tax
    const items = $json.line_items
    const subtotal = items.reduce((sum, item) => sum + item.price * item.qty, 0)
    const tax = subtotal * 0.08
    return (subtotal + tax).toFixed(2)
})() }}

関数内では、式のスコープ全体($json、$('Node Name')、$now、Luxon)に加え、通常の関数で使える JavaScript(const/let、if/switch、try/catch、正規表現)も使えます。

引数は使えません。 式には引数を渡す呼び出し元がないため、(text) => text.replace(...) に値を渡して実行する方法がありません。外側のスコープから値を直接参照してください。関数を実際に実行するには、IIFE の形式((...)())で囲む必要があります。

{{ (() => $json.text.replace(/\b(?:foo|bar)\b/gi, 'baz'))() }}

先頭の ( と末尾の )() は必須です。最初の括弧の組で関数式を囲み、末尾の () で呼び出します。どちらかを省略すると n8n はエラーになり、ワークフローを実行できません。

Code ノードではなく、こちらを使う理由は? Code ノードはサンドボックス化された VM で実行されるため、最悪の場合は約 500~1000 ms かかります。式の IIFE は周囲の式と同じコンテキストで実行され、安定して 1~10 ms です。単一アイテムの整形だけであれば、機能上の違いはなく、速度に 100 倍の差があります。これは熟練ユーザーによく使われる方法です。

複数アイテムの集約($input.all())、外部ライブラリ、非同期処理には、引き続き Code ノードが適しています。判断フローについては n8n-code-nodes-official を、より詳しい例と書式ルールについては n8n-code-nodes-official ARROW_FUNCTIONS_IN_EDIT_FIELDS.md を参照してください。

利用できる標準 JavaScript

String、Array、Number、Object、Map、Set、JSON.parse、JSON.stringify、Math、正規表現、Date(ただし、Luxon のみ使用してください)。

便利なイディオム

フィールドがない場合のデフォルト値
{{ $json.id || "fallback-id-here" }}

または、オプショナルチェーンを使います。

{{ $json.user?.profile?.id ?? "anonymous" }}

クエリに渡すフィルター値には特に便利です。undefined によるクエリの失敗を避け、行に一致しないデフォルト値を渡せます。

テキストフィールドに JSON を埋め込む場合:シリアライザーの選び方

シリアライザーは 2 種類あり、用途が異なります。

  • .toJsonString() は、書式が重要でないコンパクトな JSON に使います。代表的な用途は AI プロンプト です。サイズが小さく、トークンを節約でき、プロンプトテンプレートでも読み取りやすくなります。
    {{ $('Get Data').item.json.toJsonString() }}
    
  • JSON.stringify(value, null, 2) は、書式が重要な整形済み JSON に使います。代表的な用途は メール本文、Slack メッセージ、デバッグ出力 など、人が結果を読む場面です。
    {{ JSON.stringify($('Source Node').item.json, null, 2) }}
    

用途に応じて選んでください。LLM のプロンプト内での整形出力はトークンを無駄にし、モデルのコンテキストを clutter で埋めてしまいます。メール内のコンパクトな JSON は読みにくくなります。

JSON.stringify と JSON.parse:使うべき場所

JSON.stringify と JSON.parse は式でよく使われます。どちらも問題ありません。重要な原則は、文字列化と解析はストレージ層の操作であり、インターフェース層の操作ではないということです。

  • 型をネイティブに保持できないストレージ列に書き込むときは、文字列化します。 典型的な例は、実際には配列またはオブジェクトを格納している、Data Tables の _object が末尾につく文字列列です。n8n-data-tables-official を参照してください。
  • そのストレージ列から読み出すときは、解析します。 ストレージを所有するワークフローの内部で行います。
  • 文字列化した形を境界の先まで伝播させないでください。 サブワークフローの戻り値、Webhook のレスポンス、エージェントのツール結果、後続のコンシューマーには、文字列化された殻(呼び出し元が JSON.parse する必要がある形)ではなく、自然な形(配列は配列、オブジェクトはオブジェクト)で渡す必要があります。

よくあるミスは、サブワークフローに「新規」パス(LLM が生成したばかりのデータで、すでに配列になっている)と、「キャッシュ」パス(_object 列から読み出したばかりのデータで、まだ文字列になっている)があるケースです。誤った対応は、キャッシュの形に「合わせる」ために新規パスを文字列化することです。正しい対応は、キャッシュの形を解析して、両方の分岐が出力時に同じ自然な形になるようにすることです。

ストレージ表現は、ストレージを所有するワークフローの内部に置きます。その境界の外では、自然な形でやり取りします。n8n-subworkflows-official の SKILL.md にある「Return natural shapes, not storage shapes」ではサブワークフロー側からこの点を説明しており、n8n-data-tables-official ではストレージ側から説明しています。

適切な型を返す:={{ ... }} で囲む場合

一部のノードフィールドでは、n8n に式として評価させるよう指定しない限り、値が文字列リテラルとして扱われます。={{ ... }} で囲むと(= の接頭辞によってフィールドが式モードになり)、内側のコードが生成する実際の型が返されます。

// String literal (default behavior)
foo: 'plain string'

// Number
foo: '={{ 100 }}'

// Boolean
foo: '={{ true }}'

// Object (the `={{ ... }}` is what makes the receiver see an object, not a string)
foo: '={{ { "valid": true, "items": [] } }}'

// Array
foo: '={{ ["a", "b", "c"] }}'

// Reference to another node's value (preserves whatever type that value already is)
foo: '={{ $("Source Node").item.json.payload }}'

型が重要になるのは、Set / Edit Fields のオブジェクト/配列フィールド(列の Type を Object または Array に設定)、HTTP Request の JSON ボディパラメーター、サブワークフローの型付き workflowInputs.values[type] への構造化入力、エージェントのツールパラメーターなど、受け取り側のノードが型を検証する箇所です。={{ ... }} で囲まないと、文字列が渡され、受け取り側で型変換されるか、エラーになります。

上記の譲れないルール #1 に従い、$json ではなくノード名で参照します。

// WRONG
foo: '={{ $json.payload }}'

// RIGHT
foo: '={{ $("Source Node").item.json.payload }}'

例外は、$json が本当に適切な場合(中間変換も合流もない場合)で、そのフィールドが1つのソースの直後にあるノードのアイテムごとのスロットである場合です。その場合でも、名前付き参照の方がリファクタリングに強くなります。

説明コメント付きの複数行の式
{{
  // Default to avoid query errors when user_id is missing.
  // The fallback UUID is a known-empty row.
  $json.id || "305f7106-6988-4651-b26a-18979641b7b5"
}}

ロジックが自明でない場合は推奨されます。 次に読む人のためにコメントを残せます。

式でできないこと

  • 外部ライブラリの使用(require は使用できません)。
  • 非同期処理(async / await)。

$json 自体が参照できるのは現在のアイテムだけですが、式では $input.all()、$input.all()[3]、$('Source Node').all() などを使ってアイテムをまたいで参照できます。上記の「メソッドチェーン」を参照してください。

詳しくは n8n-code-nodes-official を参照してください。

判断:式、Edit Fields、Code ノードのどれを使うか?

n8n-code-nodes-official の判断ツリーに従います。

1. Single-field transform → expression in the field
2. Multi-step pure logic on one item → arrow function in Edit Fields
3. Multi-source aggregation, libraries, or stateful → Code node

式がデフォルトです。入力やスコープの要件がある場合に限り、それ以外を使います。

「余分なノード」の匂い

式の中にとどめるべき、余分なノードを追加しがちなケース:

追加するノード代わりに使う方法
日付の書式設定に使う DateTime ノード使用側の式で DateTime.fromISO(...).toFormat(...) を使う
メール本文の作成に使う Set ノードメールノードの本文フィールドに式を直接記述する
一度しか使わない派生フィールドの計算に使う Set ノード使用側に式を直接記述する
計算してから判定するための 2 つのノード(Set + IF)条件式で計算する 1 つの IF
.toUpperCase() の呼び出しに使う Code ノード式だけを使う

変換のためにノードを追加すると、見た目が煩雑になり、ワークフローの実行が遅くなり、読みづらくなります。

余分なノードが適切な場合:

  • 変換が複数の下流の使用箇所で再利用される。
  • 変換が高負荷である(Code ノードを使うべき)。
  • 変換がセクションの主な目的である(「X を計算する」という明確なステップ)。

アンチパターン

アンチパターン問題点修正方法
Webhook の本文から 1 つのフィールドを取り出し、下流の 1 か所で使うためだけに存在する Set ノードインライン化すべき処理にノードを追加しており、リファクタリングにも弱いSet ノードを削除し、使用側で $('Webhook').item.json.body.x を直接参照する
それぞれ 1 つのフィールドを定義する Set ノードが連続しているワークフローの水増しまとめる。ほとんどは不要で、必要な場合も 1 つの Set ノードにまとめる
複数の分岐と中間変換があるワークフローの深い位置で $json.x を使う中間ノードの追加やコンテキストのクリアで参照が壊れる$('Source Node').item.json.x を使う。分岐が合流する場合は NoOp の合流ポイントを追加する
タイムスタンプの書式設定に DateTime ノードを追加する1 行の Luxon 式で済む処理にノードを追加している{{ DateTime.fromISO($('Source').item.json.x).toFormat('yyyy-MM-dd') }}
メールの HTML を作成してから Email ノードで読み込む Set ノード1 つの式で済む処理に 2 つのノードを使っているHTML をメールノードの本文フィールドに直接記述する
Luxon の代わりに new Date($json.created_at) を使う書式設定や操作の機能が失われるDateTime.fromISO($('Source').item.json.created_at)
実際には 200 文字ある 1 行の式読みにくいアロー関数を使い、インデントとコメントを加えて複数行にする
$json.foo が存在するか確認せずに $json.foo.bar.baz を使う中間の値がない場合にエラーになる?. チェーンを使う:$('Source').item.json.foo?.bar?.baz
設定にすべき値を式にハードコードするマジックストリングになる$vars.X(n8n Variables、有料プラン)または Data Table を使う
分岐の合流後に $json で参照する最後に実行された分岐が勝つため、結果が非決定的になる合流地点に NoOp(「Combine Inputs」)を挿入し、名前で参照する
あらゆる式で $env.X を使う動作せず、実行時にエラーになる設定には $vars.X(有料プラン)または Data Table を使う。シークレットには認証情報システムを使う

コメント