If you’ve tried to track conversions on Shopify’s checkout, you know the pain. Shopify’s checkout runs in a sandboxed environment, which means your standard Google Tag Manager container doesn’t fire there. The solution? Shopify’s Customer Events (Custom Pixels).

In this post, I’m sharing the exact custom pixel code I use for my clients. It tracks all major e-commerce events and pushes them to the data layer in GA4’s expected format - ready for your GTM tags to pick up.

What This Code Tracks

This custom pixel captures the complete customer journey:

Shopify EventData Layer EventWhen It Fires
page_viewedshopify_page_viewEvery page load
product_viewedview_itemProduct page views
product_added_to_cartadd_to_cartAdd to cart clicks
cart_viewedview_cartCart page views
checkout_startedbegin_checkoutCheckout initiated
checkout_shipping_info_submittedadd_shipping_infoShipping details entered
payment_info_submittedadd_payment_infoPayment details entered
checkout_completedpurchaseOrder completed

The Complete Custom Pixel Code

Here’s the full code. Copy it, replace GTM-EXAMPLE with your actual GTM container ID, and paste it into your Shopify Custom Pixel.

// GTM for Shopify Custom Pixel by Adnan Agic
// Contact: agic_adnan@icloud.com
// Replace GTM-EXAMPLE with your GTM container ID 

window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}

