カスタムトリガー
組み込みのトリガーは、ほとんどの店舗が重視する変更を網羅しています。残りの変更については、カスタムトリガーで対応できます。Shopifyのイベントを選択し、それが重要かどうか、および何を送信するかを判断するためのJavaScriptを数行記述するだけで、Shopify Flowで利用できるトリガーとして機能します。
組み込みのトリガーでは解決できない、以下の3つの課題を解決します:
- 指定した条件が満たされた場合にのみ実行します。「500ユーロ以上の注文にのみ『
vip』タグを付与する」という処理は、すべての注文に対してワークフローを実行してからフィルタリングするのではなく、たった1行のコードで実現できます。 - 状態ではなく、遷移の時点で判定を行ってください。コードでは変更前後の値を確認できるため、「ステータスが『下書き』から『アクティブ』に変わった」や「在庫が5を下回った」といった表現が可能です。現在の値に基づく条件では、そのようなことを表現できません。
- **必要なフィールドだけを正確に送信してください。**イベントデータを、一つずつ読み出すのではなく、ワークフローで実際に使用する値に合わせて再構成してください。
カスタムトリガーには、Shopifyイベントさえ必要ありません。スケジュールに従って実行され、何が変更されたかを独自に判断することも可能です。
仕組み
- 実行方法を選択してください:Shopifyイベントを1回実行するか、スケジュールに従って実行するかです。
- イベントについては、アプリがすでに受信しているShopifyのイベントの中から、リッスンするイベントを選択するか、テンプレートから開始して、テンプレートがイベントを選択し、コードを自動生成するようにします。
- **実際のペイロードを取得します。**ストアでその変更を加えると、アプリが正確なイベント本文を取得します。
- トランスフォームを作成する - 実行する場合はオブジェクトを返し、スキップする場合は
nullを返す。 - キャプチャされたイベントに対してテストを行い、ワークフローが実際にどのようなデータを受け取るかを確認してください。
- 電源を入れてください。

