Googleアナリティクス タグマネージャー設定
ECサイト(WooCommerce)で「商品が見られているか」「カートに入れたのに買われていないか」を数値で追えるようにするため、Googleタグマネージャー(GTM)経由でGA4にeコマースイベントを送る設定を行いました。あわせて、自分自身の作業アクセスがデータに混ざらないよう内部トラフィックの除外も設定しています。同じことをやりたい人向けに、手順とハマったポイントをまとめます。
対象読者:WordPress(WooCommerce)サイトの運営者・実装者で、GA4のeコマースレポート(商品別の閲覧・カート追加・購入)を使えるようにしたい人。
以下、サイトの独自情報(ドメイン、測定ID、IPアドレスなど)はすべて置き換え済みのサンプル値です。実際に設定する際は、ご自身の値に読み替えてください。
全体構成
WooCommerceでの行動(商品閲覧・カート追加・購入など)
↓(テーマ/プラグインがdataLayerに出力)
dataLayer(ブラウザ上の一時的なデータ置き場)
↓(GTMのトリガーが検知)
GTM(Googleタグマネージャー)
↓(GA4イベントタグが送信)
GA4(Googleアナリティクス4)
GTMの役割は「WordPress側が出したデータをGA4まで届ける」ところまでです。届いた後のレポート表示(ダッシュボード化など)は別の話になります。
1. サイトへのGTM設置
<head>内のできるだけ上の方に、GTMのコンテナスニペットを設置します。
<!-- Google Tag Manager -->
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-XXXXXXX');</script>
<!-- End Google Tag Manager -->
<body>タグの直後にnoscript用のタグも設置します(JavaScriptが無効な環境向け)。
<!-- Google Tag Manager (noscript) -->
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-XXXXXXX"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<!-- End Google Tag Manager (noscript) -->
GTM側では、測定ID(G-XXXXXXXXXX)を指定した「Googleタグ」を作成し、トリガーを「All Pages(すべてのページ)」に設定してページビュー計測用のタグを用意しておきます。
2. WooCommerceの行動をdataLayerに出力する
GA4は、WooCommerceがカートや注文をどう扱っているかを自動的には知りません。そのため、以下の4つのタイミングでWordPress側からdataLayerにイベントを送る実装が必要です。
| イベント名 | タイミング |
| view_item | 商品詳細ページの閲覧 |
| add_to_cart | カートに追加 |
| begin_checkout | 購入手続き開始(チェックアウトページ表示) |
| purchase | 購入完了(サンクスページ表示) |
これは自作の軽量プラグインとして実装しました(テーマに直接書くと、テーマをバージョンアップ・コピーするたびに再実装が必要になるため)。主なポイントだけ抜粋します。
view_item(商品詳細ページ)
function sge_output_view_item() {
if ( ! is_product() ) {
return;
}
global $product;
if ( ! $product instanceof WC_Product ) {
return;
}
$item = sge_get_item_data( $product );
sge_render_ecommerce_script( 'view_item', array(
'currency' => get_woocommerce_currency(),
'value' => $item['price'],
'items' => array( $item ),
) );
}
add_to_cart(カート追加)
ここが一番ハマりやすいポイントでした。詳細は後述の「つまずいたポイント」を参照してください。AJAX追加とAJAXを使わない通常送信の両方に対応するため、2つの仕組みを併用しています。
- 一覧ページなどのAJAX追加ボタン → WooCommerceが発火する
added_to_cartというjQueryイベントを利用してJavaScript側でdataLayerにpush - 商品詳細ページ等の通常フォーム送信 → PHP側の
woocommerce_add_to_cartアクションで検知し、WooCommerceのセッションに一時保存 → 次のページ表示時(wp_footer)に出力
// AJAXを使わない通常送信の場合の受け皿
function sge_store_add_to_cart_event( $cart_item_key, $product_id, $quantity, $variation_id, $variation, $cart_item_data ) {
if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
return; // AJAX分はJS側で既に計測済みなので除外
}
if ( ! WC()->session ) {
return;
}
$product = wc_get_product( $variation_id ? $variation_id : $product_id );
$item = sge_get_item_data( $product, $quantity );
WC()->session->set( 'sge_pending_add_to_cart', $item );
}
function sge_output_add_to_cart_fallback() {
if ( ! WC()->session ) {
return;
}
$item = WC()->session->get( 'sge_pending_add_to_cart' );
if ( ! $item ) {
return;
}
WC()->session->__unset( 'sge_pending_add_to_cart' );
sge_render_ecommerce_script( 'add_to_cart', array(
'currency' => get_woocommerce_currency(),
'value' => $item['price'] * $item['quantity'],
'items' => array( $item ),
) );
}
begin_checkout(チェックアウトページ)
function sge_output_begin_checkout() {
if ( ! WC()->cart || WC()->cart->is_empty() ) {
return;
}
$items = array();
foreach ( WC()->cart->get_cart() as $cart_item ) {
$items[] = sge_get_item_data( $cart_item['data'], $cart_item['quantity'] );
}
sge_render_ecommerce_script( 'begin_checkout', array(
'currency' => get_woocommerce_currency(),
'value' => (float) WC()->cart->get_subtotal(),
'items' => $items,
) );
}
purchase(購入完了)
サンクスページの再読み込みで二重計測しないよう、注文ごとに送信済みフラグを記録しています。
function sge_output_purchase( $order_id ) {
if ( get_post_meta( $order_id, '_sge_purchase_tracked', true ) ) {
return; // 二重計測防止
}
$order = wc_get_order( $order_id );
// ...注文の商品情報を組み立ててdataLayerへpush...
update_post_meta( $order_id, '_sge_purchase_tracked', 1 );
}
3. GTM側:トリガーとタグの設定
WordPress側の実装だけではGA4にデータは届きません。GTM側で「dataLayerに来たイベントを検知して、GA4に送る」設定が必要です。
- トリガーを作成:タイプ「カスタムイベント」、イベント名を正規表現一致にして以下を指定
^(view_item|add_to_cart|begin_checkout|purchase)$ - タグを作成:タグの種類「GA4 イベント」、イベント名に組み込み変数
{{Event}}を指定、「eコマースデータを送信する」をオンにし、上記トリガーで発火するよう設定
こうすることで、4つのイベントを1つのトリガー・1つのタグでまとめて処理できます。
つまずいたポイント
(1) is_woocommerce()のタイミング問題
プラグインの冒頭に以下のようなガード処理を書いたところ、WooCommerceが有効なのに常に処理が止まってしまいました。
// ダメな例
if ( ! function_exists( 'is_woocommerce' ) ) {
return;
}
原因は、WooCommerce本体がこの関数を定義するのがplugins_loadedというタイミングであるのに対し、上記のチェックはプラグインファイルが読み込まれる最初の瞬間(plugins_loadedより前)に実行されてしまうためです。フックの登録はplugins_loaded以降に行うよう修正しました。
add_action( 'plugins_loaded', 'sge_register_hooks' );
function sge_register_hooks() {
if ( ! function_exists( 'is_woocommerce' ) ) {
return;
}
add_action( 'wp_footer', 'sge_output_view_item' );
// ...他のフックもここに登録...
}
(2) カート追加後のリダイレクト設定
WooCommerceの設定「カートに追加後、カートページへ自動的に移動する」がオンの場合、URLに?added-to-cart=商品IDが付与されないため、URLパラメータを見て判定する方式では検知できません。この設定の有無に関わらず動くよう、WooCommerceのセッションを使う方式(上記のwoocommerce_add_to_cartアクション経由)に変更しました。
4. 動作確認
GTMの「プレビュー」機能(Tag Assistant)を使うと、実際にサイトを操作しながらどのイベントがdataLayerに届き、どのタグが発火したかをリアルタイムで確認できます。
- ブラウザの開発者ツール(Console)で
dataLayerと入力すると、実際に送られた内容を直接確認できるので、Tag Assistant側の表示と食い違う場合の切り分けに有効です
5. 内部トラフィックの除外設定(GA4)
自分自身の作業アクセス(動作確認や管理業務)がデータに混ざると、コンバージョン率などの数値が歪みます。GA4の管理画面で以下の設定を行いました。
手順1:内部トラフィックの定義
- GA4管理画面 →「データストリーム」→ 対象のウェブストリームをクリック
- 「Googleタグ」欄の「タグ設定を行う」をクリック
- 「詳細設定を表示」の中から「内部トラフィックを定義する」をクリック
- 「作成」をクリックし、以下を入力
ルール名 自宅(分かりやすい名前でOK) traffic_typeの値 internal 一致演算子 IPアドレスが次と等しい 値 xxx.xxx.xxx.xxx(自分の現在のIPアドレス) - 「作成」で保存
自分の現在のIPアドレスは「IPアドレス確認」などで検索すると調べられます。家庭用回線の多くは固定IPではないため、IPアドレスが変わると設定が効かなくなる点に注意が必要です。
手順2:データフィルタの作成
- GA4管理画面 →「データ設定」→「データフィルタ」
- 「作成」をクリック
- フィルタの種類「内部トラフィック」を選択
- フィルタ名を入力し、パラメータ値に「internal」(手順1と同じ値)を指定
- 運用状況は最初「テスト中」にして様子を見てから「有効」に切り替える
まとめ
- GTMの役割は「WordPress側のデータをGA4に届ける」ところまで。レポートでの分析は別工程
- WooCommerceの行動をGA4に送るには、dataLayerへの出力(WordPress側の実装)とGTM側のトリガー・タグ設定の両方が必要
- カート追加の計測は、WooCommerceの設定(AJAX有無・カート追加後のリダイレクト有無)によって挙動が変わるため、複数のパターンに対応する実装が必要
- 内部トラフィックの除外は、IPアドレスに依存するため、固定IPでない場合は定期的な見直しが必要