GA4 ecommerce tracking is foundational to understanding how customers interact with your Shopify store. Without proper implementation, you are flying blind on critical questions: which products do customers view but not buy? Where in the checkout funnel are you losing revenue? Which marketing channels drive the highest-value customers? Which collection pages generate the most engagement?
The challenge on Shopify is that the platform’s architecture — with its controlled checkout, multi-step purchase flow, and varied theme structures — requires careful implementation to capture the complete customer journey. The built-in Google channel provides basic tracking, but for comprehensive ecommerce analytics, you need a custom implementation via Google Tag Manager.
This guide covers the complete GA4 ecommerce tracking implementation for Shopify, from the data layer foundation through to advanced configuration patterns. Every code example is production-tested on real Shopify stores. For the analytics strategy that sits on top of this tracking, see our guide on what to track in your ecommerce analytics.
The GA4 ecommerce event model
GA4 uses a predefined set of ecommerce events that map to stages in the customer journey. Implementing these specific event names (exactly as documented) unlocks GA4’s built-in ecommerce reports, monetisation reports, and purchase journey funnel.
| Event name | When to fire | Required parameters |
|---|---|---|
view_item_list | Collection page / product list viewed | item_list_id, item_list_name, items[] |
select_item | Product clicked from a list | item_list_id, items[] |
view_item | Product page viewed | currency, value, items[] |
add_to_cart | Product added to cart | currency, value, items[] |
remove_from_cart | Product removed from cart | currency, value, items[] |
view_cart | Cart page viewed | currency, value, items[] |
begin_checkout | Checkout initiated | currency, value, items[] |
add_shipping_info | Shipping method selected | currency, value, shipping_tier, items[] |
add_payment_info | Payment information submitted | currency, value, payment_type, items[] |
purchase | Order completed | transaction_id, currency, value, tax, shipping, items[] |
Each event includes an items array containing product details. The item parameters are consistent across events: item_id, item_name, item_brand, item_category, item_variant, price, and quantity.
Implementation options for Shopify
There are three approaches to implementing GA4 ecommerce tracking on Shopify:
1. Shopify Google channel (basic)
Shopify’s built-in Google channel provides automatic GA4 tracking. It handles basic ecommerce events and purchase tracking. Limitations: no custom dimensions, limited event customisation, no control over the data layer, and poor handling of edge cases like subscription purchases or custom checkout flows.
2. GTM with custom data layer (recommended)
Install GTM via your theme’s theme.liquid file and build a comprehensive data layer that pushes ecommerce events at each stage of the customer journey. This approach gives you complete control over what data is captured, how events are structured, and which parameters are included.
3. GTM with server-side container (advanced)
Combine the GTM approach with a server-side GTM container for improved data accuracy, first-party cookie management, and privacy-compliant tracking. This is the gold standard for ecommerce analytics.
Data layer setup for Shopify
The data layer is a JavaScript object that acts as a structured communication channel between your Shopify store and GTM. Every ecommerce event you want to track starts with a data layer push.
Initialising the data layer
Add this to your theme.liquid before the GTM container snippet:
<script>
window.dataLayer = window.dataLayer || [];
dataLayer.push({
event: 'page_view',
page_type: '{{ request.page_type }}',
customer_logged_in: {{ customer | default: false | json }},
{% if customer %}
customer_id: '{{ customer.id }}',
customer_order_count: {{ customer.orders_count }},
customer_total_spent: {{ customer.total_spent | money_without_currency }},
{% endif %}
shop_currency: '{{ cart.currency.iso_code }}'
});
</script>
Collection page data layer
// In collection.liquid or main-collection.liquid
<script>
dataLayer.push({ecommerce: null}); // Clear previous ecommerce data
dataLayer.push({
event: 'view_item_list',
ecommerce: {
item_list_id: '{{ collection.handle }}',
item_list_name: '{{ collection.title | escape }}',
items: [
{% for product in collection.products limit: 20 %}
{
item_id: '{{ product.variants.first.sku | default: product.id }}',
item_name: '{{ product.title | escape }}',
item_brand: '{{ product.vendor | escape }}',
item_category: '{{ product.type | escape }}',
price: {{ product.price | money_without_currency | remove: ',' }},
index: {{ forloop.index }}
}{% unless forloop.last %},{% endunless %}
{% endfor %}
]
}
});
</script>
Product page data layer
// In product.liquid or main-product.liquid
<script>
dataLayer.push({ecommerce: null});
dataLayer.push({
event: 'view_item',
ecommerce: {
currency: '{{ cart.currency.iso_code }}',
value: {{ product.selected_or_first_available_variant.price | money_without_currency | remove: ',' }},
items: [{
item_id: '{{ product.selected_or_first_available_variant.sku | default: product.id }}',
item_name: '{{ product.title | escape }}',
item_brand: '{{ product.vendor | escape }}',
item_category: '{{ product.type | escape }}',
item_variant: '{{ product.selected_or_first_available_variant.title | escape }}',
price: {{ product.selected_or_first_available_variant.price | money_without_currency | remove: ',' }},
quantity: 1
}]
}
});
</script>
GTM tag and trigger configuration
With the data layer in place, configure GTM to capture ecommerce events and send them to GA4:
GA4 Configuration tag
Create a GA4 Configuration tag with your Measurement ID. Set it to fire on all pages. If using server-side GTM, set the transport URL to your tagging server subdomain.
GA4 Event tags
For each ecommerce event, create a GA4 Event tag:
// GTM Tag Configuration for view_item
Tag Type: Google Analytics: GA4 Event
Configuration Tag: Your GA4 Config
Event Name: view_item
Event Parameters:
- currency: {{DLV - ecommerce.currency}}
- value: {{DLV - ecommerce.value}}
- items: {{DLV - ecommerce.items}}
Trigger: Custom Event
Event Name: view_item
Data layer variables
Create Data Layer Variables in GTM for each ecommerce parameter:
ecommerce.currency— Data Layer Variableecommerce.value— Data Layer Variableecommerce.items— Data Layer Variableecommerce.transaction_id— Data Layer Variableecommerce.shipping— Data Layer Variableecommerce.tax— Data Layer Variable
Product interaction events
Add to cart tracking
Shopify’s add-to-cart mechanism varies by theme. For themes using the Fetch API (most modern themes), intercept the add-to-cart request:
// Add to cart event listener
document.addEventListener('submit', function(e) {
var form = e.target;
if (form.action && form.action.indexOf('/cart/add') !== -1) {
var formData = new FormData(form);
var variantId = formData.get('id');
// Get product data from the page
var productData = window.productJSON || {};
dataLayer.push({ecommerce: null});
dataLayer.push({
event: 'add_to_cart',
ecommerce: {
currency: window.Shopify.currency.active,
value: productData.price / 100,
items: [{
item_id: productData.sku || variantId,
item_name: productData.title,
item_brand: productData.vendor,
price: productData.price / 100,
quantity: parseInt(formData.get('quantity')) || 1
}]
}
});
}
});
Cart page view
// In cart.liquid or main-cart.liquid
<script>
dataLayer.push({ecommerce: null});
dataLayer.push({
event: 'view_cart',
ecommerce: {
currency: '{{ cart.currency.iso_code }}',
value: {{ cart.total_price | money_without_currency | remove: ',' }},
items: [
{% for item in cart.items %}
{
item_id: '{{ item.sku | default: item.variant_id }}',
item_name: '{{ item.product.title | escape }}',
item_brand: '{{ item.product.vendor | escape }}',
item_variant: '{{ item.variant.title | escape }}',
price: {{ item.final_price | money_without_currency | remove: ',' }},
quantity: {{ item.quantity }}
}{% unless forloop.last %},{% endunless %}
{% endfor %}
]
}
});
</script>
Checkout and purchase tracking
The checkout and purchase events are the most critical — and the most challenging on Shopify because of the controlled checkout environment.
Begin checkout
Track when customers click the checkout button. This typically fires on the cart page when the checkout form is submitted or the checkout button is clicked:
// Track checkout initiation
document.querySelector('[name="checkout"]')?.addEventListener('click', function() {
dataLayer.push({ecommerce: null});
dataLayer.push({
event: 'begin_checkout',
ecommerce: {
currency: window.Shopify.currency.active,
value: parseFloat(document.querySelector('[data-cart-total]')?.textContent) || 0,
items: window.cartItems || []
}
});
});
Purchase event (order status page)
// Shopify Admin → Settings → Checkout → Additional scripts
// Or Settings → Customer events
{% if first_time_accessed %}
<script>
window.dataLayer = window.dataLayer || [];
dataLayer.push({ecommerce: null});
dataLayer.push({
event: 'purchase',
ecommerce: {
transaction_id: '{{ order.name }}',
value: {{ order.total_price | money_without_currency | remove: ',' }},
tax: {{ order.tax_price | money_without_currency | remove: ',' }},
shipping: {{ order.shipping_price | money_without_currency | remove: ',' }},
currency: '{{ order.currency }}',
coupon: '{{ order.discount_applications.first.title | default: "" }}',
items: [
{% for line_item in order.line_items %}
{
item_id: '{{ line_item.sku | default: line_item.variant_id }}',
item_name: '{{ line_item.product.title | escape }}',
item_brand: '{{ line_item.vendor | escape }}',
item_variant: '{{ line_item.variant.title | escape }}',
price: {{ line_item.final_price | money_without_currency | remove: ',' }},
quantity: {{ line_item.quantity }}
}{% unless forloop.last %},{% endunless %}
{% endfor %}
]
}
});
</script>
{% endif %}
The {% if first_time_accessed %} check is critical — it prevents the purchase event from firing again if the customer refreshes the thank you page.
Custom dimensions and metrics
GA4 allows up to 50 custom event parameters and 25 custom user properties. For Shopify stores, useful custom dimensions include:
- product_type — Shopify’s product type field, useful for category-level analysis.
- collection_handle — which collection the customer was browsing when they found the product.
- customer_type — new vs returning customer (check
customer.orders_count). - discount_code — which discount code was applied, if any.
- payment_method — credit card, PayPal, Klarna, etc.
- shipping_method — standard, express, click and collect, etc.
Register these in GA4 admin under Custom Definitions before the data starts flowing, otherwise GA4 will not process them.
Revenue reconciliation with Shopify
Your GA4 revenue will almost certainly differ from Shopify’s reported revenue. Common discrepancies and their causes:
- GA4 reports lower revenue — typically caused by ad blockers preventing the purchase event from firing, or the thank you page script not loading for some users.
- GA4 reports higher revenue — usually caused by duplicate purchase events (missing the
first_time_accessedcheck) or test orders not being filtered. - Currency discrepancies — if you sell in multiple currencies, ensure the
currencyparameter matches the actual transaction currency, not the store’s base currency.
A reasonable target is GA4 capturing 85–95% of Shopify revenue. If you are below 80%, investigate your implementation. If you need higher accuracy, implement server-side GTM to bypass ad blockers.
Debugging and validation
Before going live, validate every ecommerce event:
GTM Preview Mode
Use GTM’s preview mode to verify that data layer pushes contain the correct data and that tags fire on the expected triggers. Check that ecommerce objects are properly formed and contain all required parameters.
GA4 DebugView
Enable debug mode and monitor events in real time through GA4’s DebugView. Verify that ecommerce parameters appear correctly and that item arrays contain the expected products.
Browser console checks
// Check data layer contents in browser console
console.log(JSON.stringify(dataLayer, null, 2));
// Verify specific ecommerce events
dataLayer.filter(e => e.event === 'view_item');
Advanced configuration patterns
User ID tracking
If customers log in to your Shopify store, pass their customer ID as a GA4 user property. This enables cross-device tracking and more accurate user-level analysis:
{% if customer %}
gtag('set', 'user_properties', {
customer_id: '{{ customer.id }}',
customer_type: {% if customer.orders_count > 0 %}'returning'{% else %}'new'{% endif %}
});
{% endif %}
Enhanced conversions
Google’s Enhanced Conversions improve conversion measurement by sending hashed first-party customer data (email, phone, address) alongside conversion events. On the purchase event, include hashed customer data to improve Google Ads attribution accuracy.
Proper GA4 ecommerce tracking is not optional for data-driven Shopify stores. It provides the analytical foundation for understanding your customers, optimising your marketing spend, and identifying revenue opportunities. The implementation requires technical attention to detail, but the resulting data quality justifies the investment many times over.
If your current analytics setup is incomplete or inaccurate, or if you are planning a migration from Universal Analytics patterns, we can help. As part of our SEO and analytics services and Shopify development work, we implement comprehensive GA4 tracking that gives you the data you need to make better decisions. Get in touch.