変換の記述
トリガーは、transform という名前の関数をエクスポートする JavaScript モジュールです。この関数は 4 つの引数を受け取ります:
payload- 受信したままの、Shopifyイベントの本文。topic- どのイベントが発生したか(例:PRODUCTS_UPDATE)。shop- あなたの myshopify ドメイン。ctx-ctx.log(...)はエディタの横にあるログパネルに出力し、ctx.shopify(...)は管理用 GraphQL クエリを実行し、ctx.fetch(...)はパブリックインターネット上の URL を呼び出します。
何を返すかによって、その後の展開が決まります:
- オブジェクトを返すと、そのオブジェクトを引数としてトリガーが発火します。
nullを返すと、そのイベントはスキップされます。フィルタリングはこのように機能します。別途、フィルタリング用の言語を学ぶ必要はありません。- ファイルを空のままにしておくと、そのタイプのイベントが発生するたびに処理が実行されます。
/**
* Only fire for high-value orders carrying the vip tag.
*/
export async function transform(payload, topic, shop, ctx) {
const total = parseFloat(payload.total_price || "0");
const tags = (payload.tags || "").split(",").map(t => t.trim());
if (total < 500) return null;
if (!tags.includes("vip")) return null;
ctx.log("firing for", payload.name, total);
return {
orderId: payload.admin_graphql_api_id,
orderNumber: payload.name,
total,
currency: payload.currency,
customerEmail: payload.email,
};
}変更前の値
Shopify
「Product Update」、「Order Update」、「Customer Update」では、payload._changes に、この更新によって変更された追跡対象フィールドがすべて一覧表示され、それぞれに oldValue および newValue が記載されています:
- 製品:
title、handle、description、status、vendor、productType、tags - 注文:
financialStatus、fulfillmentStatus、tags、note、lineItems、およびcustomAttributes.<name> - 顧客:
tags、note、state(ENABLED、DISABLED、INVITED、DECLINED)
更新対象がこれらのフィールド以外の場合(たとえば、在庫の変更など)や、アプリがそのレコードを初めて参照する場合、リストは空になります。エディタでキャプチャしたサンプルイベントにはそのフィールドが表示されるため、書き込みを行う前に実際の構造を確認することができます。
/**
* Fire only when a product BECOMES active - not on every later edit of an
* active product, which is what a condition on the current status would do.
*/
export async function transform(payload, topic, shop, ctx) {
const change = (payload._changes || []).find((c) => c.field === "status");
if (!change) return null;
if (change.oldValue !== "draft" || change.newValue !== "active") return null;
return {
productId: payload.admin_graphql_api_id,
title: payload.title,
oldStatus: change.oldValue,
newStatus: change.newValue,
};
}より具体的なイベントには、それぞれ固有の「old」と「new」の値が設定されているため、その場合は_changesを指定する必要はありません。価格の変更にはoldPrice、newPrice、percentChangeがあり、在庫の変更には_oldAvailable、_newAvailable、_deltaがあり、メタフィールドの変更にはmetafield.valueのほかにpreviousValueがあります。
テンプレートから始める
エディタで「Fires on」を選択すると、**「テンプレートから開始」**機能によってイベントが選択され、動作するコードが入力されます。上部の定数を編集し、テストしてから保存してください。各テンプレートでは、変更前後の値が読み込まれます:
- 製品、注文、または顧客のフィールドの値が変更された――ステータスが「下書き」から「有効」に変わった、注文が「支払い済み」になった、アカウントが「有効」になった。
- 価格がN%以上値下がりしました。これは単なる価格の修正ではなく、本格的な値下げです。
- 在庫が閾値を下回った場合、一度だけアラームが鳴る。これは在庫がそのラインを越えた瞬間に発生するもので、在庫がすでに少ない状態にある間、販売のたびにアラームが鳴るわけではない。
- 製品のメタフィールドの数値が閾値を超えました。評価が3を下回り、マージンが40を上回りました。
- 「高額顧客による注文」 - お客様の当店舗での累計購入額を読み取り、設定した金額を超える場合にのみトリガーされます。

ctx.shopify を使用して追加データを取得する
Webhookの本文には、Shopifyが送信するフィールドのみが含まれます。それ以外の情報(顧客の注文数、バリエーションの在庫数、メタフィールドなど)が必要な場合は、トランスフォームから直接Admin APIにクエリを実行してください:
export async function transform(payload, topic, shop, ctx) {
const data = await ctx.shopify(`
query($id: ID!) {
customer(id: $id) { numberOfOrders tags }
}
`, { id: payload.customer.admin_graphql_api_id });
// Only fire for repeat customers
if (data.customer.numberOfOrders < 5) return null;
return {
orderId: payload.admin_graphql_api_id,
orderCount: data.customer.numberOfOrders,
};
}クエリのdataを返します。また、クエリにエラーがある場合は例外をスローするため、何も返さずに黙って処理が終わってしまうのではなく、テスト中にエラーが明確に表示されます。
知っておくべき3つの制限:
- **1回の実行につき最大10件の呼び出し。**データ補完には数件で十分であり、それ以上になると通常はループが必要になります。可能な場合は、1回のクエリで必要なデータを取得するようにしてください。
- **このアプリは読み取り専用であり、書き込みは行いません。**ユーザーが許可した権限のみを使用し、アプリが要求するのは常に読み取りアクセス権のみです。「権限」ページで「注文データへのアクセス」が許可されていない場合、注文データのクエリは失敗します。ストアの設定を変更するには、トリガーに続く「フローアクション」で行ってください。
- **お客様のストアの認証情報は、コードには一切渡されません。**クエリはアプリがお客様に代わって実行するため、サンドボックス内で漏洩する可能性のあるアクセス トークンは存在しません。
ctx.fetch(url, options) パブリックインターネット上のあらゆるもの(サプライヤーの在庫フィード、為替レート、独自のAPIなど)に対して、ブラウザの「fetch」と同様に機能します。内部ネットワークやプライベートネットワークのアドレスは拒否されます。トリガーコードをGitHubに保存する場合は、その中にAPIキーを含めないでください。
スケジュールに従って実行されるトリガー
Shopify Flow 何かが起こったときに反応します。何も起こらない場合は反応せず、また、お店の外の状況は把握できません。そのため、「実行条件」で「スケジュールに従って」を選択してください。このトリガーには、Shopifyというイベントは関連付けられていません。コードは30秒ごとから1日1回までの間隔で実行され、何が変更とみなされるかをコード自身が判断します。
これは、transform関数と同じですが、payloadが異なり、戻り値も異なります:
payload.state- 前回の実行時に、コードがstateとして返した値。初回実行時はnull。payload.nowおよびpayload.lastRunAt- タイムスタンプ。{ state, events }を返します。これは、記憶すべき新しい状態(最大 32 KB)と、発生させるイベントのリスト(1回の実行につき最大 100 件)です。各イベントは Custom Trigger を 1 回だけ発生させます。イベントのresourceId(設定されている場合)は、Flow におけるレコード ID となります。報告すべき内容がない場合は、nullを返します。
**最初の実行では、比較のみを行い、決してトリガーを起動しないようにしてください。**最初の実行時、コードには比較対象となるものが何もないため、すべてが新しい状態に見えます。確認した内容を保存し、イベントを一切返さないようにすることで、トリガーをオンにしてもワークフローがイベントで溢れかえることはありません。すべてのテンプレートでこの処理が行われています。

