Skip to main content
AnalyticsLearner

Module 10

Shopify purchase dataLayer via Customer Events

Fire the GA4 purchase event from Shopify's Customer Events API (web pixels). Works with the new checkout extensibility without requiring checkout.liquid access.

shopifyga4ecommercecustomer-events

What this does

Shopify's Customer Events (web pixels) is the supported way to track checkout events on Shopify Plus and standard Shopify stores since the removal of checkout.liquid access. This snippet listens for the checkout_completed event and pushes the GA4 purchase dataLayer event.

Web pixel code

Create a custom pixel in Shopify Admin (Settings → Customer Events → Add custom pixel), then paste this code:

// Shopify Customer Events - GA4 purchase via dataLayer
// Place this code in a Custom Web Pixel in Shopify Admin.
 
analytics.subscribe('checkout_completed', (event) => {
  const checkout = event.data.checkout;
 
  if (!checkout) return;
 
  // Build GA4 items array from Shopify line items
  const items = checkout.lineItems.map((lineItem) => ({
    item_id: lineItem.variant?.sku ?? lineItem.variant?.id ?? '',
    item_name: lineItem.title,
    item_brand: lineItem.variant?.product?.vendor ?? '',
    item_category: lineItem.variant?.product?.productType ?? '',
    item_variant: lineItem.variant?.title ?? '',
    price: parseFloat(lineItem.variant?.price?.amount ?? '0'),
    quantity: lineItem.quantity,
  }));
 
  // Calculate revenue (subtotal - discounts applied)
  const revenue = parseFloat(checkout.subtotalPrice?.amount ?? '0');
  const tax = parseFloat(checkout.totalTax?.amount ?? '0');
  const shipping = parseFloat(checkout.shippingLine?.price?.amount ?? '0');
 
  // Push to the parent page dataLayer via browser storage
  // (Customer Events run in a sandboxed iframe - direct dataLayer access
  //  is not available; use postMessage or browser.sessionStorage as a bridge)
  browser.sessionStorage.setItem(
    'pending_purchase',
    JSON.stringify({
      event: 'purchase',
      ecommerce: {
        transaction_id: checkout.order?.id ?? checkout.token,
        value: revenue,
        tax: tax,
        shipping: shipping,
        currency: checkout.currencyCode,
        coupon: checkout.discountApplications?.[0]?.title ?? '',
        items,
      },
    })
  );
});

Consuming the bridge value on the order status page

On the order status page (or thank you page), read from session storage and push to dataLayer:

// On the order status / thank you page
(function () {
  const pending = sessionStorage.getItem('pending_purchase');
  if (!pending) return;
 
  try {
    const payload = JSON.parse(pending);
    window.dataLayer = window.dataLayer || [];
    window.dataLayer.push({ ecommerce: null }); // Clear first
    window.dataLayer.push(payload);
    sessionStorage.removeItem('pending_purchase');
  } catch (e) {
    // Malformed payload - clear and do nothing
    sessionStorage.removeItem('pending_purchase');
  }
})();

Why the bridge is needed

Shopify's Customer Events pixels run in a sandboxed <iframe> without access to the parent page's window.dataLayer. The browser.sessionStorage object in the sandbox is shared with the parent origin, making it a reliable bridge for passing data across the iframe boundary.

Alternative: Shopify GA4 integration

Shopify has a native Google & YouTube channel integration that sends events server-to-server. The custom pixel approach is preferable when you need full control over the event payload, custom parameters, or when running a headless build where the native integration does not cover all events.

The free GA4 Ecommerce Container includes a GTM container configured to receive this dataLayer push.

Free container for this module

GA4 Ecommerce Container

Production GA4 ecommerce implementation covering the full purchase funnel: view_item, add_to_cart, begin_checkout, purchase. Includes the data layer schema and a Shopify Customer Events variant for headless builds.

Get the container →