How To Install GA4 On Shopify Using Customer Events, Pixels, and GTM

Update: Shopify now has official documentation on adding GTM using Pixels.

To add Google Analytics 4 to the Shopify checkout process using customer events with a custom pixel, use the code snippet further down the page. To get started, go to “Settings > Customer events > Add custom pixel”.

Shopify custom pixel for GA4 using customer events

However, before you add the pixel code snippet, there are a few caveats I noticed with GA4 when applied using Shopify customer events and a custom pixel:

  1. Event tracking is not 100% reliable. Maybe not even 90%. I run many tests, but some events only work some of the time. These events were primarily checkout_started, payment_info_submitted, and checkout_completed. I suspect this is an implementation feature on the Shopify end more than a bug. But, still, I can not reason with the behavior.
  2. You may need to “Connect” a custom pixel and then “Disconnect” and “Connect” again to flush your custom pixel code.
  3. Eventually, Google may add a Shopify App pixel. If and when they do, you should switch to their App pixel instead of this custom setup.
  4. I recommend setting up a separate GTM Container ID and GA4 property just for the Shopify customer event pixels. Until it’s more clear how Shopify will improve the customer events and custom pixels, I don’t see how they can be used for production reliably.

Now, for the code, ensure you replace the XXXXXXXXXX in the snippet with your Google Tag Manager ID (Container ID). XXXXXXXXXX is in two places in the code.

const script = document.createElement('script');
script.setAttribute('src', 'https://www.googletagmanager.com/gtag/js?id=XXXXXXXXXX');
script.setAttribute('async', '');
document.head.appendChild(script);

window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'XXXXXXXXXX');

analytics.subscribe("search_submitted", (event) => {
  gtag("event", "search", {search_term: event.data.searchResult.query});
  gtag("event", "view_search_results", {
    search_term: event.data.searchResult.query,
    items: []
  })
});

analytics.subscribe("collection_viewed", (event) => {
  gtag("event", "view_item_list", {
    item_list_id: event.data.collection.id,
    item_list_name: event.data.collection.title,
    items: []
  });
});

analytics.subscribe("product_added_to_cart", (event) => {

  let totalPrice = event.data.cartLine.merchandise.price.amount * event.data.cartLine.quantity;
  
  gtag("event", "add_to_cart", {
    currency: event.data.cartLine.merchandise.price.currencyCode,
    value: totalPrice.toFixed(2),
    items: [
      {
        item_name: event.data.cartLine.merchandise.product.title,
        item_id: event.data.cartLine.merchandise.sku,
        item_variant: event.data.cartLine.merchandise.title,
        currency: event.data.cartLine.merchandise.price.currencyCode,
        item_brand: event.data.cartLine.merchandise.product.vendor,
        price: event.data.cartLine.merchandise.price.amount,
        quantity: event.data.cartLine.quantity
      }
    ]
  });
});

function ga4CheckoutEvents(event) {
  let checkout = event.data.checkout;
  let lineItems = [];

  for (const checkoutLineItem of event.data.checkout.lineItems){
    lineItems.push({
      item_id: checkoutLineItem.variant.sku,
      item_name: checkoutLineItem.title,
      item_variant: checkoutLineItem?.variant.title,
      currency: checkoutLineItem.variant.price.currencyCode,
      item_brand: checkoutLineItem.variant.product.vendor,
      price: checkoutLineItem.variant.price.amount,
      quantity: checkoutLineItem.quantity
    });
  }

  payload = {
    currency: checkout.totalPrice.currencyCode,
    value: checkout.totalPrice.amount,
    items: lineItems
  };
  
  return payload;
}

analytics.subscribe("product_viewed", (event) => {
  gtag("event", "view_item", {
    currency: event.data.productVariant.price.currencyCode,
    value: event.data.productVariant.price.amount,
    items: [
      {
        item_id: event.data.productVariant.sku,
        item_name: event.data.productVariant.product.title,
        item_variant: event.data.productVariant.title,
        currency: event.data.productVariant.price.currencyCode,
        item_brand: event.data.productVariant.product.vendor,
        price: event.data.productVariant.price.amount,
      }
    ]
  });
});

analytics.subscribe("checkout_started", (event) => {
  gtag("event", "begin_checkout", ga4CheckoutEvents(event));
});

analytics.subscribe("payment_info_submitted", (event) => {
  gtag("event", "add_payment_info", ga4CheckoutEvents(event));
});

analytics.subscribe("checkout_completed", (event) => {
  
  let payload = ga4CheckoutEvents(event);
  let checkout = event.data.checkout;

  payload.transaction_id = checkout.order?.id || checkout.token;
  payload.shipping = checkout.shippingLine?.price.amount || checkout.shipping_line?.price.amount || 0;
  payload.tax = checkout.totalTax?.amount || 0;
  
  gtag("event", "purchase", payload);
});

