開発者カスタムトリガー

カスタムトリガー

ページをコピーする

任意のイベントから、カスタムShopify Flowトリガーを作成し、トリガー発動前後の値やJavaScriptによる変換を設定することで、ご指定の条件にぴったり合うタイミングでトリガーを発動させることができます。

組み込みのトリガーは、ほとんどのストアが重視する変更を網羅しています。残りの変更については、カスタムトリガーで対応できます。Shopifyのイベントを選択し、それが重要かどうか、およびどのようなデータを送信するかを判断するためのJavaScriptを数行記述するだけで、Shopify Flowで利用できるトリガーとして機能します。

組み込みのトリガーでは解決できない、以下の3つの課題を解決します:

  • ご指定の条件にのみ発火するようにしてください。「500ユーロを超える注文にのみ『vip』タグが付与される場合」という条件は、すべての注文に対してワークフローを実行してからフィルタリングするのではなく、1行のコードで実現できます。
  • 状態ではなく、遷移の時点で判定を行ってください。コードでは変更前後の値を確認できるため、「ステータスが『下書き』から『アクティブ』に変わった」や「在庫が5を下回った」といった表現が可能です。現在の値に基づく条件では、そのようなことを表現することはできません。
  • **必要なフィールドのみを送信してください。**イベントを、一つずつ読み出すのではなく、ワークフローで実際に使用する値に合わせて再構成してください。

カスタムトリガーには、Shopify イベントさえ必要ありません。スケジュールに従って実行され、何が変更されたかを自ら判断することも可能です。

仕組みについて

  1. 実行のトリガーとして、Shopifyのイベント1つ、またはスケジュールをお選びください。
  2. イベントについては、アプリがすでに受信しているShopifyのイベントの中から、対象となるイベントを選択するか、テンプレートから作成して、イベントの選択とコードの入力を行ってください。
  3. **実際のペイロードを取得します。**ストアでその変更を行うと、アプリが正確なイベント本文を取得します。
  4. 変換関数を記述してください。実行する場合はオブジェクトを返し、スキップする場合はnullを返してください。
  5. キャプチャされたイベントに対してテストを行い、ワークフローが実際にどのような結果を受け取るかを確認してください。
  6. 電源を入れてください。
名前、ハンドル、Shopifyイベントかスケジュールかの選択、イベント、およびテンプレートを設定できるカスタムトリガーエディタ
新しいカスタムトリガー:トリガーが実行される条件を選択し、イベントを指定し、必要に応じてテンプレートから作成できます。

変換式の記述

このトリガーは、transform という名前の関数をエクスポートする JavaScript モジュールです。この関数は 4 つの引数を受け取ります:

  • payload - 受信されたままの、Shopifyイベントのボディです。
  • topic - どのイベントが発生したか、例えば「PRODUCTS_UPDATE」など。
  • shop - あなたの myshopify ドメインです。
  • ctx - ctx.log(...) はエディタの横にあるログパネルに出力され、ctx.shopify(...) は管理用 GraphQL クエリを実行し、ctx.fetch(...) はパブリックインターネット上の URL を呼び出します。

何を返すかによって、その後の展開が決まります:

  • オブジェクトを返すと、そのオブジェクトを引数としてトリガーが発火します。
  • nullを返すと、そのイベントはスキップされます。これがフィルタリングの仕組みです。別途、フィルタ言語を学ぶ必要はありません。
  • このファイルを空のままにしておくと、そのタイプのイベントが発生するたびに処理が実行されます。
