---
title: "Shopify Flow連携の実装：トリガー、アクション、そして一周期を失った落とし穴"
description: "トリガーとアクションではフィールドキーの記法が逆で、トリガーの全項目が必須です。デバッグに一サイクルを要した二つの罠と、実装の要点を解説します。"
url: "https://www.quotway.com/ja/blog/building-a-shopify-flow-integration"
type: "blog post"
category: "Engineering"
published: "2026-07-04"
updated: "2026-10-07"
verified: "2026-10-07"
shopify_api_version: "2026-10"
audience: "Flowのトリガー、アクション、テンプレートを作るShopifyアプリ開発者"
scope: "API 2026-10におけるFlowアプリ連携。トリガー・アクション・テンプレートの各拡張、逆向きのキーの命名規則、「全フィールド必須」の契約、そして私たち自身の構築で踏んだデプロイとデバッグの落とし穴"
locale: "ja"
source: "QuotWay - B2B Quote & Negotiation App for Shopify"
---

# Shopify Flow連携の実装：トリガー、アクション、そして一周期を失った落とし穴

**要点：** Shopify Flowの連携には三つの可動部があります。**トリガー**（アプリがFlowに送るイベント）、**アクション**（Flowがアプリを呼び出すステップ）、そして**テンプレート**（出来合いのワークフロー）です。拡張機能の設定は素朴なTOMLですが、罠はプラットフォームの文書化されていない規則の中にあります。私たちが実際にデバッグの一周期を費やした二つは、**トリガーのフィールドキーとアクションのフィールドキーで記法の慣習が逆であること**、そして**トリガーで宣言したすべての項目が、すべてのペイロードで必須であること**です。つまり「省略可能な参照フィールド」は矛盾であり、ゲストのイベントを静かに壊します。[QuotWay](/features/shopify-flow)で10個のトリガー、3個のアクション、6個のテンプレートを提供した経験から、その全体像を示します。

## Flow連携とは何か（そして誰が使えるのか）

