Shopify GA4 and GTM Code Setup

Seamlessly integrate Shopify with GA4 and GTM to capture accurate user behavior and product engagement data. Learn more

Ceyhun Enki Aksan
Ceyhun Enki Aksan Entrepreneur, Maker

Universal Analytics (UA) migration to Google Analytics 4 must ensure that user engagement data is not compromised. Otherwise, changes in the data used across marketing and reporting processes will inevitably occur.

note

This article may be considered an update and expansion of the previously published “Shopify - Google Analytics and Tag Manager Operations” article.

Below, I’ve also addressed a few indirect topics that I’ve frequently encountered. If you’d like to directly view explanations related to the topic headings, you can jump to the section titled Theme-Level Code Operations. While this article primarily focuses on general setup procedures rather than e-commerce-specific activities, it includes code snippets related to GTAG and Ads’ purchase and conversion events in the final section.

Shopify and Google Analytics Code Section

Shopify and WooCommerce are among the leading options in the e-commerce space. Shopify can facilitate a more efficient transition to GA4 through its WordPress plugin capabilities, while the Shopify process itself is progressing at a slightly slower pace. As is well known, Shopify provides configuration fields under Sales Channels > Online Store > Preferences for Google Analytics and Facebook Pixel integrations1.

Shopify - Google Analytics
Shopify - Google Analytics

For now, the Google Analytics section continues to use the UA-123456789-1 identifier format previously used for Universal Analytics properties. It’s certainly possible that over time, this section will be updated to recognize GA4 properties. When attempting to add a snippet for a GA4 property, as illustrated below, the system will return an error message Analytics snippet does not look valid (UA-xxxx-x)2.

<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());

  gtag('config', 'G-XXXXXXXXXX');
</script>

The page view and e-commerce tracking (enhanced ecommerce) features will not function until this section is verified. Once the relevant section is recorded alongside the UA property ID, the Additional Google Analytics JavaScript and Use Enhanced Ecommerce fields will become available1.

Shopify - Google Analytics
Shopify - Google Analytics
Shopify - Google Analytics
Shopify - Google Analytics

In summary, the snippet added to this section is inserted before the </head> tag and is called via the merchantGoogleAnalytics function. However, this does not support HTML code.

<script>window.ShopifyAnalytics.merchantGoogleAnalytics = function() {
  // code insertion area
};
</script>

When a new snippet specific to GTAG is added to this field, Shopify will remove its HTML tags. For example, if the snippet above is to be added, the relevant code will be recorded as follows.

<!-- Global site tag (gtag.js) - Google Analytics -->
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());

  gtag('config', 'G-XXXXXXXXXX');

Upon inspecting the source code, it will appear as follows:

<script>window.ShopifyAnalytics.merchantGoogleAnalytics = function() {
  <!-- Global site tag (gtag.js) - Google Analytics -->

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

  gtag('config', 'G-XXXXXXXXXX');
};
</script>

The first issue here is obviously the presence of an HTML comment line inside the JS code, styled as <!-- Global site tag (gtag.js) - Google Analytics -->. The other issue is that instead of loading the gtag.js library, the analytics.js library is being loaded. As a result, the gtag() methods will actually have no functionality.

File path called for gtag.js:
: https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX

File path called for analytics.js:
: https://www.google-analytics.com/analytics.js

Generally, store owners are not advocates of making extensive modifications to their store’s theme, and they usually prefer to perform code operations within the areas provided by Shopify. Therefore, it would be beneficial to clearly define the scope of the relevant code sections. As a result, a different solution will be required to call GTAG methods.

For information, we can similarly load the GTAG library using the append method3, just as we do for the GTM snippet.

