Implement comprehensive GA4 ecommerce tracking for your 3dcart/Shift4Shop 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 3dcart/Shift4Shop:
| Store Action | GA4 Event | Page Location | Implementation |
|---|---|---|---|
| View category | view_item_list |
Category pages | Manual |
| View product | view_item |
Product pages | Manual |
| Add to cart | add_to_cart |
All pages | Manual/GTM |
| Remove from cart | remove_from_cart |
Cart page | Manual |
| View cart | view_cart |
Cart page | Manual |
| Begin checkout | begin_checkout |
Checkout start | Manual |
| Add payment info | add_payment_info |
Checkout | Manual |
| Purchase | purchase |
Order confirmation | Manual |
Product Impressions (Category Pages)
Track when customers view product categories and listings.
Using 3dcart Template on Category Pages
Add this code to your category template or Global Footer with page detection:
<script>
// Only fire on category pages
if (window.location.pathname.indexOf('/category') !== -1 ||
document.querySelector('.category-page, .product-listing')) {
// Wait for page to load
window.addEventListener('load', function() {
var products = [];
var productElements = document.querySelectorAll('.product-item, .product-box');
productElements.forEach(function(element, index) {
var productId = element.getAttribute('data-product-id') || '';
var productName = element.querySelector('.product-name, .product-title');
var productPrice = element.querySelector('.product-price, .price');
if (productId || productName) {
products.push({
item_id: productId,
item_name: productName ? productName.textContent.trim() : '',
index: index,
price: productPrice ? parseFloat(productPrice.textContent.replace(/[^0-9.]/g, '')) : 0,
item_list_name: '[categoryname]',
item_list_id: '[categoryid]'
});
}
});
if (products.length > 0) {
gtag('event', 'view_item_list', {
item_list_id: '[categoryid]',
item_list_name: '[categoryname]',
items: products.slice(0, 20) // Limit to first 20 products
});
}
});
}
</script>
Alternative: Hardcode Product Data
For more reliable tracking, hardcode product data in category template:
<script>
gtag('event', 'view_item_list', {
item_list_id: '[categoryid]',
item_list_name: '[categoryname]',
items: [
{
item_id: 'PRODUCT_ID_1',
item_name: 'Product Name 1',
price: 29.99,
index: 0,
item_list_name: '[categoryname]'
},
{
item_id: 'PRODUCT_ID_2',
item_name: 'Product Name 2',
price: 39.99,
index: 1,
item_list_name: '[categoryname]'
}
// Add more products as needed
]
});
</script>
Product Detail Views
Track when customers view individual product pages.
Product Page Implementation
Add to product template or Global Footer with page detection:
<script>
// Only fire on product pages
if (window.location.pathname.indexOf('/product') !== -1 ||
document.querySelector('.product-page, .product-details')) {
// Fire view_item event
gtag('event', 'view_item', {
currency: 'USD', // Change to your currency
value: parseFloat('[productprice]'),
items: [{
item_id: '[productid]',
item_name: '[productname]',
price: parseFloat('[productprice]'),
item_brand: '[manufacturer]', // If available
item_category: '[categoryname]',
quantity: 1
}]
});
}
</script>
With Product Variants
If tracking product options/variants:
<script>
if (window.location.pathname.indexOf('/product') !== -1) {
// Get selected variant
var variantSelect = document.querySelector('select[name="options"], .product-options select');
var selectedVariant = variantSelect ? variantSelect.options[variantSelect.selectedIndex].text : '';
gtag('event', 'view_item', {
currency: 'USD',
value: parseFloat('[productprice]'),
items: [{
item_id: '[productid]',
item_name: '[productname]',
item_variant: selectedVariant,
price: parseFloat('[productprice]'),
item_category: '[categoryname]',
quantity: 1
}]
});
}
</script>
Add to Cart
Track when items are added to the shopping cart.
Standard Add to Cart Tracking
<script>
document.addEventListener('DOMContentLoaded', function() {
// Find add to cart buttons (adjust selector for your theme)
var addToCartForms = document.querySelectorAll('form[action*="addtocart"], .add-to-cart-form');
addToCartForms.forEach(function(form) {
form.addEventListener('submit', function(e) {
// Get quantity
var quantityInput = form.querySelector('input[name="quantity"]');
var quantity = quantityInput ? parseInt(quantityInput.value) : 1;
// Get product data
var productId = form.querySelector('input[name="productid"]')?.value || '[productid]';
var productPrice = parseFloat('[productprice]');
// Fire add_to_cart event
gtag('event', 'add_to_cart', {
currency: 'USD',
value: productPrice * quantity,
items: [{
item_id: productId,
item_name: '[productname]',
price: productPrice,
quantity: quantity,
item_category: '[categoryname]'
}]
});
});
});
});
</script>
AJAX Add to Cart
If your theme uses AJAX cart functionality:
<script>
document.addEventListener('DOMContentLoaded', function() {
// Listen for AJAX cart add (adjust for your theme's AJAX method)
var addToCartButtons = document.querySelectorAll('.ajax-add-to-cart, [data-ajax-cart]');
addToCartButtons.forEach(function(button) {
button.addEventListener('click', function() {
var productId = button.getAttribute('data-product-id') || '[productid]';
var productName = button.getAttribute('data-product-name') || '[productname]';
var productPrice = button.getAttribute('data-product-price') || '[productprice]';
var quantity = 1;
// Get quantity from nearby input if exists
var qtyInput = button.closest('.product-item')?.querySelector('input[name="quantity"]');
if (qtyInput) {
quantity = parseInt(qtyInput.value) || 1;
}
gtag('event', 'add_to_cart', {
currency: 'USD',
value: parseFloat(productPrice) * quantity,
items: [{
item_id: productId,
item_name: productName,
price: parseFloat(productPrice),
quantity: quantity
}]
});
});
});
});
</script>
Remove from Cart
Track when items are removed from the cart.
Cart Page Remove Tracking
<script>
document.addEventListener('DOMContentLoaded', function() {
// Only on cart page
if (window.location.pathname.indexOf('/cart') !== -1) {
// Find remove buttons
var removeButtons = document.querySelectorAll('.remove-item, [data-remove-cart], a[href*="remove"]');
removeButtons.forEach(function(button) {
button.addEventListener('click', function() {
// Get product data from cart row
var cartRow = button.closest('tr, .cart-item');
var productId = cartRow?.getAttribute('data-product-id') || '';
var productName = cartRow?.querySelector('.product-name, .item-name')?.textContent || '';
var productPrice = cartRow?.querySelector('.price, .item-price')?.textContent || '0';
var quantity = cartRow?.querySelector('.quantity, input[name="quantity"]')?.value || '1';
gtag('event', 'remove_from_cart', {
currency: 'USD',
value: parseFloat(productPrice.replace(/[^0-9.]/g, '')) * parseInt(quantity),
items: [{
item_id: productId,
item_name: productName.trim(),
price: parseFloat(productPrice.replace(/[^0-9.]/g, '')),
quantity: parseInt(quantity)
}]
});
});
});
}
});
</script>
View Cart
Track when customers view their shopping cart.
Cart Page View Tracking
<script>
// Only on cart page
if (window.location.pathname.indexOf('/cart') !== -1) {
window.addEventListener('load', function() {
var cartItems = [];
var cartRows = document.querySelectorAll('.cart-item, table.cart tbody tr');
cartRows.forEach(function(row) {
var productId = row.getAttribute('data-product-id') ||
row.querySelector('[data-product-id]')?.getAttribute('data-product-id') || '';
var productName = row.querySelector('.product-name, .item-name')?.textContent || '';
var productPrice = row.querySelector('.price, .item-price')?.textContent || '0';
var quantity = row.querySelector('input[name="quantity"]')?.value || '1';
if (productName) {
cartItems.push({
item_id: productId,
item_name: productName.trim(),
price: parseFloat(productPrice.replace(/[^0-9.]/g, '')),
quantity: parseInt(quantity)
});
}
});
// Get cart total
var cartTotalElement = document.querySelector('.cart-total, .total-amount, [data-cart-total]');
var cartTotal = cartTotalElement ? parseFloat(cartTotalElement.textContent.replace(/[^0-9.]/g, '')) : 0;
if (cartItems.length > 0) {
gtag('event', 'view_cart', {
currency: 'USD',
value: cartTotal,
items: cartItems
});
}
});
}
</script>
Begin Checkout
Track when customers start the checkout process.
Checkout Page Tracking
<script>
// Fire on checkout page load
if (window.location.pathname.indexOf('/checkout') !== -1 ||
window.location.pathname.indexOf('/onepage') !== -1) {
window.addEventListener('load', function() {
// Extract cart data (you may need to adjust based on your checkout template)
var checkoutItems = [];
// Method 1: Parse from checkout display
var itemRows = document.querySelectorAll('.checkout-item, .order-item');
itemRows.forEach(function(row) {
var productName = row.querySelector('.product-name, .item-name')?.textContent || '';
var productPrice = row.querySelector('.price, .item-price')?.textContent || '0';
var quantity = row.querySelector('.quantity, .qty')?.textContent || '1';
if (productName) {
checkoutItems.push({
item_name: productName.trim(),
price: parseFloat(productPrice.replace(/[^0-9.]/g, '')),
quantity: parseInt(quantity.replace(/[^0-9]/g, ''))
});
}
});
// Get order total
var orderTotal = document.querySelector('.order-total, .total-amount');
var total = orderTotal ? parseFloat(orderTotal.textContent.replace(/[^0-9.]/g, '')) : 0;
if (checkoutItems.length > 0) {
gtag('event', 'begin_checkout', {
currency: 'USD',
value: total,
items: checkoutItems
});
}
});
}
</script>
Alternative: Trigger from Cart Page
Track when "Checkout" button is clicked on cart page:
<script>
document.addEventListener('DOMContentLoaded', function() {
var checkoutButton = document.querySelector('.checkout-button, [href*="checkout"], button[name="checkout"]');
if (checkoutButton) {
checkoutButton.addEventListener('click', function() {
// Get cart data
var cartTotal = parseFloat('[carttotal]');
gtag('event', 'begin_checkout', {
currency: 'USD',
value: cartTotal,
items: [
// Add items array if available
]
});
});
}
});
</script>
Add Payment Info
Track when customers enter payment information.
Payment Step Tracking
<script>
// Detect when on payment step of checkout
if (window.location.pathname.indexOf('/checkout') !== -1) {
// Listen for payment form interaction
var paymentForm = document.querySelector('form[name="payment"], .payment-form');
if (paymentForm) {
var paymentTracked = false;
paymentForm.addEventListener('submit', function() {
if (!paymentTracked) {
paymentTracked = true;
var orderTotal = document.querySelector('.order-total, .total-amount');
var total = orderTotal ? parseFloat(orderTotal.textContent.replace(/[^0-9.]/g, '')) : 0;
gtag('event', 'add_payment_info', {
currency: 'USD',
value: total,
payment_type: 'credit_card', // Or detect actual payment method
items: [
// Add items if available
]
});
}
});
}
}
</script>
Purchase Event (Order Confirmation)
Track completed purchases on the order confirmation/receipt page.
Order Confirmation Tracking
Add to Global Footer with page detection or directly in receipt template:
<script>
// Only fire on order confirmation page
if (window.location.pathname.indexOf('/receipt') !== -1 ||
window.location.pathname.indexOf('/thankyou') !== -1 ||
document.querySelector('.order-confirmation, .receipt-page')) {
// Use 3dcart template variables for transaction data
gtag('event', 'purchase', {
transaction_id: '[invoicenumber]',
affiliation: 'Online Store',
value: parseFloat('[invoicetotal]'),
currency: 'USD',
tax: parseFloat('[invoicetax]'),
shipping: parseFloat('[invoiceshipping]'),
items: [
// See below for items array implementation
]
});
}
</script>
Complete Purchase Tracking with Items
For complete tracking with all order items:
<script>
if (window.location.pathname.indexOf('/receipt') !== -1) {
// Parse order items from receipt page
var orderItems = [];
var itemRows = document.querySelectorAll('.order-item, .receipt-item, table.order tbody tr');
itemRows.forEach(function(row) {
var productName = row.querySelector('.product-name, .item-name')?.textContent || '';
var productSku = row.querySelector('.sku, .product-sku')?.textContent || '';
var productPrice = row.querySelector('.price, .item-price')?.textContent || '0';
var quantity = row.querySelector('.quantity, .qty')?.textContent || '1';
if (productName) {
orderItems.push({
item_id: productSku.trim(),
item_name: productName.trim(),
price: parseFloat(productPrice.replace(/[^0-9.]/g, '')),
quantity: parseInt(quantity.replace(/[^0-9]/g, ''))
});
}
});
// Fire purchase event
gtag('event', 'purchase', {
transaction_id: '[invoicenumber]',
affiliation: 'Online Store',
value: parseFloat('[invoicetotal]'),
currency: 'USD',
tax: parseFloat('[invoicetax]'),
shipping: parseFloat('[invoiceshipping]'),
coupon: '[couponcode]', // If coupon was used
items: orderItems
});
// Prevent duplicate tracking on page refresh
sessionStorage.setItem('purchase_tracked_[invoicenumber]', 'true');
}
</script>
Prevent Duplicate Purchase Events
Add deduplication to prevent multiple purchase events on page refresh:
<script>
if (window.location.pathname.indexOf('/receipt') !== -1) {
var orderId = '[invoicenumber]';
var storageKey = 'purchase_tracked_' + orderId;
// Only fire if not already tracked
if (!sessionStorage.getItem(storageKey)) {
gtag('event', 'purchase', {
transaction_id: orderId,
value: parseFloat('[invoicetotal]'),
currency: 'USD',
tax: parseFloat('[invoicetax]'),
shipping: parseFloat('[invoiceshipping]'),
items: [
// Items array
]
});
// Mark as tracked
sessionStorage.setItem(storageKey, 'true');
}
}
</script>
GTM Implementation
For Google Tag Manager implementation of ecommerce events:
Create Data Layer on Product Pages
<script>
window.dataLayer = window.dataLayer || [];
dataLayer.push({
'event': 'view_item',
'ecommerce': {
'currency': 'USD',
'value': parseFloat('[productprice]'),
'items': [{
'item_id': '[productid]',
'item_name': '[productname]',
'price': parseFloat('[productprice]'),
'item_category': '[categoryname]',
'quantity': 1
}]
}
});
</script>
Create GTM Triggers and Tags
Create Trigger:
- Type: Custom Event
- Event name:
view_item
Create GA4 Event Tag:
- Event Name:
view_item - Configuration Tag: Select your GA4 Config tag
- Ecommerce Data: Use Data Layer variables
- Event Name:
Repeat for other ecommerce events
See GTM Data Layer Guide for complete GTM setup.
Testing Ecommerce Tracking
Use GA4 DebugView
Enable Debug Mode:
- Add
?debug_mode=trueto URL - Or install GA Debugger extension
- Add
Test Complete Funnel:
- View category page → Check
view_item_list - View product page → Check
view_item - Add to cart → Check
add_to_cart - View cart → Check
view_cart - Start checkout → Check
begin_checkout - Complete purchase → Check
purchase
- View category page → Check
Verify Event Parameters:
- Check items array structure
- Verify currency and values
- Confirm transaction_id is unique
Browser Console Testing
// Check dataLayer
console.log(window.dataLayer);
// Manually fire test event
gtag('event', 'purchase', {
'transaction_id': 'TEST123',
'value': 99.99,
'currency': 'USD',
'items': [{'item_id': 'TEST', 'item_name': 'Test Product', 'price': 99.99, 'quantity': 1}]
});
Common Issues
Missing Transaction IDs
Problem: Purchase events without transaction_id
Fix: Ensure [invoicenumber] template variable is used on receipt page:
transaction_id: '[invoicenumber]'
Incorrect Currency Values
Problem: Values show with currency symbols ($99.99) instead of numbers
Fix: Parse currency values:
value: parseFloat('[invoicetotal]') // Correct
value: '[invoicetotal]' // Wrong
Items Array Empty
Problem: Ecommerce events fire but items array is empty
Fix: Verify items are properly constructed and product data is available on the page
Duplicate Purchase Events
Problem: Multiple purchase events for same transaction
Fix: Use sessionStorage deduplication (see example above)
Next Steps
- GA4 Event Tracking - Custom event tracking
- GTM Data Layer - Advanced GTM setup
- Troubleshoot Events - Debug tracking issues
Additional Resources
- GA4 Ecommerce Events - Google
- 3dcart Template Variables (Shift4Shop help articles are no longer available; see your store's admin panel)
- GA4 Item Parameters