Shopify運用設計研究所 > Shopify GraphQL設計ガイド|クエリ・mutation・Bulk Operationsを実装前に整理する
2026年7月3日
•約18分で読めます
Shopify GraphQL Admin APIで商品、注文、メタフィールドを扱う前に、QueryRoot、query/mutation、variables、global ID、fields設計、connections、query cost、userErrors、再実行、Bulk Operationsへの切替基準を整理します。
Shopify GraphQLで商品や注文を取るクエリは書けそうです。あとは実装しながら調整すればよいですか?
動くクエリを書くことと、本番で運用できるクエリを設計することは別です。どのfieldsを取るか、何件ずつページングするか、costをどう見るか、mutation失敗時にどこから再実行するかを先に決めないと、あとで復旧できない連携になります。
「shopify graphql」で調べる人は、GraphQLの文法だけを知りたいわけではありません。商品、注文、顧客、メタフィールドを必要な分だけ取りたい。RESTより柔軟に取れると聞いたが、どこまでネストしてよいか分からない。first: 250で回せばよいのか、Bulk Operationsにすべきなのか、mutationのuserErrorsをどう扱うべきか判断したい。そういう実装直前の悩みが多いはずです。
Shopify公式のGraphQL Admin API referenceでは、Admin APIのGraphQLスキーマ、QueryRoot、Products、Orders、Metafieldsなどの型が確認できます。2026年7月3日時点のlatestは2026-07です。公式Docsは正確ですが、実務では「このクエリは書ける」だけでは足りません。
この記事では、どのAPIを選ぶかを扱うShopify APIの種類と選び方や、Admin API全体の実装計画を扱うShopify Admin API実装計画とは重複させず、GraphQLそのもののquery/mutation設計に絞ります。主役は、過取得を避けるfields設計、cursor pagination、query cost、userErrors、再実行、Bulk Operationsへの切替です。
Shopify GraphQL設計で最初に決めるべきなのは、取得できる最大項目ではなく、業務判断に必要な最小fieldsと、失敗時に再実行できる処理単位です。
Shopify GraphQLは、必要なfieldsだけを指定して取得できるため、RESTより柔軟です。しかし柔軟な分、設計を誤ると「1回のクエリが重い」「ページング途中で止まる」「mutationが一部失敗したのに成功扱いになる」「同じ注文を二重連携する」という事故につながります。
実装前には、次の6つを分けて設計します。
| 設計項目 | 決めること | 決めないと起きること |
|---|---|---|
| スキーマ探索 | GraphiQLでQueryRoot、対象型、入力型、戻り値を確認する | 古いサンプルを流用し、現行versionで使えないfieldに依存する |
| fields設計 | 商品、注文、メタフィールドごとに必要最小限のfieldsを決める | 便利だから全部取るクエリになり、costと保守負荷が増える |
| pagination設計 | first、after、pageInfo、cursor保存単位を決める |
途中停止後に先頭からやり直し、重複処理や欠損が起きる |
| cost設計 | requested/actual cost、throttleStatus、分割基準を見る | 本番件数で急にrate limitに当たり、夜間バッチが終わらない |
| mutation設計 | variables、global ID、userErrors、部分失敗、冪等性を決める |
HTTP 200を成功扱いし、失敗した商品や注文を見落とす |
| Bulk判断 | 通常Queryで回す範囲とBulk Operationsへ逃がす範囲を決める | 全件同期を通常Queryで押し切り、遅くて壊れやすい処理になる |
GraphQLの設計は、文法の話ではありません。EC担当が見たい商品、CSが追う注文、倉庫が使う在庫、外部DBが持つID、再実行時のログまで含めた業務処理の設計です。
Shopify GraphQLの入口はQueryRootです。公式Docsでも、クエリはQueryRootにあるオブジェクトから始まると説明されています。また、GraphiQL ExplorerやShopifyのGraphiQL appを使うと、ストアデータに対してquery/mutationを試しながらスキーマを探索できます。
最初にやるべきことは、完成クエリを探すことではなく、対象業務に必要な型を確認することです。
| 探索するもの | 見るポイント | 例 |
|---|---|---|
| QueryRoot | どの入口から取るか | products、orders、product、order、nodes |
| Object type | 取得できるfields | Product、ProductVariant、Order、Metafield |
| Connection | 一覧取得時の構造 | ProductConnection、OrderConnection |
| Edge/Node | 一覧内の要素とcursor | edges { cursor node { ... } } |
| PageInfo | 次ページの有無 | hasNextPage、endCursor |
| Input type | mutationに渡すvariables | MetafieldsSetInputなど |
| Payload | mutationの戻り値 | 更新後の対象、userErrors |
GraphiQLで確認したクエリは、そのまま本番コードに貼るのではなく、operation名、variables、ログ項目、再実行キーを付けて実装用に整えます。
query ProductSyncProbe($first: Int!, $after: String) {
products(first: $first, after: $after, query: "status:active") {
nodes {
id
title
handle
updatedAt
}
pageInfo {
hasNextPage
endCursor
}
}
}
variablesを使う理由は、クエリ文字列を毎回組み立てないためです。first、after、検索条件、対象IDをvariablesに出しておけば、ログにも残しやすく、再実行もしやすくなります。
GraphQLでは、読み取りをquery、書き込みをmutationで扱います。Shopifyでは、商品一覧を取る、注文詳細を取る、メタフィールドを書く、タグを付ける、といった処理をそれぞれ別のoperationとして設計します。
| 業務処理 | queryで読むもの | mutationで書くもの | 分ける理由 |
|---|---|---|---|
| 商品同期 | 商品ID、handle、status、SKU、必要なメタフィールド | 外部商品ID、同期済みフラグ、商品メタフィールド | 販売文言と外部同期項目を混ぜない |
| 注文連携 | 注文ID、name、createdAt、支払状態、明細、配送先、顧客 | 連携済みタグ、外部WMS ID、注文メタフィールド | 出荷対象判定と連携結果の書き戻しを分ける |
| メタフィールド更新 | ownerId、namespace、key、type、既存値 | metafieldsSetなど |
失敗項目だけ再実行できるようにする |
| 差分確認 | updatedAt、status、対象ID | 原則書かない | 読む処理と書く処理を分離し、障害時の影響を狭める |
| 手動再実行 | 対象ID、前回エラー、最新状態 | 必要なmutationだけ | 全バッチを再実行せず、対象単位で復旧する |
Admin APIの全体設計では認証、スコープ、API version、Webhookも重要です。ただし、GraphQL query/mutation設計だけを見るなら、ポイントは「1つのoperationが何の業務結果を作るか」です。1本の巨大なqueryで商品、注文、顧客、メタフィールドを全部見るより、処理単位で分けた方がログも再実行も安定します。
Shopify GraphQLでは、gid://shopify/Product/123456789のようなglobal IDを使います。商品、バリエーション、注文、顧客、メタフィールド、ロケーションなど、mutationの対象指定でもglobal IDが必要になります。
実務では、global IDだけでも、SKUだけでも足りません。外部DBやWMS、PIM、CRMが持つIDとShopify側のIDを対応させる必要があります。
| 対象 | Shopify GraphQLで使うID | 外部で持ちがちなキー | 保存方針 |
|---|---|---|---|
| 商品 | Product global ID | 外部商品ID、handle、商品コード | 外部商品IDとProduct IDの対応を保存する |
| バリエーション | ProductVariant global ID | SKU、JAN、色サイズコード | SKUとVariant IDの対応を保存する |
| 注文 | Order global ID | 注文番号、WMS注文ID、会計伝票ID | Order IDと外部連携ID、処理状態を保存する |
| 顧客 | Customer global ID | メール、CRM顧客ID | メール変更を前提に、Customer IDを保存する |
| メタフィールド | ownerIdとnamespace/key | 外部属性名、項目コード | ownerId、namespace、key、typeを台帳化する |
SKUやメールは業務上分かりやすいですが、変更される可能性があります。mutationの再実行では、対象を取り違えないことが最優先です。GraphQL IDと外部キーの対応表を持ち、再実行時には最新状態をqueryで確認してからmutationを投げます。
メタフィールドのnamespace/key/typeの考え方は、Shopifyメタフィールド設計ガイドでも詳しく整理しています。GraphQL実装では、それをさらにownerId単位の実行設計へ落とします。
GraphQLは、欲しいfieldsを選べます。だからこそ、設計しないと「今後使うかもしれない」項目が増え続けます。商品詳細、注文明細、顧客、メタフィールド、フルフィルメントまで深くネストすると、query costもレスポンスサイズも大きくなります。
fields設計では、画面や連携先が本当に使う項目だけを残します。
| 対象 | 最初に取るfields | 後回しにするfields | 理由 |
|---|---|---|---|
| products | id、title、handle、status、updatedAt |
画像全件、説明HTML、全variants、全metafields | 商品一覧同期では識別と差分判定を優先する |
| variants | id、sku、displayName、必要な価格/在庫参照 |
すべてのselectedOptions、画像、販売計画 | SKU単位で使う項目だけに絞る |
| orders | id、name、createdAt、displayFinancialStatus、displayFulfillmentStatus |
返金、イベント履歴、全顧客属性 | 出荷や連携判定に必要な状態を優先する |
| lineItems | id、quantity、sku、variant { id } |
商品説明、画像、全discountAllocations | WMSや会計へ渡す粒度に合わせる |
| metafields | namespace、key、type、value |
使わないnamespaceの全件 | 対象namespace/keyを明示して取得する |
たとえば商品同期で必要なのが外部商品IDと配送温度帯だけなら、メタフィールド全件を取る必要はありません。注文連携で必要なのがSKU、数量、配送先、支払状態なら、商品説明HTMLや画像URLを同じqueryに入れない方がよいです。
GraphQLの「必要なfieldsだけ取れる」は、自由にたくさん取れるという意味ではなく、業務に不要なfieldsを取らない責任が実装側にあるという意味です。
1つの万能queryを作るより、目的別に小さく分けます。以下は考え方の例です。実装時はストアのスコープ、API version、対象件数、必要fieldsに合わせて調整します。
商品差分を見るqueryでは、まず識別と更新日時を優先します。
query ProductsForDeltaSync($first: Int!, $after: String) {
products(first: $first, after: $after, query: "status:active") {
nodes {
id
title
handle
status
updatedAt
variants(first: 20) {
nodes {
id
sku
displayName
}
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
注文連携では、出荷や外部連携に必要なfieldsだけに絞ります。
query OrdersForFulfillmentExport($first: Int!, $after: String) {
orders(first: $first, after: $after, query: "financial_status:paid fulfillment_status:unfulfilled") {
nodes {
id
name
createdAt
displayFinancialStatus
displayFulfillmentStatus
lineItems(first: 50) {
nodes {
id
quantity
sku
variant {
id
}
}
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
メタフィールドは、ownerとnamespace/key/typeを意識します。
query ProductMetafieldsForIntegration($id: ID!) {
product(id: $id) {
id
title
metafields(first: 20, namespace: "integration") {
nodes {
id
namespace
key
type
value
}
}
}
}
ここで大事なのは、例のfieldsをそのまま使うことではありません。業務処理ごとに「このfieldsを使って何を判断するか」を書ける状態にすることです。
Shopify公式のPaginating results with GraphQLでは、connections、nodes、edges、pageInfo、forward paginationが説明されています。forward paginationでは、firstで1ページの件数を指定し、前ページのendCursorを次回のafterに渡します。
ページネーションは、ただループを書く作業ではありません。途中で止まったときに、どこから再開するかを決める設計です。
| 要素 | 役割 | 実装で決めること |
|---|---|---|
first |
1回で取る件数 | 250固定にせず、costとレスポンスサイズで調整する |
after |
次ページの開始位置 | 前回保存したendCursorを渡す |
nodes |
取得対象の配列 | cursorが不要な一覧処理では読みやすい |
edges |
nodeとcursorの組 | レコード単位のcursorやデバッグが必要な場合に使う |
pageInfo.hasNextPage |
次ページの有無 | falseになるまで処理する |
pageInfo.endCursor |
次ページ用cursor | バッチログに保存し、再開地点に使う |
実務では、cursorだけでなく検索条件も保存します。query: "updated_at:>=2026-07-01"のような条件で回したバッチなら、再実行時に同じ条件、同じoperation、同じvariablesで再開できるようにします。
| 処理 | cursor保存単位 | 注意点 |
|---|---|---|
| 商品全件棚卸し | operation名、検索条件、endCursor、処理済み件数 | 商品更新が並行して起きるため、必要なら取得時点を固定する |
| 注文差分連携 | 対象期間、注文ステータス、endCursor、最後に成功したOrder ID | 同じ注文を二重送信しない処理済みキーが必要 |
| メタフィールド補正 | owner type、namespace/key、対象ID、成功/失敗 | 失敗したownerだけ再実行できるようにする |
| 管理画面の一覧 | ページごとのcursor | ユーザー操作なのでレスポンス時間を優先する |
first: 250で常に最大件数を取る設計は、分かりやすい一方で危険です。深いネストや重いfieldsがあると、件数を増やした分だけcostとレスポンスサイズが増えます。まず小さく試し、requestedQueryCostとactualQueryCostを見て調整します。
Shopify公式のShopify API limitsでは、GraphQL Admin APIはcalculated query costで制限され、レスポンスのextensions.costにrequestedQueryCost、actualQueryCost、throttleStatusが返ると説明されています。さらに、Shopify-GraphQL-Cost-Debug=1ヘッダーを付けると、fieldごとのcost内訳を確認できます。
cost設計で見るべきものは、HTTPステータスだけではありません。
| 項目 | 意味 | 見る理由 |
|---|---|---|
| requestedQueryCost | Shopifyが実行前に見積もったcost | クエリが重すぎるかを事前に判断する |
| actualQueryCost | 実際の結果に応じたcost | 検索結果が少ない場合など、実行後の実コストを見る |
| maximumAvailable | 利用可能なcostバケット上限 | ストア/アプリの上限感を把握する |
| currentlyAvailable | 現在使える残量 | 次のリクエストを待つべきか判断する |
| restoreRate | 1秒あたりの回復量 | バッチの待機時間や並列数を決める |
| Cost-Debug fields | どのfieldが重いか | 深いネストや不要fieldを削る根拠にする |
ログには、operation名、variables、対象件数、requested/actual cost、throttleStatusを残します。これがないと、本番で遅くなったときに「Shopifyが遅い」のか「自分たちのqueryが重い」のか切り分けられません。
cost超過時の対応は、待つだけでは不十分です。
| 症状 | まず見るもの | 設計変更 |
|---|---|---|
| requested costが高い | Cost-Debugのfield別内訳 | 不要fieldsを削る、ネストを分ける |
| actual costが高い | 取得件数、検索条件、connection | firstを下げる、条件を絞る |
| currentlyAvailableが不足 | throttleStatus | キュー化し、restoreRateに合わせて待機する |
| 夜間バッチが終わらない | 総件数、1ページcost、再試行回数 | 差分化またはBulk Operationsへ切り替える |
| 管理画面操作が遅い | レスポンス時間、同時実行 | 画面用queryとバッチ用queryを分ける |
GraphQL mutationは、HTTPとして成功しても、payload内のuserErrorsで業務エラーを返すことがあります。したがって、200 OKだけで成功扱いしてはいけません。
メタフィールド更新を例にすると、variablesでownerId、namespace、key、type、valueを渡し、戻り値で更新されたmetafieldsとuserErrorsを確認します。
mutation SetProductIntegrationMetafields($metafields: [MetafieldsSetInput!]!) {
metafieldsSet(metafields: $metafields) {
metafields {
id
namespace
key
value
}
userErrors {
field
message
code
}
}
}
mutation設計では、成功/失敗を次の粒度で見ます。
| 観点 | 設計内容 | ログに残すもの |
|---|---|---|
| 入力単位 | 1件ずつか、複数件まとめるか | ownerId、namespace/key、外部キー |
| 成功判定 | payloadの更新結果とuserErrorsを見る |
mutation名、成功件数、失敗件数 |
| 部分失敗 | 失敗した入力だけ再実行できるか | field、message、code、該当input |
| 冪等性 | 同じmutationを再実行しても結果が壊れないか | idempotency key、外部連携ID、処理済み状態 |
| 手動補正 | 人が直すべきエラーを分けるか | エラー分類、担当、対応メモ |
在庫や注文連携では、差分加算のようなmutationを無造作に再実行すると危険です。再実行しても同じ結果になる処理、または再実行前に最新状態をqueryで取り直してから判断する処理にします。
GraphQLの再実行性は、ログ設計で決まります。どのquery/mutationを、どのvariablesで、どのshopに、どのAPI versionで投げたかが残っていなければ、再実行はできません。
| ログ項目 | queryで必要 | mutationで必要 | 理由 |
|---|---|---|---|
| shop domain | 必要 | 必要 | 複数ストアで取り違えない |
| API version | 必要 | 必要 | version差分を切り分ける |
| operation name | 必要 | 必要 | どの処理か分かる |
| variables | 必要 | 必要 | 同じ条件で再実行する |
| cursor | 一覧queryで必要 | 通常は不要 | 途中再開に使う |
| global ID | 対象queryで必要 | 必須 | 対象を一意に特定する |
| requested/actual cost | 必要 | 必要 | 重い処理を見直す |
| userErrors | 不要 | 必須 | 失敗理由と再実行可否を見る |
| 外部連携ID | 場合による | 必須 | 二重送信や二重更新を避ける |
operation名を付けずに匿名queryを投げ続けると、ログ上で何が起きたか追いにくくなります。ProductsForDeltaSync、OrdersForFulfillmentExport、SetProductIntegrationMetafieldsのように、業務処理が分かる名前を付けます。
Shopify公式のAPI limitsでは、大量データの取得には単発queryではなくBulk Operationsを使うべきと説明されています。Bulk Operationsは、大量データを非同期で処理するための仕組みです。通常のqueryと同じ感覚で「遅くなったらBulkにする」ではなく、最初から判断基準を決めておきます。
| 処理 | 通常Queryが向く | Bulk Operationsが向く |
|---|---|---|
| 管理画面で1商品を表示 | 向く | 向かない |
| Webhook後に1注文を取り直す | 向く | 向かない |
| 更新日で絞った数百件の商品差分 | 向く | 条件次第 |
| 数万商品の初期同期 | 重くなりやすい | 向く |
| 過去数年の注文を分析DBへ投入 | 重くなりやすい | 向く |
| 全メタフィールド棚卸し | 件数次第 | 向く |
| ユーザー操作に即時結果を返す処理 | 向く | 向かない |
Bulk Operationsを使う場合も、設計は必要です。
| 設計項目 | 決めること |
|---|---|
| 開始条件 | 初期同期、月次棚卸し、過去注文投入など、通常処理と分ける |
| query内容 | Bulk用にfieldsを絞り、画面用queryを流用しない |
| 完了検知 | Webhookまたはポーリングで状態を確認する |
| 結果処理 | JSONLを1行ずつ処理し、メモリに全件載せない |
| 保存先 | 取得ファイル、処理済みID、エラー行、再実行キーを保存する |
| 再実行 | 前回結果の破棄、差分再取得、部分再処理を分ける |
Bulk Operationsは「重いqueryの逃げ道」ではなく、通常APIとは別の処理方式です。即時性が必要な処理、1件ずつ最新状態を見たい処理、mutationで細かく書き戻す処理とは分けて設計します。
最後に、Shopify GraphQLのquery/mutationを本番に入れる前のチェックリストを置きます。
| チェック項目 | 確認すること |
|---|---|
| スキーマ探索 | GraphiQLでQueryRoot、対象型、Connection、Input、Payloadを確認したか |
| operation名 | 匿名queryではなく、業務処理名が分かる名前を付けたか |
| variables | ID、cursor、件数、検索条件をvariablesに出したか |
| fields | 連携先や画面が使うfieldsだけに絞ったか |
| global ID | Shopify global IDと外部キーの対応を保存するか |
| pagination | first、after、pageInfo.endCursor、再開方法を決めたか |
| cost | requested/actual cost、throttleStatus、Cost-Debugを見るログがあるか |
| mutation | userErrorsを成功判定に含めたか |
| 再実行 | 失敗した対象だけ再実行できるoperation/variables/ログになっているか |
| Bulk判断 | 通常Query、差分Query、Bulk Operationsの切替基準を決めたか |
このチェックリストが埋まっていれば、Shopify GraphQLは「書けるクエリ」から「運用できるクエリ」に近づきます。
Shopify GraphQLは、必要なfieldsだけを取れる強力なAPIです。しかし、本番運用ではそれだけでは足りません。
GraphiQLでスキーマを探索し、QueryRootから入口を選び、query/mutationを業務単位に分ける。variablesとglobal IDで再実行できる形にし、products/orders/metafieldsのfieldsを最小化する。connections、nodes、edges、pageInfo、first、afterで途中再開できるページネーションを設計する。requestedQueryCost、actualQueryCost、throttleStatus、Cost-Debugをログに残し、重いqueryは分割する。mutationではuserErrorsを見て、部分失敗と冪等性を扱う。大量取得は通常queryで押し切らず、Bulk Operationsへ切り替える。
Bitlightでは、ShopifyのGraphQL Admin APIを単なる開発手段ではなく、商品、注文、メタフィールド、外部DB、運用ログ、再実行まで含めた業務連携として設計します。GraphQLのサンプルを動かす前に、過取得・cost超過・再実行不能を避ける設計に落とし込むことが、長く保守できるShopify連携の第一歩です。
千葉県出身。10歳の頃からプログラミングを始め、ゲーム、Webサイト、ロボット、スマホアプリなどを制作。大阪大学基礎工学部情報科学科で情報工学と統計学を学び、大学時代はAIを研究。大学在学中にWeb広告代理店でのインターンや人材系Webサービスの立ち上げを経験し、卒業後はフリーランスエンジニアとしてGISシステム、データ基盤構築、Webシステムの開発に従事。10年以上のWebアプリ開発・データ分析経験を基に、2023年9月に株式会社ビットライトを設立し、現場業務の仕組み化からデータ基盤構築、データ活用支援までを一気通貫で支援。