← All Guides

WooCommerce GA4 Ecommerce Tracking with Google Tag Manager: Data Layer, Purchase Deduplication and Google Ads

How to set up GA4 ecommerce tracking for WooCommerce through Google Tag Manager: the view_item, add_to_cart, begin_checkout and purchase data layer, plugin versus custom implementation, deduplicating the thank-you page, and feeding Google Ads and Meta from the same events.

Atiqul Islam
Written by Atiqul Islam

Tracking & analytics specialist. 278+ GTM, GA4 and server-side tracking projects for clients in the US, UK and Australia.

Rather not do this yourself? I set this up for clients every week. Free 15-minute audit, no obligation.

Book a Free Audit

WooCommerce stores have an advantage over hosted platforms: you control the server, so you can output exactly the data layer you want on every page, including the thank-you page. The flip side is that nothing is set up for you, and a surprising number of stores run GA4 with only page views, or with a purchase event that fires twice per order.

This guide covers the full GA4 ecommerce setup for WooCommerce through Google Tag Manager, the choice between a plugin and a custom data layer, purchase deduplication, and sending the same events to Google Ads and Meta.

The Events That Matter

Event Fires when Key data
view_item Product page viewed item_id, item_name, price, category
add_to_cart Product added, including AJAX item, quantity, value
begin_checkout Checkout page loaded cart items, value
add_shipping_info, add_payment_info Optional checkout steps items, value
purchase Order received page, once transaction_id, value, tax, shipping, currency, items

Step 1: Install GTM

Use a lightweight plugin to insert the container, or add the GTM snippets to header.php and the body open hook of a child theme. Confirm the container loads on the storefront in Preview.

Step 2: Choose a Data Layer Method

Plugin method. Install GTM4WP, enable the WooCommerce integration and the GA4 events you need. It outputs a data layer for product, cart, checkout and order pages and handles AJAX add to cart. Fast and reliable for standard themes.

Custom method. Use WooCommerce hooks in a small plugin or your child theme to output data layer pushes. The purchase event is the most important one:

add_action('wp_head', function () {
  if (!function_exists('is_order_received_page') || !is_order_received_page()) { return; }
  $order_id = absint(get_query_var('order-received'));
  $order = wc_get_order($order_id);
  if (!$order || $order->get_meta('_ga4_purchase_tracked')) { return; }

  $items = [];
  foreach ($order->get_items() as $item) {
    $product = $item->get_product();
    $items[] = [
      'item_id'   => $product ? ($product->get_sku() ?: $product->get_id()) : $item->get_product_id(),
      'item_name' => $item->get_name(),
      'price'     => (float) $order->get_item_total($item, false),
      'quantity'  => (int) $item->get_quantity(),
    ];
  }

  $payload = [
    'event' => 'purchase',
    'ecommerce' => [
      'transaction_id' => (string) $order->get_order_number(),
      'value'          => (float) $order->get_total(),
      'tax'            => (float) $order->get_total_tax(),
      'shipping'       => (float) $order->get_shipping_total(),
      'currency'       => $order->get_currency(),
      'coupon'         => implode(',', $order->get_coupon_codes()),
      'items'          => $items,
    ],
    'user_email' => strtolower($order->get_billing_email()),
    'user_phone' => $order->get_billing_phone(),
  ];

  echo '<script>window.dataLayer=window.dataLayer||[];window.dataLayer.push({ecommerce:null});window.dataLayer.push(' . wp_json_encode($payload) . ');</script>';
  $order->update_meta_data('_ga4_purchase_tracked', 1);
  $order->save();
});

The order meta flag is what makes the thank-you page safe to reload. For view_item, hook woocommerce_before_single_product and output the product data. For AJAX add_to_cart, add a small script that listens for WooCommerce's added_to_cart event:

<script>
jQuery(document.body).on('added_to_cart', function (e, fragments, hash, button) {
  var $b = jQuery(button);
  window.dataLayer = window.dataLayer || [];
  window.dataLayer.push({ ecommerce: null });
  window.dataLayer.push({
    event: 'add_to_cart',
    ecommerce: {
      currency: window.wc_currency || 'USD',
      value: parseFloat($b.data('price') || 0) * parseInt($b.data('quantity') || 1, 10),
      items: [{
        item_id: $b.data('product_sku') || String($b.data('product_id')),
        item_name: $b.data('product_name') || $b.attr('aria-label') || '',
        price: parseFloat($b.data('price') || 0),
        quantity: parseInt($b.data('quantity') || 1, 10)
      }]
    }
  });
});
</script>

Add data-price and data-product_name attributes to the add to cart button through the woocommerce_loop_add_to_cart_args filter if your theme does not already expose them.

Step 3: GTM Variables, Triggers and Tags

Variables for ecommerce.transaction_id, ecommerce.value, ecommerce.currency, ecommerce.items, user_email, user_phone. Custom Event triggers for each event name.

Step 4: Payment Gateway Redirects

PayPal, Stripe hosted checkout and bank redirects send the customer away and back. Add the gateway domains to GA4's unwanted referrals list so the purchase keeps its original source. If the gateway returns the customer to the order received page twice, the order meta flag above prevents a second purchase push.

Step 5: Test

  1. GTM Preview. View a product, add to cart with AJAX, begin checkout. Confirm each event with items and values.
  2. Place a test order using cash on delivery or a test gateway. Confirm one purchase with the correct order number, total, tax and shipping.
  3. Reload the order received page. No second purchase.
  4. Check GA4 DebugView and Monetisation reports, Meta Test Events, and the Google Ads request in a tag debugger such as the Insighter Debugger extension.
  5. After a few days, compare GA4 revenue with WooCommerce Analytics for the same period.

Common Problems

Outcome

GA4 shows the full WooCommerce funnel with accurate revenue, each order counted once. Google Ads and Meta receive the same purchase with value and customer data for matching. From there you can trust return on ad spend by channel, which is the whole point of the exercise.

Frequently Asked Questions

Plugin or custom data layer for WooCommerce?

A data layer plugin such as GTM4WP gets you running in an hour and is fine for most stores. A custom implementation through WooCommerce hooks gives full control and avoids plugin conflicts, and is worth it for larger stores or unusual themes. Never run both.

Why does my purchase event fire more than once?

The thank-you page can be reloaded, revisited from an email, or opened by a payment gateway redirect twice. Use the order ID as the transaction ID in every tag and mark the order as tracked in order meta so the data layer is only output once.

Do I need a GA4 Configuration tag and separate event tags?

You need the Google tag with your measurement ID on All Pages and a GA4 Event tag per ecommerce event. The event tags read items, value and currency from the ecommerce object in the data layer.

How do I handle AJAX add to cart?

WooCommerce triggers an added_to_cart JavaScript event after a successful AJAX addition. Listen for it and push add_to_cart with the product data. The plugin method does this for you.

Can I include tax and shipping?

Yes. Pass tax and shipping as separate parameters on the purchase event and keep value as the order total consistent with how you want revenue reported. Be consistent between GA4 and Google Ads.

Want This Done Right the First Time?

Book a free 15-minute audit call. I'll review your setup, show you what is broken, and tell you exactly what it takes to fix it.

Chat Now