はじめに
「Shopifyの管理画面では今月120件売れているのに、GA4を見ると180件になっている」「売上が1.5倍に膨らんでいる」――こういった相談は本当に多く、Shopify運営者がGA4計測でつまずく定番ポイントです。
数字が合わないとき、症状はだいたい次の3つに分類できます。
- 購入数が多すぎる(重複計測) …
purchaseが同じ注文で2回以上発火している。Shopify標準のGA4連携とGTM/カスタムタグが両方動いているケースが典型です。 - 金額がズレる(税・送料・通貨の扱い) …
valueに税や送料が含まれたり含まれなかったり、海外通貨が混ざって合計が狂う。 - 購入数が少なすぎる(取りこぼし) … サンキューページにJavaScriptを仕込む旧来の方法に依存していて、チェックアウト経路が変わると
purchaseが発火しない。
この記事では、これら3症状の原因を切り分け、GA4 DebugViewやBigQueryで診断し、最終的に「Shopify管理画面と±数%以内で合う」状態まで持っていく手順を整理します。GTM/GA4を自分で触っている中小ECの運営者を想定読者にしています。
💡 補足
「±数%以内」と書いたのは、GA4とShopifyは完全一致しないのが正常だからです。広告ブロッカーやCookie拒否、計測タグの読み込み失敗などでGA4側は構造的に数%少なく出ます。逆にGA4のほうが多い場合は、ほぼ確実に重複計測を疑ってください。
eコマース計測の基本設計そのものを見直したい場合は、GTMでGA4 eコマースを完全設定(Shopify対応) に全体像をまとめています。本記事は「すでに入れたが数字が合わない」を解決することにフォーカスします。
原因を切り分ける
数字を直す前に、まず「どこで」「なぜ」ズレているかを特定します。やみくもにタグをいじると、合っていた部分まで壊すので順番が大事です。
1. 計測経路が二重化していないか
Shopifyには大きく分けて2系統のGA4計測の入れ方があります。
| 経路 | 仕組み | 注意点 |
|---|---|---|
| Shopify標準のGA4連携 | 管理画面の設定からGA4測定IDを入れるだけ | 自動でpurchase等を送る。手軽だがカスタマイズ性が低い |
| GTM/カスタムイベント | GTM経由でGA4タグを発火、またはテーマに直接記述 | 柔軟だが標準連携と併用すると二重計測になりやすい |
最頻出の事故が「両方を有効にしている」状態です。 標準連携でpurchaseが1回、GTMでもう1回送られ、購入数がきれいに2倍になります。まずはこのどちらか一方に寄せる、というのが大原則です。
2. purchaseの発火条件は適切か
purchase は「決済が完了したとき1回だけ」発火させるのが正解です。ところが、
- サンキューページをリロードするたびに発火する
- 「注文履歴」や購入後のアカウントページでも発火している
- ありがとうページに戻るリンクを踏むと再発火する
といった作りになっていると、1注文で複数のpurchaseが飛びます。発火の起点が「ページの表示」になっていると起きやすい問題です。
3. サンキューページ依存の限界
これは特に重要です。従来は「注文完了(サンキュー)ページにスクリプトを置いてpurchaseを送る」のが定番でしたが、Shopifyはチェックアウト周りを Checkout Extensibility(チェックアウト拡張) へ移行しており、additional_scripts(注文ステータスページの追加スクリプト)に依存した計測は将来的に動かなくなる方向です。
現在の推奨は Customer Events(顧客イベント / Web Pixel) を使う方法です。checkout_completed などの標準イベントを購読し、そこからGA4のpurchaseを送ります。サンキューページにJSを直書きしている場合、取りこぼし(購入数が少なすぎる症状)の原因はここにあることが多いです。
⚠️ 注意
Customer Events(Web Pixel)はサンドボックス環境で動くため、
window.dataLayerへの直接pushや、ページ側で動いているGTMのスナップショットには直接触れません。Shopifyのanalytics.subscribe()でイベントを受け取り、そこからGA4へ送る設計に切り替える必要があります。「GTMをテーマに入れているのにpurchaseだけ来ない」場合、この壁にぶつかっていることが多いです。
4. 税・送料・通貨の扱い
金額がズレる症状は、ほぼvalue・tax・shipping・currencyの設計ミスです。
valueに税・送料を含めるか … GA4の購入金額(value)に税送料を含めるかは設計判断ですが、サイト内で統一されていないのが一番まずい。標準連携は商品小計ベース、GTMは税送料込み、のように混在すると合計が合わなくなります。- 通貨(
currency) … 多通貨販売をしているのにcurrencyを送っていない、または常にJPY固定で送っていると、海外通貨の注文が円換算されず金額が壊れます。 - 値引き・ポイント … クーポンやポイント利用後の実支払額を送るのか、定価を送るのかも統一が必要です。
診断手順
原因の当たりがついたら、実際に発火を観測して裏を取ります。推測ではなく「目で見て確認」するのがポイントです。
GA4 DebugViewで発火回数を見る
GTMのプレビューを有効にするか、Chrome拡張「GA4 Debug」等でデバッグモードに入ると、GA4のDebugViewにリアルタイムでイベントが流れます。テスト注文を1件入れて、
purchaseが ちょうど1回 か(2回出れば重複)transaction_idが入っているかvalue/currency/tax/shippingの値がShopifyの注文金額と一致するかitems配列に正しい商品・数量・単価が入っているか
を確認します。
💡 補足
本番でテストするのが難しい場合は、少額商品(100円など)の隠し商品を作ってテスト購入し、確認後に返金・削除する運用が安全です。テスト注文には
transaction_idが分かるようメモを残しておくと、後でGA4・BigQuery側と突き合わせやすくなります。
GTMプレビューとリアルタイムで経路を追う
GTMプレビュー(Tag Assistant)では、どのトリガーで何回タグが発火したかが分かります。「purchaseのGA4タグが2回発火していないか」「サンキューページのリロードで再発火していないか」をここで確認します。GA4のリアルタイムレポートは、本番でざっくり「いま購入イベントが来ているか」を見るのに使えます。
BigQueryで重複を定量的に確認する
GA4のBigQueryエクスポートを有効にしているなら、重複は数字で一発で分かります。同じtransaction_idが複数回記録されていないかを数えます。
-- 同一 transaction_id が複数回 purchase されている注文を検出
SELECT
(SELECT value.string_value
FROM UNNEST(event_params)
WHERE key = 'transaction_id') AS transaction_id,
COUNT(*) AS purchase_count
FROM
`your_project.analytics_XXXXXXXXX.events_*`
WHERE
_TABLE_SUFFIX BETWEEN '20260601' AND '20260628'
AND event_name = 'purchase'
GROUP BY
transaction_id
HAVING
purchase_count > 1
ORDER BY
purchase_count DESC;
purchase_count が2以上の行が出てきたら、その分だけ重複計測しています。transaction_id が NULL の行が大量にあれば、IDを送れていない(=重複排除が効いていない)サインです。
金額のズレを確認したいときは、注文単位でvalue・tax・shippingを並べて、Shopifyの注文金額と突き合わせます。
-- 注文ごとの value / tax / shipping を一覧化して Shopify と突き合わせる
SELECT
(SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'transaction_id') AS transaction_id,
(SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'currency') AS currency,
(SELECT value.double_value FROM UNNEST(event_params) WHERE key = 'value') AS value,
(SELECT value.double_value FROM UNNEST(event_params) WHERE key = 'tax') AS tax,
(SELECT value.double_value FROM UNNEST(event_params) WHERE key = 'shipping') AS shipping
FROM
`your_project.analytics_XXXXXXXXX.events_*`
WHERE
_TABLE_SUFFIX BETWEEN '20260601' AND '20260628'
AND event_name = 'purchase'
ORDER BY
transaction_id;
BigQueryでの集計をもっと活用したい場合は、BigQueryでeコマースイベントを解析 も参考にしてください。そもそもイベントがBigQueryに来ていない・GA4で取れていない段階なら、GA4移行後にデータが取れない時のGTMデバッグ のほうが先に読むべき内容です。
直し方
診断で原因が確定したら、ここで直します。
重複計測を排除する
最優先はこれです。手順はシンプルで、purchaseを送る経路を1つに絞るだけです。
- Shopify標準のGA4連携を使うなら、GTM側の
purchaseタグを止める。 - GTM/Customer Eventsでフルコントロールしたいなら、Shopify標準連携のGA4測定IDを外す。
中小ECで「ある程度カスタマイズしたい」「広告計測も含めて整理したい」場合は、後者(GTM+Customer Events)に寄せたほうが後々ラクなことが多いです。逆に「とにかくシンプルでいい」なら標準連携に一本化します。
transaction_idで重複を防ぐ
経路を1つにしても、リロードや再訪でpurchaseが再発火することがあります。最後の保険としてtransaction_id(Shopifyの注文番号)を必ず送ります。GA4は 同一プロパティ内で同じtransaction_idのpurchaseを重複と見なして除外 してくれるため、IDさえ正しく入っていれば二重計上のリスクが大きく下がります。
// Customer Events (Web Pixel) で checkout_completed を購読し purchase を送る例
analytics.subscribe("checkout_completed", (event) => {
const checkout = event.data.checkout;
gtag("event", "purchase", {
transaction_id: checkout.order.id, // 注文ID = 重複防止の要
currency: checkout.currencyCode, // 通貨は固定せず実際の値を
value: checkout.totalPrice.amount, // 含める範囲をサイト内で統一
tax: checkout.totalTax.amount,
shipping: checkout.shippingLine?.price.amount ?? 0,
items: checkout.lineItems.map((li) => ({
item_id: li.variant?.sku ?? li.variant?.id,
item_name: li.title,
quantity: li.quantity,
price: li.variant?.price.amount,
})),
});
});
⚠️ 注意
上のコードはあくまで構造の例です。Shopifyの
event.dataの正確なプロパティ名やネスト構造はバージョンで変わるため、実装時は必ずDebugViewで実際に飛んでいる値を確認してください。UIや項目名の細部は最新の管理画面・公式ドキュメントに従ってください。
items配列とvalue/tax/shippingを整合させる
金額のズレは、ここで「ルールを決めて統一」します。
valueの定義を1つに決める … 例:「税送料込みの実支払額」。決めたら全経路でそれに統一します。tax・shippingを別パラメータで送る …valueに含めつつ、tax・shippingも内訳として送っておくと、後でどちらの集計もできて便利です。currencyは実際の通貨を動的に … 多通貨ならイベントごとに通貨コードを入れる。固定値は禁物です。itemsの単価×数量の合計がvalue(税送料を除いた小計)とおおむね一致するかを確認します。
整えたら、もう一度テスト購入してDebugViewとBigQueryで突き合わせ、Shopifyの注文金額と一致することを確認します。
チェックリスト
公開前・修正後にこの順で確認すると、たいていの「合わない」は潰せます。
| # | 確認項目 | OKの状態 |
|---|---|---|
| 1 | GA4計測経路 | 標準連携かGTM/Customer Events、どちらか 一方だけ |
| 2 | purchase発火回数 | テスト1注文で purchase が ちょうど1回 |
| 3 | transaction_id | 全purchaseに注文IDが入っている(NULLがない) |
| 4 | サンキューページ依存 | additional_scripts直書きではなくCustomer Eventsで送っている |
| 5 | value | 含める範囲(税送料込み/抜き)が全経路で統一 |
| 6 | currency | 実際の通貨コードを動的に送っている(固定値でない) |
| 7 | tax / shipping | 内訳パラメータとして送られ、金額がShopifyと一致 |
| 8 | items配列 | 商品ID・数量・単価が正しく、小計がvalueと整合 |
| 9 | リロード再発火 | サンキューページを再読込してもpurchaseが増えない |
| 10 | 突き合わせ | GA4/BigQueryの集計がShopify管理画面と±数%以内 |
まとめ
Shopify×GA4で数字が合わない問題は、原因をたどると驚くほど定番のパターンに収束します。
- 購入数が 多すぎる → 計測経路の二重化。経路を一方に絞り、
transaction_idで重複を防ぐ。 - 金額が ズレる →
value/tax/shipping/currencyの定義が不統一。ルールを1つに決めて全経路で揃える。 - 購入数が 少なすぎる → サンキューページ直書きの取りこぼし。Customer Eventsへ移行する。
そして、直したつもりで終わらせず、テスト注文をDebugViewとBigQueryで突き合わせて「Shopify管理画面と合う」ことを目で確認する――ここまでやって初めて「計測が直った」と言えます。
数字が信頼できないと、広告の費用対効果も在庫判断も全部ぶれてしまいます。チェックリストを上から1つずつ潰していけば、多くのケースは自力で解決できるはずです。経路の整理やCustomer Eventsへの移行など、構造から見直す段階で手が止まったら、無理に本番をいじる前に一度立ち止まって設計から確認することをおすすめします。