(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-EXAMPLE');

// Purchase Event
analytics.subscribe("checkout_completed", (event) => {
  dataLayer.push({ ecommerce: null });
  const items = event.data?.checkout?.lineItems?.map((item) => {
    return {
      item_id: item.variant.product.id,
      item_name: item.variant.product.title,
      price: item.variant.price.amount,
      quantity: item.quantity
    }
  });
  dataLayer.push({
    event: "purchase",
    url: event.context.document.location.href,
    ecommerce: {
      currency: event.data?.checkout?.currencyCode,
      value: event.data?.checkout?.subtotalPrice?.amount,
      transaction_id: event.data?.checkout?.order?.id,
      coupon: event.data?.checkout?.discountAllocations,
      shipping: event.data?.checkout?.shippingLine?.price?.amount,
      tax: event.data?.checkout?.totalTax?.amount,
      items: items
    }
  });
});

// Payment Info Submitted
analytics.subscribe("payment_info_submitted", (event) => {
  dataLayer.push({ ecommerce: null });
  const items = event.data?.checkout?.lineItems?.map((item) => {
    return {
      item_id: item.variant.product.id,
      item_name: item.variant.product.title,
      price: item.variant.price.amount,
      quantity: item.quantity
    }
  });
  dataLayer.push({
    event: "add_payment_info",
    url: event.context.document.location.href,
    ecommerce: {
      currency: event.data?.checkout?.currencyCode,
      value: event.data?.checkout?.subtotalPrice?.amount,
      items: items
    }
  });
});

// Shipping Info Submitted
analytics.subscribe("checkout_shipping_info_submitted", (event) => {
  dataLayer.push({ ecommerce: null });
  const items = event.data?.checkout?.lineItems?.map((item) => {
    return {
      item_id: item.variant.product.id,
      item_name: item.variant.product.title,
      price: item.variant.price.amount,
      quantity: item.quantity
    }
  });
  dataLayer.push({
    event: "add_shipping_info",
    url: event.context.document.location.href,
    ecommerce: {
      currency: event.data?.checkout?.currencyCode,
      value: event.data?.checkout?.subtotalPrice?.amount,
      items: items
    }
  });
});

// Checkout Started
analytics.subscribe("checkout_started", (event) => {
  dataLayer.push({ ecommerce: null });
  const items = event.data?.checkout?.lineItems?.map((item) => {
    return {
      item_id: item.variant.product.id,
      item_name: item.variant.product.title,
      price: item.variant.price.amount,
      quantity: item.quantity
    }
  });
  dataLayer.push({
    event: "begin_checkout",
    url: event.context.document.location.href,
    ecommerce: {
      currency: event.data?.checkout?.currencyCode,
      value: event.data?.checkout?.subtotalPrice?.amount,
      items: items
    }
  });
});

// Cart Viewed
analytics.subscribe("cart_viewed", (event) => {
  dataLayer.push({ ecommerce: null });
  const items = event.data?.cart?.lines?.map((item) => {
    return {
      item_id: item.merchandise.product.id,
      item_name: item.merchandise.product.title,
      price: item.merchandise.price.amount,
      quantity: item.quantity
    }
  });
  dataLayer.push({
    event: "view_cart",
    url: event.context.document.location.href,
    ecommerce: {
      currency: event.data?.cart?.cost?.totalAmount?.currencyCode,
      value: event.data?.cart?.cost?.totalAmount?.amount,
      items: items
    }
  });
});

// Product Added to Cart
analytics.subscribe("product_added_to_cart", (event) => {
  dataLayer.push({ ecommerce: null });
  dataLayer.push({
    event: "add_to_cart",
    url: event.context.document.location.href,
    ecommerce: {
      currency: event.data?.cartLine?.cost?.totalAmount?.currencyCode,
      value: event.data?.cartLine?.cost?.totalAmount?.amount,
      items: [{
        item_id: event.data?.cartLine?.merchandise?.product?.id,
        item_name: event.data?.cartLine?.merchandise?.product?.title,
        price: event.data?.cartLine?.merchandise?.price?.amount,
        quantity: event.data?.cartLine?.quantity
      }]
    }
  });
});

// Product Viewed
analytics.subscribe("product_viewed", (event) => {
  dataLayer.push({ ecommerce: null });
  dataLayer.push({
    event: "view_item",
    url: event.context.document.location.href,
    ecommerce: {
      currency: event.data?.productVariant?.price?.currencyCode,
      value: event.data?.productVariant?.price?.amount,
      items: [{
        item_id: event.data?.productVariant?.product?.id,
        item_name: event.data?.productVariant?.product?.title,
        price: event.data?.productVariant?.price?.amount,
        quantity: 1
      }]
    }
  });
});

// Page Viewed
analytics.subscribe("page_viewed", (event) => {
  window.dataLayer.push({
    event: "shopify_page_view",
    url: event.context.document.location.href
  });
});

How to Install This on Shopify

  1. In your Shopify admin, go to SettingsCustomer events
  2. Click Add custom pixel
  3. Name it something like “GTM Tracking Pixel”
  4. Paste the code above into the code editor
  5. Replace GTM-EXAMPLE with your actual GTM container ID
  6. Set Permission to “Not required” (or “Required” if you want it to respect consent)
  7. Click Save, then Connect
💡 Pro Tip: The permission setting controls whether the pixel fires before or after consent. If you're using a consent management platform, set it to "Required" so tracking only fires for users who consent.

Setting Up GTM to Receive These Events

Once the pixel is installed, you need to configure GTM to listen for these events and fire your tags.

Create Data Layer Variables

In GTM, create these Data Layer Variables:

Create Custom Event Triggers

Create a Custom Event trigger for each event you want to track:

Create Your Tags

Now create your Google Ads Conversion or GA4 Event tags, using the data layer variables for dynamic values and the custom event triggers to fire them.

For a detailed walkthrough on the Google Ads side, see my guide on Google Ads conversion tracking for Shopify.

Understanding the Code Structure

Let me break down what’s happening in this code so you can customize it if needed.

The GTM Container Loader

The first block loads your GTM container into the Shopify checkout environment:

(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-EXAMPLE');

This is the standard GTM snippet, modified to work within Shopify’s sandboxed checkout.

The Event Subscribers

Each analytics.subscribe() block listens for a specific Shopify event. When that event fires, the callback function:

  1. Clears any previous ecommerce data with dataLayer.push({ ecommerce: null })
  2. Maps the Shopify event data to GA4’s expected format
  3. Pushes the formatted data to the data layer

Why Clear Ecommerce Data?

The dataLayer.push({ ecommerce: null }) line before each event is crucial. GA4 and GTM can have issues if ecommerce objects from previous events persist. Clearing it ensures each event has clean, accurate data.

Customization Options

Adding More Item Parameters

You can extend the items array to include more parameters like variant, brand, or category:

const items = event.data?.checkout?.lineItems?.map((item) => {
  return {
    item_id: item.variant.product.id,
    item_name: item.variant.product.title,
    item_variant: item.variant.title,
    item_brand: item.variant.product.vendor,
    price: item.variant.price.amount,
    quantity: item.quantity
  }
});

Adding User Data for Enhanced Conversions

To enable enhanced conversions, you can add user data to the purchase event:

dataLayer.push({
  event: "purchase",
  ecommerce: {
    // ... existing ecommerce data
  },
  user_data: {
    email: event.data?.checkout?.email,
    phone_number: event.data?.checkout?.phone,
    address: {
      first_name: event.data?.checkout?.billingAddress?.firstName,
      last_name: event.data?.checkout?.billingAddress?.lastName,
      city: event.data?.checkout?.billingAddress?.city,
      region: event.data?.checkout?.billingAddress?.provinceCode,
      postal_code: event.data?.checkout?.billingAddress?.zip,
      country: event.data?.checkout?.billingAddress?.countryCode
    }
  }
});
⚠️ Important: When using enhanced conversions, the data is automatically hashed by Google's tags before being sent. However, you must ensure you have proper consent and privacy policies in place for collecting this data.

Troubleshooting Common Issues

Events not firing in GTM Preview

GTM Preview mode doesn’t work well with Shopify’s sandboxed checkout. Instead:

Purchase value showing as 0 or undefined

Check that:

Duplicate conversions

If you’re seeing duplicate conversions:

Conclusion

This custom pixel gives you complete control over e-commerce tracking on Shopify. Unlike Shopify’s built-in integrations, you can customize exactly what data is captured, add enhanced conversions, and maintain consistency with your broader GTM setup.

The code is free to use - just replace the container ID and you’re ready to go. If you run into issues or need help with a more complex implementation (like cross-domain tracking or server-side tagging), feel free to reach out.

Related Posts

Why Shopify Sales and Google Ads Sales Don't Match: Understanding the Discrepancy

10 min read

ShopifyGoogle AdsConversion TrackingAttributionEcommerceShopify Tracking Series

How to Set Up Google Ads Conversion Tracking on Shopify (2026 Guide)

5 min read

Google AdsShopifyConversion TrackingShopify Tracking Series

How to Connect Shopify to Google Merchant Center

Merchant CenterGoogle ShoppingShopifyEcommerceMerchant Center Intro Series
Adnan Agic

Adnan Agic

Google Ads Strategist & Technical Marketing Expert with 5+ years experience managing $10M+ in ad spend across 100+ accounts.

Need Help With Your Google Ads?

I help e-commerce brands scale profitably with data-driven PPC strategies.

Get In Touch
Back to Blog