Shopify GA4 Ecommerce Tracking
Implement comprehensive GA4 ecommerce tracking for your Shopify store, including product impressions, detail views, cart actions, checkout steps, and purchase transactions.
Ecommerce Events Overview
GA4 uses specific event names and parameters for ecommerce tracking. Here's how they map to Shopify:
| Shopify Action | GA4 Event | Available On | Plus Required |
|---|---|---|---|
| View collection | view_item_list |
Collection pages | No |
| View product | view_item |
Product pages | No |
| Add to cart | add_to_cart |
All pages | No |
| Remove from cart | remove_from_cart |
Cart page | No |
| View cart | view_cart |
Cart page | No |
| Begin checkout | begin_checkout |
Checkout start | Yes (Plus) |
| Add shipping info | add_shipping_info |
Checkout shipping | Yes (Plus) |
| Add payment info | add_payment_info |
Checkout payment | Yes (Plus) |
| Purchase | purchase |
Order confirmation | No |
Product Impressions (Collection Pages)
Track when customers view product collections.
Using GTM + Shopify Data Layer
1. Create Data Layer Variable for Products
In GTM, create a Custom JavaScript variable:
function() {
// Get products from Shopify collection data layer
if (window.Shopify && window.Shopify.theme && window.Shopify.theme.collection) {
return window.Shopify.theme.collection.products || [];
}
return [];
}
2. Create Trigger
- Type: Page View - Window Loaded
- Fire on:
Page Type equals collection
3. Create GA4 Tag
- Type: GA4 Event
- Event Name:
view_item_list - Parameters:
item_list_id: Collection IDitem_list_name: Collection nameitems: Products array variable
Manual Implementation
Add to your collection template (collection.liquid or theme section):
<script>
gtag('event', 'view_item_list', {
item_list_id: '{{ collection.id }}',
item_list_name: '{{ collection.title }}',
items: [
{% for product in collection.products limit: 20 %}
{
item_id: '{{ product.id }}',
item_name: '{{ product.title }}',
item_brand: '{{ product.vendor }}',
item_category: '{{ product.type }}',
item_list_name: '{{ collection.title }}',
price: {{ product.price | money_without_currency }},
index: {{ forloop.index }}
}{% unless forloop.last %},{% endunless %}
{% endfor %}
]
});
</script>
Note: Limit to first 20 products to avoid data layer size issues.
Product Detail Views
Track when customers view individual products.
Using GTM + Shopify Data Layer
Shopify automatically fires product_viewed event. Create a GTM tag to translate it:
1. Create Trigger
- Type: Custom Event
- Event name:
product_viewed
2. Create Variables
Extract product data from Shopify data layer:
// Variable: Product - Items Array
function() {
const dl = window.dataLayer || [];
for (let i = dl.length - 1; i >= 0; i--) {
if (dl[i].ecommerce && dl[i].ecommerce.detail) {
const products = dl[i].ecommerce.detail.products;
return products.map(function(p) {
return {
item_id: p.id,
item_name: p.name,
item_brand: p.brand || '',
item_category: p.category || '',
item_variant: p.variant || '',
price: parseFloat(p.price),
quantity: 1
};
});
}
}
return [];
}
3. Create GA4 Tag
- Type: GA4 Event
- Event Name:
view_item - Parameters:
currency:\{\{DLV - Currency\}\}value:\{\{DLV - Product Price\}\}items:\{\{Product - Items Array\}\}
- Trigger:
product_viewed
Manual Implementation
Add to product template (product.liquid or theme section):
<script>
gtag('event', 'view_item', {
currency: '{{ cart.currency.iso_code }}',
value: {{ product.selected_or_first_available_variant.price | money_without_currency }},
items: [{
item_id: '{{ 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 }},
quantity: 1
}]
});
</script>
Add to Cart
Track when items are added to cart.
Using GTM + Shopify Data Layer
Shopify fires product_added_to_cart automatically when customers add items.
1. Create Variables
// Variable: Add to Cart - Items
function() {
const dl = window.dataLayer || [];
for (let i = dl.length - 1; i >= 0; i--) {
if (dl[i].ecommerce && dl[i].ecommerce.add) {
return dl[i].ecommerce.add.products.map(function(p) {
return {
item_id: p.id,
item_name: p.name,
item_brand: p.brand || '',
item_category: p.category || '',
item_variant: p.variant || '',
price: parseFloat(p.price),
quantity: parseInt(p.quantity)
};
});
}
}
return [];
}
// Variable: Add to Cart - Value
function() {
const dl = window.dataLayer || [];
for (let i = dl.length - 1; i >= 0; i--) {
if (dl[i].ecommerce && dl[i].ecommerce.add) {
return dl[i].ecommerce.add.products.reduce(function(sum, p) {
return sum + (parseFloat(p.price) * parseInt(p.quantity));
}, 0);
}
}
return 0;
}
2. Create GA4 Tag
- Type: GA4 Event
- Event Name:
add_to_cart - Parameters:
currency:\{\{DLV - Currency\}\}value:\{\{Add to Cart - Value\}\}items:\{\{Add to Cart - Items\}\}
- Trigger: Custom Event
product_added_to_cart
Manual Theme Implementation
If your theme uses AJAX cart (most modern themes), listen for cart updates:
<script>
// Listen for Shopify AJAX cart add
document.addEventListener('DOMContentLoaded', function() {
const addToCartForms = document.querySelectorAll('form[action*="/cart/add"]');
addToCartForms.forEach(function(form) {
form.addEventListener('submit', function(e) {
// Get form data
const formData = new FormData(form);
const variantId = formData.get('id');
const quantity = formData.get('quantity') || 1;
// Fetch product data via AJAX
fetch('/products/' + productHandle + '.js')
.then(response => response.json())
.then(product => {
const variant = product.variants.find(v => v.id == variantId);
gtag('event', 'add_to_cart', {
currency: '{{ cart.currency.iso_code }}',
value: (variant.price / 100) * quantity,
items: [{
item_id: product.id,
item_name: product.title,
item_brand: product.vendor,
item_category: product.type,
item_variant: variant.title,
price: variant.price / 100,
quantity: parseInt(quantity)
}]
});
});
});
});
});
</script>
For themes with data attributes (like Dawn theme):
<script>
document.addEventListener('DOMContentLoaded', function() {
// Listen for variant change to get current selection
let currentVariant = {{ product.selected_or_first_available_variant | json }};
const variantSelectors = document.querySelectorAll('[name="id"]');
variantSelectors.forEach(function(selector) {
selector.addEventListener('change', function() {
const variantId = this.value;
// Fetch variant data
fetch('/variants/' + variantId + '.js')
.then(response => response.json())
.then(variant => {
currentVariant = variant;
});
});
});
// Listen for add to cart
const addToCartButtons = document.querySelectorAll('[name="add"]');
addToCartButtons.forEach(function(button) {
button.addEventListener('click', function(e) {
const form = button.closest('form');
const quantity = form.querySelector('[name="quantity"]').value || 1;
gtag('event', 'add_to_cart', {
currency: '{{ cart.currency.iso_code }}',
value: (currentVariant.price / 100) * quantity,
items: [{
item_id: '{{ product.id }}',
item_name: '{{ product.title | escape }}',
item_brand: '{{ product.vendor | escape }}',
item_category: '{{ product.type | escape }}',
item_variant: currentVariant.title,
price: currentVariant.price / 100,
quantity: parseInt(quantity)
}]
});
});
});
});
</script>
Remove from Cart
Track cart item removals.
Using GTM
Similar to add to cart, Shopify fires product_removed_from_cart:
GA4 Tag:
- Event Name:
remove_from_cart - Parameters: Same as add to cart
- Trigger: Custom Event
product_removed_from_cart
Manual Implementation
For AJAX cart removals:
<script>
// Listen for cart updates (theme-specific)
document.addEventListener('cart:updated', function(event) {
const removedItems = event.detail.removedItems || [];
if (removedItems.length > 0) {
gtag('event', 'remove_from_cart', {
currency: '{{ cart.currency.iso_code }}',
items: removedItems.map(item => ({
item_id: item.product_id,
item_name: item.title,
price: item.price / 100,
quantity: item.quantity
}))
});
}
});
</script>
View Cart
Track when customers view their cart.
Implementation
Add to cart.liquid template:
<script>
gtag('event', 'view_cart', {
currency: '{{ cart.currency.iso_code }}',
value: {{ cart.total_price | money_without_currency }},
items: [
{% for item in cart.items %}
{
item_id: '{{ item.product_id }}',
item_name: '{{ item.product.title | escape }}',
item_brand: '{{ item.vendor | escape }}',
item_category: '{{ item.product.type | escape }}',
item_variant: '{{ item.variant.title | escape }}',
price: {{ item.final_price | money_without_currency }},
quantity: {{ item.quantity }}
}{% unless forloop.last %},{% endunless %}
{% endfor %}
]
});
</script>
Begin Checkout (Shopify Plus Only)
Track when checkout begins.
Shopify Plus: checkout.liquid
Add to checkout.liquid when customer reaches first checkout step:
{% if checkout.step == 'contact_information' and checkout.customer_email == blank %}
<script>
gtag('event', 'begin_checkout', {
currency: '{{ checkout.currency }}',
value: {{ checkout.total_price | money_without_currency }},
coupon: '{% if checkout.discount %}{{ checkout.discount.code }}{% endif %}',
items: [
{% for line_item in checkout.line_items %}
{
item_id: '{{ line_item.product_id }}',
item_name: '{{ line_item.title | escape }}',
item_brand: '{{ line_item.vendor | escape }}',
item_category: '{{ line_item.product.type | escape }}',
item_variant: '{{ line_item.variant_title | escape }}',
price: {{ line_item.final_price | money_without_currency }},
quantity: {{ line_item.quantity }},
discount: {{ line_item.total_discount | money_without_currency }}
}{% unless forloop.last %},{% endunless %}
{% endfor %}
]
});
</script>
{% endif %}
Non-Plus Alternative
Use cart page or when customer clicks "Checkout" button:
<script>
document.querySelector('button[name="checkout"]').addEventListener('click', function() {
gtag('event', 'begin_checkout', {
currency: '{{ cart.currency.iso_code }}',
value: {{ cart.total_price | money_without_currency }},
items: [
{% for item in cart.items %}
{
item_id: '{{ item.product_id }}',
item_name: '{{ item.product.title | escape }}',
price: {{ item.final_price | money_without_currency }},
quantity: {{ item.quantity }}
}{% unless forloop.last %},{% endunless %}
{% endfor %}
]
});
});
</script>
Add Shipping Info (Shopify Plus Only)
Track shipping method selection in checkout.liquid:
{% if checkout.step == 'shipping_method' %}
<script>
gtag('event', 'add_shipping_info', {
currency: '{{ checkout.currency }}',
value: {{ checkout.total_price | money_without_currency }},
coupon: '{% if checkout.discount %}{{ checkout.discount.code }}{% endif %}',
shipping_tier: '{{ checkout.shipping_method.title }}',
items: [
{% for line_item in checkout.line_items %}
{
item_id: '{{ line_item.product_id }}',
item_name: '{{ line_item.title | escape }}',
quantity: {{ line_item.quantity }}
}{% unless forloop.last %},{% endunless %}
{% endfor %}
]
});
</script>
{% endif %}
Add Payment Info (Shopify Plus Only)
Track when customer reaches payment step in checkout.liquid:
{% if checkout.step == 'payment_method' %}
<script>
gtag('event', 'add_payment_info', {
currency: '{{ checkout.currency }}',
value: {{ checkout.total_price | money_without_currency }},
coupon: '{% if checkout.discount %}{{ checkout.discount.code }}{% endif %}',
payment_type: 'credit_card',
items: [
{% for line_item in checkout.line_items %}
{
item_id: '{{ line_item.product_id }}',
item_name: '{{ line_item.title | escape }}',
quantity: {{ line_item.quantity }}
}{% unless forloop.last %},{% endunless %}
{% endfor %}
]
});
</script>
{% endif %}
Purchase Event (All Stores)
Track completed purchases on order confirmation page.
Go to: Settings → Checkout → Order status page → Additional scripts
<script>
gtag('event', 'purchase', {
transaction_id: '{{ order.order_number }}',
affiliation: '{{ shop.name }}',
value: {{ total_price | money_without_currency }},
currency: '{{ currency }}',
tax: {{ tax_price | money_without_currency }},
shipping: {{ shipping_price | money_without_currency }},
coupon: '{% if discount %}{{ discount.code }}{% endif %}',
items: [
{% for line_item in line_items %}
{
item_id: '{{ line_item.sku }}',
item_name: '{{ line_item.title | escape }}',
item_brand: '{{ line_item.vendor | escape }}',
item_category: '{{ line_item.product.type | escape }}',
item_variant: '{{ line_item.variant_title | escape }}',
price: {{ line_item.final_price | money_without_currency }},
quantity: {{ line_item.quantity }},
discount: {{ line_item.total_discount | money_without_currency }}
}{% unless forloop.last %},{% endunless %}
{% endfor %}
]
});
</script>
Testing Your Ecommerce Tracking
1. Use GA4 DebugView
- Enable debug mode
- Walk through full purchase funnel
- Verify all events fire with correct parameters
2. Verify Items Array Structure
Each item must include:
item_id(required)item_name(required)price(recommended)quantity(recommended)
3. Check Value Calculations
valueshould be numeric (no currency symbols)- Convert Shopify money format:
\{\{ price | money_without_currency \}\} - Multiply price by quantity for multi-item carts
4. Test Edge Cases
- Multiple items in cart
- Apply discount codes
- Remove items from cart
- Different shipping methods (Plus)
- Guest vs logged-in checkout
Common Issues
Duplicate Purchase Events
Cause: Multiple GA4 implementations or page refreshes.
Fix:
- Check for duplicate scripts in Additional Scripts
- Implement transaction deduplication
- Ensure purchase event fires only once per transaction
// Add transaction check
if (!localStorage.getItem('ga4_purchase_' + transactionId)) {
gtag('event', 'purchase', {...});
localStorage.setItem('ga4_purchase_' + transactionId, 'true');
}
Missing Checkout Events
Cause: Non-Plus store or incorrect step detection.
Fix: Only Plus stores can track mid-checkout events. Non-Plus stores can only track begin_checkout (on cart page) and purchase (order confirmation).
Incorrect Currency or Value
Cause: Shopify money format includes currency symbols.
Fix: Always use money_without_currency filter:
{{ price | money_without_currency }} // Correct: 19.99
{{ price }} // Wrong: $19.99
Items Array Empty
Cause: Variable not capturing product data correctly.
Fix: Log data layer to console and verify structure matches your variable paths.
Next Steps
- GA4 Event Tracking - Additional custom events
- Shopify Data Layer - Full data layer reference
- Events Not Firing - Troubleshooting
For general ecommerce concepts, see GA4 Ecommerce Guide.