[Shopify Flow](https://shopify.dev/docs/apps/build/flow)はShopifyのノーコード自動化ビルダーです。アプリは三つの方法で接続します。

- **トリガー**（`flow_trigger`） - アプリがイベント（「見積が送信された」）を送り、マーチャントはそれを起点にワークフローを組み立てます。
- **アクション**（`flow_action`） - Flowのワークフローがアプリを呼び出して何かを*行わせ*（「見積の状況を更新する」）、マーチャントが設定した項目を渡します。
- **テンプレート**（`flow_template`） - Flowのテンプレートギャラリーに現れる、あらかじめ作られたワークフロー。マーチャントは白紙ではなく動く例から始められます。

まず一つ、よくある誤解を潰しておきます。**アプリが提供する拡張機能について、FlowはPlus専用ではありません。** 公開されているApp Storeアプリのトリガーとアクションは、Plusに限らず**どの有料Shopifyプラン**のマーチャントでも利用できます。（マーチャント自身が作るFlowのワークフローには別のプラン規則がありますが、それは拡張機能が*利用可能かどうか*とは別の話です。）私たちが区分しているのはパッケージ上の選択によるもので、トリガーはProfessional、アクションはEnterpriseです。プラットフォームがそれを強制しているわけではありません。

## 三つの構成要素

トリガー、アクション、テンプレートはそれぞれ独立した拡張機能のディレクトリで、`shopify.extension.toml` を持ちます。共通の「Flowアプリ」オブジェクトのようなものはなく、多数の小さな拡張機能から連携を組み立てます。

### トリガー：アプリ → Flow

トリガーは名前と、ワークフローで使えるデータになる項目の集合を宣言します。フィールドキーは**公開されたペイロードの契約**です。

```toml
# extensions/flow-quote-submitted/shopify.extension.toml
[[extensions]]
type = "flow_trigger"
name = "Quote submitted"
handle = "quote-submitted"

  [[settings.fields]]
  type = "single_line_text_field"
  key = "Quote id"            # ← 単語頭大文字、スペース区切り

  [[settings.fields]]
  type = "number_decimal"
  key = "Total amount"        # マーチャントは「Total amount >= X」の条件を作ります

  [[settings.fields]]
  type = "single_line_text_field"
  key = "Customer id"         # ゲストの場合は空文字列 — 落とし穴 #2 を参照
```

イベントの送信は、トリガーのハンドルと、まさにその項目名をキーとするペイロードを添えて `flowTriggerReceive` ミューテーションを呼び出すことで行います。私たちは状態が遷移するたびに、小さな `flow/emit` サービスから送信しています（送信、提案の送付、承認、再見積、辞退、期限切れ、変換、そして承認関連の三つで、合計10個です）。

### アクション：Flow → アプリ

アクションは、マーチャントが入力する項目と、ステップの実行時にFlowがPOSTする `runtime_url` を宣言します。私たちは**すべての**アクションを一つのエンドポイントに送り、ハンドルで振り分けています。

```toml
# extensions/flow-update-quote-status/shopify.extension.toml
[[extensions]]
type = "flow_action"
name = "Update quote status"
handle = "update-quote-status"
runtime_url = "/api/flow/action"     # すべてのアクションで一つのエンドポイント
validation_url = "/api/flow/validate" # 保存時の項目検証
schema = "./schema.graphql"
return_type_ref = "QuoteActionResult"

  [[settings.fields]]
  type = "single_line_text_field"
  key = "quote_id"            # ← スネークケース（トリガーとは逆！）
  required = true

  [[settings.fields]]
  type = "single_line_text_field"
  key = "target_status"       # IN_REVIEW | DECLINED | EXPIRED
  required = true
```

`/api/flow/action` のハンドラーはShopifyのHMACを検証し、アクションのハンドルで振り分け、**`action_run_id` について冪等**であり（Flowは再試行します）、`return_type_ref` に一致する型付きの結果を返します。私たちは三つのアクションを提供しました。状況の更新、通知の送信、担当者への割り当てです。いずれもルートではなく、サービスの入口でEnterpriseに区分しています。

### テンプレート：動く出発点

`flow_template` の拡張機能は、`.flow` ファイル（ワークフローの定義）とロケールの文言をまとめたものです。マーチャントが自分で配線せずに「高額の見積が届いたらSlackに投稿する」を得られるようにします。私たちは六つ提供しました。注意点として、テンプレートは**Flowチームの審査を経てから**ギャラリーに現れます（数営業日）。そのため、アプリの他の部分とは別の時計で進みます。

## 落とし穴（どれも傷跡です）

| 落とし穴 | 何が起きるか | 対処 |
|---|---|---|
| **記法が逆** | トリガーのフィールドキーは `Title Case With Spaces`（「Total amount」）、アクションのフィールドキーは `snake_case`（`quote_id`）です。取り違えると項目が結び付きません。 | この分かれ方を覚えてください。トリガーは単語頭大文字、アクションはスネークケースです。 |
| **トリガーの全項目が必須** | 宣言したすべての項目が、**すべての**ペイロードに存在しなければ**なりません**。「省略可能な参照フィールド」は矛盾です。一度でも省く（たとえば顧客のないゲスト見積）と、そのイベント全体が拒否されます。 | 参照フィールドではなく、`""` になりうる素のテキスト項目を使い、Flow側で「空でない」による分岐をマーチャントに任せてください。 |
| **設定画面は保存できない** | ステップに対してアプリが提供する設定画面は**読み取り専用**で、`intent.finish({properties})` は何もしません（Shopifyのスタッフに確認済み）。独自の選択UIを作っても、選択内容を保持できません。 | 標準の `[[settings.fields]]` と、保存時に不正な値を拒否する `validation_url` を使ってください。 |
| **ライフサイクルのコールバックは別の拡張機能** | `flow_trigger` 拡張機能だけでは、誰が待ち受けているか分かりません。Flowがワークフローのオン・オフを知らせるのは別の `flow_trigger_lifecycle_callback` 拡張機能を通じてだけで、これを追加すると、エンドポイントが5秒以内に `2xx` で応答しない限り、マーチャントはワークフローを有効にできません。受け取ることのない購読の記録を条件に送信すると**閉じる側に失敗**し、誰にもイベントが届きません。 | **権利のあるすべてのストア**に対して送信する（私たちはこちら）か、ライフサイクルのコールバックの拡張機能を追加し、そのエンドポイントを高速で稼働した状態に保ってください。 |
| **`.flow` ファイルは自己検証する** | テンプレートのファイルは自身のSHA-256のダイジェストを持ち、エディターからの書き出しです。手で編集すると拒否されます。 | `.flow` を手で編集しないでください。マーチャント固有の項目を空にしたまま、Flowのエディターから書き出し直します。`preInstallNote` はTOMLではなくロケールのJSONにあります。 |
| **スキーマの変更には協調したリリースが必要** | トリガーの項目を変更すると、送信するペイロードは*デプロイ済み*のスキーマと一致していなければなりません。送信側を出さずに拡張機能をデプロイする（またはその逆）と、すべてのイベントが拒否されます。 | 同時にリリースしてください。`shopify app deploy` **と**バックエンドの反映を歩調を合わせて行います。 |
| **型に厳密** | `number_decimal` は文字列ではなくJSONの**数値**である必要があり、`*_reference` の項目はGIDではなく**数値のレガシーID**を求めます。 | 送信の境界で型を変換し、参照フィールドより素の項目を優先してください（落とし穴 #2 を参照）。 |

*2026年10月7日訂正：* ライフサイクルのコールバックの行は以前、TOMLで定義したトリガーには購読・購読解除のコールバックがなく、待ち受けがなければ `flowTriggerReceive` は何もしないと書いていました。この記事を書いた時点で、Shopifyはすでにライフサイクルのコールバックを別のCLI拡張機能として記載しており、何もしないという挙動は記載していないため、行の内容を[ライフサイクルイベントのガイド](https://shopify.dev/docs/apps/build/flow/track-lifecycle-events)に合わせました。

最初の二つが、本番でのデバッグ時間を費やしたものです。「全項目必須」は特に厄介です。項目が欠けた*一部*のイベントだけが壊れるため、私たちの場合はゲスト見積（顧客IDなし）といくつかの境界的な遷移でしたが、構造的な問題だと気づくまでは断続的な不具合に見えます。

## 本番でのFlowのデバッグ

Flowの失敗は静かです。拒否されたペイロードはマーチャントには表示されません。境界の両側でログを取り、それを検索してください。

- **送信側：** 成功時に `flow.emit.sent` を、`flowTriggerReceive` がuserErrorsを返したときに `flow.emit.user_errors` を記録します（スキーマの不一致はここに現れます）。
- **アクション側：** 振り分けたハンドル、`action_run_id`、結果を記録します。HMACの失敗と冪等性による短絡も、それぞれ一行残すべきです。

トリガーが「発火しない」ときは、まず送信のログを確認してください。エラーなしで `sent` が出ているならペイロードは受理されており、問題はマーチャントのワークフロー側です。`user_errors` が出ているなら、ペイロードがデプロイ済みのトリガーのスキーマと一致していません（ほぼ必ず落とし穴 #6 です）。

## 現実的な最初の目標

1. 最も重要なイベントについて**トリガーを一つ**、ワークフローが実際に必要とする項目だけで作ります（項目が少ないほど「必須」の罠も減ります）。キーは単語頭大文字です。
2. 既存の状態遷移のコードから、権利のあるすべてのストアに対して送信します。購読による絞り込みはしません。
3. 単一の `runtime_url` にPOSTする**アクションを一つ**、ハンドルで振り分け、HMACを検証し、`action_run_id` について冪等にし、設定画面ではなく `validation_url` を使って作ります。
4. 拡張機能とバックエンドを**同時に**デプロイし、開発ストアで実際のワークフローを使って確認します。
5. テンプレートは最後に追加します。Flowチームの審査が入るため、トリガーとアクションの提供を遅らせないようにしてください。

トリガーとアクションはそれぞれ小さなものです。連携の難しさは、ここまでに挙げたプラットフォームの規則に尽きます。記法と「全項目必須」の契約を初日に正しく押さえておけば、残りはすでに送信しているイベントを配線するだけです。

開発者ではなくマーチャントの方へ。ここで説明した連携は、QuotWayでは[Shopify Flowのトリガーとアクション](/features/shopify-flow)として動いており、Professional以上でご利用いただけます。詳しくは[料金](/pricing)をご覧ください。基幹システム（ERP）、CRM、ヘッドレスのストアフロントなど、Shopifyの外にあるシステムは、代わりにEnterpriseプランの[QuotWayのAPIとWebhook](/features/api-webhooks)で連携します。[6つの連携例](/blog/quotway-api-webhooks-recipes)で、それぞれが使うイベントとエンドポイントを紹介しています。

## 参考資料

2026年10月7日にShopifyのページで再確認しました。上の落とし穴は私たち自身の構築によるもので、ライフサイクルのコールバックの行は同日、Shopifyの[ライフサイクルイベントのガイド](https://shopify.dev/docs/apps/build/flow/track-lifecycle-events)と照合して訂正しました。

- Shopify：[アプリ開発者向けFlow](https://shopify.dev/docs/apps/build/flow)、[トリガー](https://shopify.dev/docs/apps/build/flow/triggers)、[アクション](https://shopify.dev/docs/apps/build/flow/actions)、[テンプレート](https://shopify.dev/docs/apps/build/flow/templates)
- Shopifyヘルプセンター：[Shopify Flow](https://help.shopify.com/en/manual/shopify-flow) - FlowはBasic、Grow、Advanced、Plusで無料であり、だからこそアプリのトリガーとアクションは有料のどのプランにも届く
- 姉妹記事：[Shopify Sidekickのアプリ拡張を作る](/blog/building-shopify-sidekick-app-extensions)
- マーチャント側でトリガーが何のためにあるか：[Shopify B2B見積と基幹システム・CRM・PIMの連携パターン](/blog/shopify-b2b-erp-crm-pim-integration-patterns)