// Fires when a value at a JSON URL changes, with the value before and after.
const URL = "https://api.example.com/stock/1042"
export async function transform(payload, topic, shop, ctx) {
const res = await ctx.fetch(URL, { headers: { accept: "application/json" } })
if (!res.ok) throw new Error("The URL answered with HTTP " + res.status)
const value = String((await res.json()).stock)
// The first run only remembers the value.
const previous = payload.state ? payload.state.value : undefined
if (previous === undefined || previous === value) return { state: { value } }
return {
state: { value },
events: [{ oldValue: previous, newValue: value }],
}
}「スケジュール」カードの**「テンプレートから開始」**にある、スケジュールされたトリガー用のテンプレート:
- N日間更新されていない商品、注文、または顧客 - 古くなったカタログのレビュー、停滞している注文、顧客の再獲得。各レコードは「静止期間」ごとに1回トリガーされ、機能を有効にした時点で既に期限切れとなっているものについては、一度にまとめてトリガーされることはありません。
- Shopify 以外の値が変更されました。具体的には、サプライヤーの在庫データ、為替レート、価格表などであり、数値については差額とパーセンテージが変更されました。
- RSS または Atom フィードの新規項目 - サプライヤーのニュース、商品リストフィード、ステータスページなど。
- N時間注文がない場合 - あなたの店舗の「鼓動」です。注文が途絶えたときと、注文が再開したときにそれぞれ1回ずつ発火します。
**Testは、**何も実行せず、状態も保存せずにコードを1回実行するため、何度でも実行することができます。
Shopify Flow での使用
すべてのカスタムトリガーは、Flow では「カスタムトリガー」という同じトリガーとして表示されます。これをワークフローに追加し、次に「トリガーハンドルが、ご自身のトリガーのハンドルと等しい」という条件を追加してください。
そのハンドル名はトリガーのページに表示され、トリガーの名前を変更しても決して変わらないため、ワークフローは引き続き正常に動作します。
transformが返す各キーは、トリガーのフィールドとなります。また、自分で解析したい場合は、オブジェクト全体をJSON形式でも利用できます。
コードはGitHubに保存しておいてください
GitHubリポジトリを連携させることで、トリガーコードをバージョン管理下に置くことができます。変更内容はプルリクエストとしてレビュー可能になり、誰が何を変更したかを確認できるほか、正常に動作しなくなった変換をロールバックすることも可能です。
「Developer」ページの「Connections」で接続を設定してください。アプリがアクセスできるリポジトリを選択でき、GitHub からいつでもそのアクセス権を取り消すことができます。
リポジトリが選択されると、既存のトリガーはすべて直ちにそのリポジトリに書き込まれ、双方向で同期が維持されます:
| 担当業務 | どうなるのか |
|---|---|
| アプリ内でトリガーを作成、編集、または複製する | ファイルがリポジトリにコミットされました |
| アプリ内のトリガーを削除する | ファイルがリポジトリから削除されました |
| 接続されているブランチに変更をプッシュする | アプリ内でトリガーのコードが更新されます |
各トリガーは、そのハンドル名をファイル名とした1つのファイルであり、エディタに表示されているモジュールそのものが含まれています。それ以外の要素は一切含まれていません。つまり、他のJavaScriptファイルと同様に、独自のエディタで開いて実行し、リンティングを行うことができます。
以前のバージョンに戻す
変更を元に戻すのに、Gitの知識は必要ありません。リポジトリが接続されると、エディタには「バージョン」ドロップダウンが表示され、そのファイルの過去のすべてのバージョンが日付や作成者とともに一覧表示されます。その中から1つを選択すると、未保存の変更としてエディタに読み込まれるため、まず内容を確認することができます。保存を行うことで、そのバージョンが元に戻ります。
ローカルで編集しながらテストする
独自のエディタでファイルを編集している場合は、アプリに一旦保存することなく、キャプチャされたサンプルイベントに対してそのファイルを実行することができます。開発者ページから「**実行」**権限を持つAPIキーを使用してください:
curl -X POST https://shopify.workflow-trigger-extensions.app/api/v1/triggers/custom/high-value-vip-order/test \
-H "Authorization: Bearer ftk_your_key_here" \
-H "Content-Type: application/json" \
-d "$(jq -Rn --rawfile c flow-triggers/high-value-vip-order.js '{code:$c}')"アプリ上の「Test」ボタンに表示されるのと同じ結果が返されます。具体的には、イベントが発火するかどうか、出力オブジェクト、ctx.logのコード行、および実行時間です。
このエンドポイントでは何もトリガーされず、何も保存されず、プランの割り当ても一切使用しないため、ファイルウォッチャーによる保存のたびに安全に実行できます。(アプリ内の「テストを実行」ボタンを押すと、Flowワークフローがトリガーされるため、ワークフローの全工程の実行状況を確認できます。これも無料です。)
また、読み取り用キーを使用してサンプルペイロードのみを取得し、ローカルに保存して、完全にオフラインでファイルを実行することもできます:
curl https://shopify.workflow-trigger-extensions.app/api/v1/triggers/custom/high-value-vip-order/test \
-H "Authorization: Bearer ftk_your_key_here"カスタムトリガーは、私のプランの割り当て量を使用しますか?▾
はい、そして、トリガーは自身が発火するイベントだけでなく、監視対象となるすべてのイベントをカウントします。たとえば、トリガーが「Product Update」イベントを監視しており、ストアで月に 60,000 件の商品更新がある場合、たとえコードがそのうち 100 件に対してのみ発火したとしても、60,000 件のイベントとしてカウントされます。
これらのイベントをそれぞれ受信し、重複を除去してキューに格納した後、隔離されたサンドボックス内でコードを実行します。これらの一連の処理は、コードがイベントを発火させるかどうかを決定する前にすべて行われます。カウント数は、その処理量を反映したものです。
言い換えれば、同じイベントに対する組み込みトリガーと同じコストがかかります。フィルタリングによる追加料金は発生せず、テストは常に無料です。スケジュールされたトリガーは、1回の実行につき1回としてカウントされます。
テストは無料ですか?▾
はい。アプリ内の「テストを実行」ボタンも、/testのAPIエンドポイントも、いずれも利用枠の対象外となります。これは、「テストを実行」が実際にFlowワークフローを起動し、その実行状況を確認できるようにしているにもかかわらずです。利用枠が消費されるのはライブ実行のみであるため、変換処理については何度でも試行することができます。
payload._changes が空なのはなぜですか?▾
更新内容が追跡対象のフィールドの範囲外だった場合(例えば、商品の在庫やバリエーションの変更など)、あるいはそのレコードについてアプリにまだベースラインが登録されていない場合です。ベースラインは、アプリがレコードを初めて認識した際に保存されるため、アプリがこれまで認識したことのないレコードの最初の更新には、以前の値が含まれません。それ以降の更新にはすべて以前の値が含まれます。
私のコードで、ストア内のデータを変更することはできますか?▾
いいえ。ctx.shopifyは、ユーザーが許可した読み取り権限で実行され、アプリが書き込みアクセスを要求することは一切ありません。これは意図的な仕様です。監視対象のレコードを編集するトリガーは、自身を再起動してしまうため、これがShopifyがワークフローを無効にする原因となるループを引き起こします。トリガーの後に続く「フローアクション」内でデータを変更してください。
後でハンドルを交換することはできますか?▾
いいえ、それは意図的な仕様です。Flowのワークフローはハンドル名でフィルタリングされるため、ハンドル名を変更すると、そのワークフローの実行が黙って停止してしまいます。トリガー名は自由に変更できますが、ハンドル名はそのまま維持されます。
このハンドルは GitHub リポジトリ内のファイル名でもあるため、これも決して移動することはありません。
自分のコードにバグがあった場合はどうなりますか?▾
そのイベントはスキップされ、トリガーにエラーが記録されるため、何が問題だったかを確認できます。変換が失敗しても、他の処理がブロックされることは決してありません。他のトリガー(カスタムトリガーや組み込みトリガーを問わず)は、影響を受けることなく正常に動作し続けます。
私のコードはどこで実行されるのでしょうか?▾
アプリの他の部分とは切り離された独立したサンドボックス内で、時間制限が短く、ストアの認証情報にはアクセスできません。このサンドボックスが認識するのは、ユーザーがキャプチャしたイベントのペイロードと、ctx.shopify または ctx.fetch を使用して取得した情報のみです。
GitHubとアプリの両方で同時にファイルを編集したらどうなるのでしょうか?▾
最後に保存した方が優先されます。アプリ内で保存するとファイルへの変更が反映され、接続されたブランチへプッシュすると、アプリ内に保持されているコードが上書きされます。主にリポジトリで作業する場合は、予期せぬ事態を避けるため、アプリのエディタを読み取り専用として扱ってください。
リポジトリ内のファイルによって、新しいトリガーが生成されることはありますか?▾
いいえ。トリガーは、どのShopifyイベントをリッスンするかも把握しておく必要がありますが、ファイルにはコードしか含まれていません。イベントを推測して設定すると、誤った対象に紐付けられてしまいます。まずアプリ内でトリガーを作成し、その後でそのファイルを自由に編集してください。
アプリがまだ受信していないイベントに対して、発火することは可能ですか?▾
いいえ。カスタムトリガーは、アプリがすでにストアに対してサブスクライブしているイベントを監視します。これは、ユーザーが許可した権限によって異なります。リソースに対する権限を許可すると、そのリソースのイベントもカスタムトリガーで利用できるようになります。それ以外の場合は、スケジュールに基づいて実行してください。
今後の手順
- 開発者向けAPI、MCP、GitHub、およびトリガーシミュレーション - 自身のコードやAIアシスタントからトリガーを管理・テストする。
- AIを使ってトリガーコードを生成する - トリガーをわかりやすい言葉で説明し、AIにコードを書いてもらう。
- プランと利用方法 - どのような事象が対象となるのか、また手当はどのように支給されるのか。
- トリガーの仕組み - 組み込みのトリガーとその発動方法。

