Shopify analytics tracking has always been tricky. The platform controls the checkout flow, which means the thank-you page — the most valuable page on your entire store — lives on a different domain (checkout.shopify.com) and blocks most tag managers. This guide shows you exactly how to do it right.
Why Shopify’s Built-in Analytics Isn’t Enough
Shopify’s native analytics dashboard tells you revenue and sessions. It can’t tell you:
- Which marketing channel drove each purchase (without UTM parameters)
- How users behaved before they added to cart
- Where users dropped off in your checkout funnel
- Which product categories drive the most LTV
For those answers, you need GA4. And for clean, maintainable GA4 implementation, you need GTM.
Two Integration Approaches
Option A: GTM via theme.liquid (Recommended for most stores)
Install GTM in your Shopify theme and push data layer events from Liquid templates. This works for everything except the hosted checkout.
Option B: Shopify Web Pixels API
A newer sandboxed JavaScript approach that works within Shopify’s checkout. More complex to build but works end-to-end. Best for stores on Shopify Plus or where checkout customization is already needed.
This guide covers Option A, which works for 95% of stores.
Step 1: Install GTM on Shopify
In your Shopify Admin, go to Online Store → Themes → Edit code.
Open theme.liquid (or layout/theme.liquid).
Add the GTM snippet inside <head> (after any existing scripts):
<!-- 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 -->
Add the GTM noscript directly after the opening <body> tag:
<!-- Google Tag Manager (noscript) -->
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-XXXXXXX"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<!-- End Google Tag Manager (noscript) -->
Replace GTM-XXXXXXX with your actual GTM container ID.
Step 2: Build the Data Layer in Liquid
Shopify doesn’t push a native data layer — you have to build it using Liquid variables.
Create a new snippet file: Snippets → Add a new snippet → name it gtm-datalayer.liquid
<script>
window.dataLayer = window.dataLayer || [];
{% if template == 'product' %}
window.dataLayer.push({
event: 'view_item',
ecommerce: {
currency: {{ shop.currency | json }},
value: {{ product.selected_or_first_available_variant.price | divided_by: 100.0 }},
items: [{
item_id: {{ product.selected_or_first_available_variant.sku | json }},
item_name: {{ product.title | json }},
item_category: {{ product.type | json }},
item_brand: {{ product.vendor | json }},
price: {{ product.selected_or_first_available_variant.price | divided_by: 100.0 }},
quantity: 1
}]
}
});
{% endif %}
{% if template == 'cart' %}
window.dataLayer.push({
event: 'view_cart',
ecommerce: {
currency: {{ shop.currency | json }},
value: {{ cart.total_price | divided_by: 100.0 }},
items: [
{% for item in cart.items %}
{
item_id: {{ item.variant.sku | json }},
item_name: {{ item.product.title | json }},
item_variant: {{ item.variant.title | json }},
item_category: {{ item.product.type | json }},
price: {{ item.price | divided_by: 100.0 }},
quantity: {{ item.quantity }}
}{% unless forloop.last %},{% endunless %}
{% endfor %}
]
}
});
{% endif %}
</script>
Include this snippet in theme.liquid inside <head>, after your GTM snippet:
{% render 'gtm-datalayer' %}
Step 3: Track Add to Cart
Add-to-cart tracking requires JavaScript because the action happens asynchronously. Add this to your product template or a global JS file:
// Listen for Shopify's cart update events
document.addEventListener('click', function(e) {
const addToCartBtn = e.target.closest('[data-action="add-to-cart"], .add-to-cart, #AddToCart');
if (!addToCartBtn) return;
// Get product data from the button's data attributes (you may need to add these in your theme)
const productData = {
item_id: addToCartBtn.dataset.sku || addToCartBtn.dataset.variantId,
item_name: addToCartBtn.dataset.productTitle,
price: parseFloat(addToCartBtn.dataset.price) / 100,
quantity: parseInt(document.querySelector('[name="quantity"]')?.value || 1)
};
window.dataLayer.push({ ecommerce: null });
window.dataLayer.push({
event: 'add_to_cart',
ecommerce: {
currency: window.Shopify?.currency?.active || 'USD',
value: productData.price * productData.quantity,
items: [productData]
}
});
});
Step 4: The Checkout Tracking Problem
This is where most Shopify implementations fail. Shopify’s checkout is hosted on checkout.shopify.com — a separate domain that you cannot inject arbitrary JavaScript into (unless you’re on Shopify Plus).
For standard Shopify plans: Use the Additional Scripts field in Shopify Admin → Settings → Checkout → Order status page. This is the order confirmation (thank-you) page.
Add this to the Additional Scripts field:
{% if first_time_accessed %}
<script>
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({ ecommerce: null });
window.dataLayer.push({
event: 'purchase',
ecommerce: {
transaction_id: '{{ order.order_number }}',
value: {{ order.total_price | divided_by: 100.0 }},
tax: {{ order.tax_price | divided_by: 100.0 }},
shipping: {{ order.shipping_price | divided_by: 100.0 }},
currency: '{{ order.currency }}',
coupon: '{{ order.discount_code }}',
items: [
{% for line_item in order.line_items %}
{
item_id: {{ line_item.sku | json }},
item_name: {{ line_item.title | json }},
item_variant: {{ line_item.variant.title | json }},
item_category: {{ line_item.product.type | json }},
price: {{ line_item.price | divided_by: 100.0 }},
quantity: {{ line_item.quantity }}
}{% unless forloop.last %},{% endunless %}
{% endfor %}
]
}
});
</script>
<!-- GTM snippet for the checkout thank-you page -->
<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>
{% endif %}
The {% if first_time_accessed %} Liquid condition ensures the purchase event fires only once, even if the customer refreshes the thank-you page.
Step 5: Testing Your Shopify GTM Setup
- Enable GTM Preview Mode — connect to your Shopify storefront URL (not
checkout.shopify.com) - Browse to a product page — verify
view_itemfires with correct product data - Add to cart — verify
add_to_cartfires - Complete a test order (use Shopify’s test payment gateway) — verify
purchasefires on the thank-you page with the correcttransaction_idandvalue
Debugging the checkout page: Since the checkout lives on a separate domain, Tag Assistant won’t connect automatically. Use console.log(window.dataLayer) in the Additional Scripts field to inspect the pushed data.
Common Shopify GTM Issues
GTM fires but no data in GA4: Check that your GA4 configuration tag fires on the checkout thank-you page domain. The “Your Domain” restriction in the tag configuration will block it.
Purchase value is wrong: Shopify prices in price fields are in cents (e.g., 4999 = $49.99). Always divide by 100.
Duplicate purchases: Remove any Shopify native GA4 integration if you’re managing tracking via GTM. Having both active double-counts revenue.
Our team specializes in Shopify + GA4 tracking setups, including complex multi-currency stores and Shopify Plus checkout extensibility. Contact us to audit your current setup or build it from scratch.