flow-triggers/high-value-vip-order.jsjavascript
/**
 * 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)

更新対象がこれらのフィールド以外の場合(例えば、在庫の変更など)や、アプリが当該レコードを初めて参照する場合、リストは空になります。エディタで取得できるサンプルイベントにはそのフィールドが表示されるため、書き込みを行う前に実際の構造を確認することができます。

flow-triggers/product-went-live.jsjavascript
/**
 * 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,
  };
}

より具体的なイベントには、それぞれ固有の新旧の値が存在するため、その場合は_changesを指定する必要はありません。価格の変更には、oldPrice、newPrice、percentChangeがあり、在庫の変更には、_oldAvailable、_newAvailable、_deltaがあり、メタフィールドの変更には、metafield.valueに加え、previousValueがあります。

テンプレートから始める

エディタで「Fires on」を選択すると、**「テンプレートから開始」**機能により、イベントが選択され、動作するコードが入力されます。上部の定数を編集し、テストを行ってから保存してください。各テンプレートでは、変更前後の値が読み込まれます:

  • 製品、注文、またはお客様のフィールドの値が変更されました。具体的には、ステータスが「下書き」から「有効」に変わったり、注文が「支払い済み」になったり、アカウントが「有効」になったりしました。
  • 価格がN%以上値下がりしました。これは単なる価格の修正ではなく、本格的な値下げです。
  • 在庫が閾値を下回りました。在庫がそのラインを越えた瞬間に1回だけ発火し、在庫がすでに少ない状態での販売のたびに発火するわけではありません。
  • 商品のメタフィールドの数値が閾値を超えました。具体的には、評価が3を下回り、マージンが40を上回りました。
  • 「高額顧客による注文」――お客様の当ストアでの累計購入額を読み取り、設定した金額以上の場合にのみトリガーされます。
ペイロード._changes と記述されたテンプレートのコードを含むエディタの「Transform」カード
テンプレートには、動作するコードが入力されています。上部の定数を編集し、動作を確認してから保存してください。

ctx.shopify を使用して追加データを取得する

Webhookの本文には、Shopifyが送信するフィールドのみが含まれます。それ以外の情報(お客様の注文数、バリエーションの在庫数、メタフィールドなど)が必要な場合は、トランスフォームから直接Admin APIにクエリを実行してください:

Enrich the event before decidingjavascript
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回のクエリで必要なデータを取得するようにしてください。
  • **このアプリは読み取り専用であり、書き込みは行いません。**ユーザーが許可した権限を使用し、アプリが要求するのは常に読み取りアクセス権のみです。「権限」ページで「注文データへのアクセス」が許可されていない場合、注文データの照会には失敗します。ストアの設定を変更するには、トリガーに続く「Shopify Flow」アクションで行ってください。
  • **お客様のストアの認証情報は、コードには一切渡されません。**クエリはアプリがお客様に代わって実行するため、サンドボックス内にアクセス トークンが存在せず、漏洩する心配もありません。

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 件)です。各イベントは カスタムトリガー を 1 回だけ発生させます。イベントの resourceId(設定されている場合)は、Shopify Flow におけるレコード ID となります。報告すべき内容がない場合は、null を返してください。

**最初の実行時には、比較のみを行い、決してトリガーを起動しないようにしてください。**最初の実行時、コードには比較対象となるものが何もないため、すべてが新しい状態に見えます。表示された内容を保存し、イベントを返さないようにしてください。そうすれば、トリガーをオンにしてもワークフローがイベントで溢れかえることはありません。すべてのテンプレートでこの処理が行われています。

「スケジュールに従って」モードのカスタムトリガーエディタで、間隔と30日間の実行回数を設定します
スケジュールに基づいて:間隔を選択すると(エディタに合計実行回数が表示されます)、スケジュール済みテンプレートから開始します。
flow-triggers/supplier-stock-changed.jsjavascript
// 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 での使用方法

すべてのカスタムトリガーは、Shopify Flow では「カスタムトリガー」という同じトリガーとして表示されます。ワークフローに追加した後、「**トリガーハンドルが、**ご自身のトリガーのハンドルと等しい」という条件を追加してください。

そのハンドル名はトリガーのページに表示され、トリガーの名前を変更しても決して変わりません。そのため、ワークフローは引き続き正常に動作します。

トランスフォームが返す各キーは、トリガーのフィールドとなります。また、ご自身で解析したい場合は、オブジェクト全体をJSON形式でも利用可能です。

コードはGitHubに保存しておいてください

GitHubリポジトリを連携させることで、トリガーコードをバージョン管理下に置くことができます。変更内容はプルリクエストとしてレビュー可能になり、誰が何を変更したかを確認できるほか、正常に動作しなくなった変換をロールバックすることも可能です。

「Developer」ページの「Connections」で接続してください。アプリがアクセスできるリポジトリを選択でき、GitHub からいつでもそのアクセス権を取り消すことができます。

リポジトリが選択されると、既存のトリガーはすべて直ちにそのリポジトリに書き込まれ、双方向で同期が保たれます:

担当業務 どうなるのでしょうか
アプリ内でトリガーを作成、編集、または複製します ファイルがリポジトリにコミットされました
アプリ内のトリガーを削除する そのファイルはリポジトリから削除されました
接続されているブランチに変更をプッシュする アプリ内でトリガーのコードが更新されます

各トリガーは、そのハンドル名をファイル名とした1つのファイルであり、エディタに表示されているモジュールそのものが含まれています。それ以外に何も追加されていません。つまり、他のJavaScriptファイルと同様に、ご自身のエディタで開いて実行し、リンティングを行うことができます。

以前のバージョンに戻す

変更を元に戻すために、Gitの知識は必要ありません。リポジトリが接続されると、エディタには「バージョン」ドロップダウンが表示され、そのファイルの過去のすべてのバージョンが、日付や作成者とともに一覧表示されます。いずれかを選択すると、未保存の変更としてエディタに読み込まれるため、まずは内容を確認することができます。保存を行うことで、そのバージョンが元に戻ります。

ローカルで編集しながらテストしてください

ご自身でエディタを使用してファイルを編集している場合は、アプリに保存することなく、キャプチャされたサンプルイベントに対してそのファイルを実行することができます。開発者ページから「**実行」**レベルのAPIキーをご利用ください:

Test the file you are editingbash
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のコード行、および実行時間です。

このエンドポイントでは何も実行されず、何も保存されず、プランの割り当ても一切使用しません。そのため、ファイルウォッチャーによる保存のたびに実行しても問題ありません。(アプリ内の「テストを実行」ボタンを押すと、Shopify Flowワークフローが実行されますので、その実行過程を最初から最後まで確認することができます。これも無料です。)

また、読み取りキーを使用してサンプルペイロードのみを取得し、ローカルに保存して、完全にオフラインでファイルを実行することも可能です:

Get the captured sample payloadbash
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エンドポイントのどちらも、使用制限の対象外となります。これは、「テストを実行」ボタンが実際にShopify Flowワークフローを起動し、その実行状況を監視できるようにしているにもかかわらずです。使用制限の対象となるのは本番実行のみですので、変換処理を何度でも繰り返し試すことができます。

なぜペイロード._changes が空になっているのでしょうか?

その更新内容が追跡対象のフィールド外であった場合(例えば、製品の在庫やバリエーションの変更など)か、あるいはそのレコードについてアプリにまだベースラインが設定されていないかのいずれかです。ベースラインは、アプリがレコードを初めて認識した際に保存されます。そのため、アプリがこれまで認識したことのないレコードに対する最初の更新には、以前の値は含まれません。それ以降の更新にはすべて以前の値が含まれます。

私のコードで、ストア内のデータを変更することはできますか?

いいえ。ctx.shopifyは、ユーザーが許可した読み取り権限で実行され、アプリが書き込みアクセスを要求することはありません。これは意図的な仕様です。監視対象のレコードを編集するトリガーは、自分自身を再起動してしまうため、Shopifyでは、このようなループを防ぐためにワークフローを停止します。トリガーに続く「フローアクション」内でデータを変更してください。

後でハンドルを交換することはできますか?

いいえ、それは意図的な仕様です。Shopify Flowのワークフローはハンドル名でフィルタリングされるため、ハンドル名を変更すると、そのワークフローの実行が黙って停止してしまいます。トリガー名は自由に変更できますが、ハンドル名は変更されません。

このハンドルは、GitHubリポジトリ内のファイル名にもなりますので、こちらも決して変更されることはありません。

もし自分のコードにバグがあったら、どうなりますか?

そのイベントはスキップされ、トリガーにエラーが記録されるため、何が問題だったのかを確認することができます。変換が失敗しても、他の処理がブロックされることはありません。カスタムトリガーや組み込みトリガーなど、他のトリガーは影響を受けることなく正常に動作し続けます。

私のコードはどこで実行されるのでしょうか?

アプリの他の部分から切り離された独立したサンドボックス内で、時間制限が短く、ストアの認証情報にはアクセスできません。このサンドボックスが認識できるのは、ユーザーがキャプチャしたイベントのペイロードと、ctx.shopify または ctx.fetch を使用して取得した情報のみです。

GitHubとアプリの両方で同時にファイルを編集してしまった場合はどうなりますか?

最後に保存した方が優先されます。アプリ内で保存するとファイルへの変更が反映され、接続されているブランチにプッシュすると、アプリ内に保存されているコードが上書きされます。主にリポジトリで作業する場合は、予期せぬ事態を避けるため、アプリのエディタを読み取り専用として扱ってください。

リポジトリ内のファイルが新しいトリガーを作成することはできますか?

いいえ。トリガーは、どのShopifyイベントを監視するかも把握しておく必要がありますが、そのファイルにはコードしか含まれていません。イベントを推測してしまうと、誤った対象に紐付けられてしまいます。まずはアプリ内でトリガーを作成し、その後でそのファイルを自由に編集してください。

アプリがまだ受信していないイベントに対して、この機能は発火するのでしょうか?

いいえ。カスタムトリガーは、アプリがすでにストアに対して購読しているイベントを監視します。これは、ユーザーが許可した権限によって異なります。リソースに対する権限を許可すると、そのリソースのイベントもカスタムトリガーで利用できるようになります。それ以外の場合は、スケジュールに従って実行してください。

今後の手順