(function(w,d,s,l,i){w[l]=w[l]||[];var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtag/js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','G-XXXXXXXXXX');

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

gtag('config', 'G-XXXXXXXXXX');
// gtag('config', 'G-XXXXXXXXXX', { 'debug_mode': true });

When testing in debug mode with 'debug_mode':true, if user_engagement, page_view, and enhanced measurement options are enabled, you can observe that GA4 receives events such as scroll initially, followed by other interactions.

GA4 DebugView
GA4 DebugView

Of course, this can be used for testing purposes, but it will not be recommended for final implementation.

GA4 DebugView
GA4 DebugView

Instead of this process, as I described in my article titled Universal Analytics (UA) to Google Analytics 4 (GA4), you can transfer your ga() events to GA4 via Embedded Site Tags and Universal Analytics event collection.

GTM Setup

The GTM snippet situation is slightly different because it’s appended via the JavaScript library. When the GTM snippet is added in the Additional Google Analytics JavaScript field, Shopify will intervene in the relevant code snippet, preserving comment lines as before.

<!-- 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 -->

After removing the HTML tags, the result will be as follows, still accompanied by comment lines. Naturally, these comment lines must be removed.

<!-- Google Tag Manager -->
(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');
<!-- End Google Tag Manager -->

However, this process only applies to the <head>...</head> tags and invalidates iframe code due to both the location where it’s placed and the filtering of HTML content. Once the relevant comment lines are removed, the GTM snippet loads successfully. This enables the GTM code to run.

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

If the goal is simply to load basic tags via GTM, this method is feasible. However, in scenarios where either the dataLayer or more extensive JavaScript operations are required, this approach will not function properly. Considering the location where the code will be inserted, it becomes unavoidable to implement operations at the theme level.

Of course, this can also be used for testing purposes, but it should be considered an approach not recommended for final implementation.

Theme-Level Code Operations

Above, I aimed to emphasize that the existing code sections currently do not support the GTAG structure and/or provide a complete solution, and therefore it is necessary to focus on alternative solutions. Under this heading, I will briefly describe how relevant code operations can be executed.

Theme Layout

Layouts are Liquid files that allow you to consolidate content (code, descriptions, etc.) that needs to be reused across multiple page types into a single location. For example, code snippets that should appear within the <head> section of all page types can be included through this file4 5 6.

Within Shopify’s Layout scope, we are provided with various customizable liquid files under a directory with the same name.

└── theme
    ├── layout
    |   ├── theme.liquid
    |   ...
    ├── templates
    ...

IMPORTANT RULES:

  1. Maintain the original formatting (markdown, HTML tags, links, etc.)
  2. Keep technical terms and proper nouns as appropriate
  3. Preserve code blocks and technical syntax exactly
  4. Maintain the same tone and style
  5. Only output the translated text, no explanations or comments

Adding Code to Your Shopify Theme

Let’s now return to the main topic: how to add GTAG and GTM codes. For this, we’ll use the theme.liquid file. You can search within the file to view the <head>...</head> and <body>...</body> tags. Now we can add our codes in the recommended format 6.

Shopify - Theme Liquid
Shopify - Theme Liquid

For Google Analytics 4 and Google Ads, you can add the GTAG or GTM setup code immediately after the <head> tag in the file. For the GTM code, in addition to this, you must also add the iframe code after the <body> tag.

<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());

  gtag('config', 'G-XXXXXXXXXX');
  gtag('config', 'AW-YYYYYYYYY');
</script>

On the other hand, as mentioned under the relevant section, your store must be on the Shopify Plus plan to use the checkout.liquid file 7.

Shopify - Theme Liquid
Shopify - Theme Liquid

If you’ve added the GTM code, you will not be able to perform any actions covering the checkout steps in stores that do not have the Shopify Plus plan. If you’re using Google Ads tags 8, you may receive a notification titled “Make sure you don’t miss any conversion by fixing your global site tag issue” in your Ads dashboard, as the relevant tags/codes will not be active on these pages.

Shopify - Theme Liquid
Shopify - Theme Liquid

IMPORTANT RULES:

  1. Maintain the original formatting (markdown, HTML tags, links, etc.)
  2. Keep technical terms and proper nouns as appropriate
  3. Preserve code blocks and technical syntax exactly
  4. Maintain the same tone and style
  5. Only output the translated text, no explanations or comments

The reason for the error can be summarized as the inability to add code to the checkout.liquid file in stores that do not use Shopify Plus, resulting in the payment steps remaining outside of the user’s browsing experience. However, please don’t worry—the payment confirmation page remains within the flow, so purchase events can still be tracked.

Since we’ve added the required code snippet into the theme.liquid file, we can now proceed to the final step.

Tracking Payment Transactions

Shopify provides us with both Liquid9 and JavaScript10 objects that can be used page-by-page. These objects can vary based on the content of each page11 12. Using these details, we can perform actions specific to each page. For now, our focus will be on the page containing the payment confirmation. To view the code addition area for the payment confirmation page (order status page), follow the instructions under Settings > Checkout > Order status page 13.

Shopify - Order Status Page
Shopify - Order Status Page

Within this section, we can utilize Liquid, HTML, and JavaScript code. The HTML and JavaScript code we add will appear immediately above the Your order is confirmed section after the payment process is completed.

Shopify - Order Status Page
Shopify - Order Status Page

The relevant section is highlighted in the red box in the above example image. After the payment process is completed, you can add any text you’d like to share with your customers in this area.

Let’s move on to how GTAG or GTM codes are added. The code we add into theme.liquid will not function on this page. However, if the UA property is enabled under the Google Analytics section, the UA tag will work on this page. To perform comprehensive operations, as previously mentioned, we also need access to the checkout.liquid file, which includes the checkout steps. Although the Order status page does not offer the same level of functionality as checkout.liquid, it still provides us with the opportunity to execute our code within the context of the payment result process.

GTAG - GA4 and Ads Code Implementation

Therefore, let’s first set up our GA4 installation using the GTAG snippet. If we plan to use the Ads conversion tag, we must also include the relevant account ID globally 14.

<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());

  gtag('set', {currency: 'USD'});
  
  gtag('config', 'G-XXXXXXXXXX');
  gtag('config', 'AW-YYYYYYYYY');
</script>

Now, let’s define the purchase events for both GA4 and Ads 8 15. We will incorporate Shopify Liquid variables into these code snippets.

Shopify - Order Status Page
Shopify - Order Status Page
Shopify - Order Status Page
Shopify - Order Status Page

Additionally, the code snippet includes a definition for the Dynamic remarketing event 16.

{% if first_time_accessed %}
<script>
    var orderProducts = [];
    var orderTotalValue = {{ total_price | times: 0.01 }};
    var orderTransactionId = '{{ order_number }}';
    var orderCurrency = '{{ order.currency }}';
    var orderTax = {{ tax_price | times: 0.01 }};
    var orderShippingPrice = {{ shipping_price | times: 0.01 }};
    var pageType = 'purchase';
    var userType = '{% if customer %}member{% else %}visitor{% endif %}';
    
    {% for line_item in line_items %}
    orderProducts.push({
        'item_id': '{{ line_item.product_id }}',
        'item_name': '{{ line_item.title }}',
        'quantity': {{ line_item.quantity }},
        'price': {{ line_item.line_price | times: 0.01 }}
    });
    {% endfor %}
</script>
    
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
    
gtag('set', { 'currency': orderCurrency });

gtag('config', 'G-XXXXXXXXXX');
// gtag('config', 'G-XXXXXXXXXX', { 'debug_mode': true });
gtag('config', 'AW-YYYYYYYYY');
// gtag('config', 'AW-YYYYYYYYY', { 'allow_enhanced_conversions': true });
</script>
    
<script>
gtag('event', 'purchase', {
    'transaction_id': orderTransactionId,
    'value': orderTotalValue,
    'tax': orderTax,
    'shipping': orderShippingPrice,
    'items': orderProducts
});
</script>
    
<script>
gtag('event', 'conversion', {
    'send_to': 'AW-YYYYYYYYY/AB12CD34EF56GH78IJ90',
    'value': orderTotalValue,
    'transaction_id': orderTransactionId
});
</script>
    
<script>
gtag('event', 'purchase', {
    'send_to': 'AW-YYYYYYYYY',
    'value': orderTotalValue,
    'items': orderProducts,
    'google_business_vertical': 'retail'
});
</script>
{% endif %}
    
You can also use **Enhanced conversions**[^13]. In this case, we can include customer information in the code snippet.
{% if customer.accepts_marketing == true %}
gtag('set', 'user_data', {
    'email': '{{ customer.email }}',
    'phone_number': '{{ customer.phone }}',
    'address': [{
        'first_name': '{{ customer.first_name }}',
        'last_name': '{{ customer.last_name }}',
        'street': '{{ customer.addresses[0].street }}',
        'city': '{{ customer.addresses[0].city }}',
        'region': '{{ customer.addresses[0].province }}',
        'postal_code': '{{ customer.addresses[0].zip }}'
    }]
});
{% endif %}

The above code snippet can be added before the config. The relevant code snippet will run only if the customer has approved the use of their data for marketing purposes.

Adding GTM Tags

Using GTM instead of GTAG will certainly enable more centralized and efficient management of tags and code. However, I would like to reiterate that this section should be included within the <body> tag.

<script>
var orderProducts = [];
window.dataLayer = window.dataLayer || []; 
window.dataLayer.push({
    'event': 'info',
    'orderProducts': [{% for line_item in line_items %}{
        'item_id': '{{ line_item.product_id }}',
        'item_name': '{{ line_item.title }}',
        'quantity': {{ line_item.quantity }},
        'price': {{ line_item.line_price | times: 0.01 }}
    }{% endfor %},],
    'orderTotalValue': {{ total_price | times: 0.01 }},
    'orderTransactionId': '{{ order_number }}',
    'orderCurrency': '{{ order.currency }}',
    'orderTax': {{ tax_price | times: 0.01 }},
    'orderQuantity': {{ line_item.quantity }},
    'orderShippingPrice': {{ shipping_price | times: 0.01 }},
    'pageType': 'purchase',
    'userType': '{% if customer %}member{% else %}visitor{% endif %}'
});
</script>

<!-- 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-ABCDEF');</script>
<!-- End Google Tag Manager -->

<!-- Google Tag Manager (noscript) -->
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-ABCDEF" height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<!-- End Google Tag Manager (noscript) -->

The dataLayer content above can certainly be enriched with many pieces of information such as coupon code, product brand, product type, total number of products, and payment method, depending on specific requirements. It’s also essential to fetch the relevant data from GTM variables in addition.

In Summary

I’ve attempted to outline what can be accomplished for the transition process from Universal Analytics to GA4, within different scopes of operations based on Shopify. The sooner and more efficiently the transition process is completed, the sooner data collection will begin. When new conversion tracking tags and re-targeting campaigns are also considered, no time is wasted in reorganizing campaigns.

For more comprehensive solutions (covering all e-commerce activities, comprehensive dataLayer structures, third-party application integrations, consent management integrations, etc.), the Google Analytics 4 & Google Tag Manager solutions such as Shopify App Store and Analyzify can be explored.

Footnotes

  1. Setting up Google Analytics. Shopify Help Center 2
  2. Google Analytics. Shopify Help Center
  3. Element.append. mdn web docs
  4. Layouts. Shopify Developers Platform
  5. How To Add Google Analytics 4 to Shopify Via GTM
  6. Editing theme code. Shopify Help Center 2
  7. Google Tag Manager. Shopify Help Center
  8. Google Ads conversion tracking. Shopify Help Center 2
  9. Liquid objects. Shopify Developers Platform
  10. Add conversion tracking to your order status page. Shopify Help Center
  11. The checkout object. Shopify Developers Platform
  12. The order object. Shopify Developers Platform
  13. Customize the order status page. Shopify Help Center
  14. Setting up Google Analytics 4 (GA4) on Shopify
  15. Purchase. Google Analytics 4
  16. Dynamic remarketing events and parameters. Google Ads Help