Google Sales Channel GA4

UPDATE: Google has just released their version of GA4 support through their Google Shopify sales channel app. However, you may still find a need for custom GA4 events and want to add more product-specific data to e-commerce events. If this sounds like you, then customer events and pixels may still be the right solution.

I’ll be digging into the “official” GA4 support provided by Google’s app at some point. However, though I have not vetted the app’s GA4 support deeply, I still recommend using it over customer events for those with elementary GA4 needs.

google GA4 shopify

Closing Thoughts

If you wish to dive deeper into the Shopify customer events API, see their documentation on customer events. Also, see a full list of Google Analytics 4 gtag events. When you see the list of gtag events and Shopify events, you will notice that Shopify has a very limited list of events compared to GA4. The above JavaScript code snippet accounts for all the events Shopify provides to date.

11 thoughts on “How To Install GA4 On Shopify Using Customer Events, Pixels, and GTM

  1. Thanks, this is helpful. In the Google Shopify App (Sales Channel), I was able to connect to GA4 here in addition to Google Ad Account. When testing, I am seeing checkout related “things” happening, but I do not see in GA4 the “checkout_started” event. I’m only seeing “form_started” and “form_submit” events when clicking checkout, and not the event I’m looking for. I do see “view_item” and “add_to_cart”, so some are working. I’m using the Shopify Dawn theme, so wondering if this is theme related (missing something), or a glitch in the pixel integration. Curious your thoughts on this last missing piece.

    1. Hey Brian,

      The official Google App for Shopify just released GA4 support. I recommend using the app moving forward. For some, a custom solution like this might still be helpful, but I’m moving all my Shopify sites to the GA4 integration provided by the app now.

      1. When moving to the new solution how are you handling events for G4 and passing data to those events. we are not seeing any data in our purchase events or product level events.

        1. The sites I’ve moved GA4 integration over to the Google App are elementary. However, the customer events system may still be the better option for custom events and applying specific data to GA4 events for now. I have not gone through and vetted the Google App GA4 integration support completely yet. So, I’m not 100% sure about what is lacking, I may write a post about it in the future.

  2. Hi Kevin,

    I’ve implemented your solution above and also the official Google App on two separate GA4 properties. When comparing the two, there is a drastic difference in reported revenue, though close to the same number of transactions. Your customer events solution seems to report twice the revenue of the Google App property, and both are way above what Shopify analytics reports internally. Any thoughts on what might be at the root of this? Again, vastly over-reporting of revenue (almost 3x), but similar counts of purchases. Thanks!

    1. Hey Ian,

      I have not seen this on my end when doing my initial testing. However, things might have changed. I wonder if | divided_by: 100.0 is needed for the $ amounts.

      When working with Shopify liquid templates, it can be helpful to use the “divided_by” filter to perform mathematical operations. This filter allows you to divide a number by a specified value, which can be useful for calculating percentages or other ratios. For example, if you wanted to calculate 25% of a price, you could use the following code:

      {{ price | divided_by: 4 }}

      This would divide the price by 4 (which is the same as multiplying by 0.25) and return the result. Keep in mind that the “divided_by” filter only works with numbers, so you may need to use other filters or functions to convert your data if necessary. Overall, using filters like “divided_by” can help you streamline your Shopify liquid templates and make your code more efficient.

      1. Hi Kevin,

        Thanks for your reply and advice. I’m still confused, however, as to how this is happening. It seems that although your solution (using ‘divided_by’) could result in having a more accurate revenue number, it seems inelegant. Why should I have to divide the output Shopify is providing before it gets to GA4 for it to be accurate? Something more fundamental is causing this to occur in the first place.

        Initially I thought that because I have parallel implementations (your customer events code and the Google Channel) that the gtag event calls are dispatching to both properties, but my understanding is the customer events is a sandboxed environment, and so the calls in there should not interact with other implementations. Is that correct? I don’t even see the tag loaded in tag assistant, nor do the events show up in GA4 debug view, for the customer events solution.

        Anyways, just some additional thoughts I wanted to share.

  3. Hey Kevin, thanks a lot for the article on the custom pixel. Reality is since I switched to the GA4 app, I’m also experiencing a lot of data discrepancies between Shopify purchases data and GA4 actual purchases data. I’m experiencing 10 to 50% data discrepancies on the Google & YouTube sales channel (10 to 50% less conversions). Would love to know if you’re experiencing the same issue. Google Support is definitely not helpful.

  4. Hi Kevin, thanks for your post. Quick question, when you subscribe to an event, have you tried sending an event to the datalayer instead of executing the gtag command? I’ve tried using a datalayer push, which arrives at the datalayer in Sandbox, but I couldn’t make any tag work with that datalayer event.
    Best,
    Leandro.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.