Webflow Ecommerce + GA4 Tracking
This guide provides comprehensive implementation instructions for tracking the complete ecommerce journey in Webflow with Google Analytics 4 (GA4), from product impressions to purchase completion.
Prerequisites
- Webflow Ecommerce plan (Standard, Plus, or Advanced)
- GA4 installed via custom code method
- Published Webflow site with ecommerce products configured
- GA4 Measurement ID (format:
G-XXXXXXXXXX)
Webflow Ecommerce Architecture
Understanding Webflow's ecommerce structure is essential for proper tracking:
Page Types
- Product Pages: Individual product detail pages (CMS Collection Pages)
- Category Pages: Collection List pages showing multiple products
- Cart Page:
/checkout- Shopping cart review - Checkout Page:
/checkout- Payment and shipping information - Order Confirmation:
/order-confirmation- Post-purchase success page
Webflow Ecommerce Objects
Webflow exposes ecommerce data through the global Webflow.commerce object on ecommerce pages. This includes:
- Cart data:
window.Webflow?.commerce?.cart - Order data: Available on order confirmation page
- Product data: Available on product pages
GA4 Ecommerce Events Overview
Implement these GA4 recommended ecommerce events:
view_item_list- Product category/collection pagesview_item- Individual product page viewsadd_to_cart- Adding products to cartremove_from_cart- Removing products from cartview_cart- Viewing the cart pagebegin_checkout- Starting the checkout processadd_payment_info- Adding payment informationpurchase- Completed purchase
Implementation Strategy
Method 1: Direct Implementation (Basic)
Install tracking code directly in Webflow Custom Code.
Method 2: Google Tag Manager (Advanced)
Use GTM with a Webflow ecommerce data layer. See GTM Data Layer Guide.
This guide focuses on Method 1 (Direct Implementation).
1. Product List Impressions (view_item_list)
Track when users see products on category or collection pages.
Implementation
Add this code to your Collection List Page template (or Project Settings > Custom Code > Footer):
<script>
document.addEventListener('DOMContentLoaded', function() {
// Find all product items in collection list
const productItems = document.querySelectorAll('.w-dyn-item');
if (productItems.length > 0) {
const items = [];
productItems.forEach(function(item, index) {
const productName = item.querySelector('.product-name')?.textContent.trim() || 'Unknown Product';
const productPrice = item.querySelector('.product-price')?.textContent.replace(/[^0-9.]/g, '') || '0';
const productLink = item.querySelector('a')?.getAttribute('href') || '';
const productId = productLink.split('/').pop() || 'unknown';
items.push({
item_id: productId,
item_name: productName,
price: parseFloat(productPrice),
index: index,
item_category: 'Collection Products' // Customize based on your collection
});
});
// Send view_item_list event
gtag('event', 'view_item_list', {
item_list_id: 'webflow_collection',
item_list_name: 'Product Collection',
items: items
});
}
});
</script>
Customize selectors: Update .product-name and .product-price to match your Webflow class names.
2. Product Detail View (view_item)
Track individual product page views.
Implementation
Add this code to your Product Template Page as an Embed element or in Custom Code:
<script>
document.addEventListener('DOMContentLoaded', function() {
// Get product data from page elements
const productName = document.querySelector('.product-name')?.textContent.trim() || 'Unknown Product';
const productPrice = document.querySelector('.product-price')?.textContent.replace(/[^0-9.]/g, '') || '0';
const productSku = document.querySelector('.product-sku')?.textContent.trim() || '';
const productCategory = document.querySelector('.product-category')?.textContent.trim() || 'Uncategorized';
// Get product ID from URL slug
const productId = window.location.pathname.split('/').pop();
// Send view_item event
gtag('event', 'view_item', {
currency: 'USD', // Change to your currency
value: parseFloat(productPrice),
items: [{
item_id: productId,
item_name: productName,
item_category: productCategory,
price: parseFloat(productPrice),
quantity: 1
}]
});
});
</script>
Using CMS Fields
For more accurate data, use Webflow's CMS fields in an Embed element on your Product Template:
<script>
// Product data from CMS (click "Insert field" to add actual CMS values)
const productData = {
id: 'PRODUCT_SKU_FIELD', // Insert SKU field
name: 'PRODUCT_NAME_FIELD', // Insert Name field
price: 'PRODUCT_PRICE_FIELD', // Insert Price field
category: 'PRODUCT_CATEGORY_FIELD' // Insert Category field
};
gtag('event', 'view_item', {
currency: 'USD',
value: parseFloat(productData.price),
items: [{
item_id: productData.id,
item_name: productData.name,
item_category: productData.category,
price: parseFloat(productData.price),
quantity: 1
}]
});
</script>
In Webflow: Use the purple "Insert field" button in the Embed element to insert actual CMS field values.
3. Add to Cart (add_to_cart)
Track when users add products to their cart.
Implementation
Add this code to Project Settings > Custom Code > Footer Code:
<script>
// Wait for Webflow commerce to load
window.Webflow = window.Webflow || [];
window.Webflow.push(function() {
// Listen for cart updates
let lastCartCount = 0;
function checkCartChanges() {
const cart = window.Webflow?.commerce?.cart;
if (cart && cart.items) {
const currentCount = cart.items.reduce((sum, item) => sum + item.count, 0);
// If count increased, an item was added
if (currentCount > lastCartCount) {
// Find the newly added item(s)
cart.items.forEach(function(item) {
gtag('event', 'add_to_cart', {
currency: cart.currency || 'USD',
value: parseFloat(item.price) * item.count,
items: [{
item_id: item.sku || item.productId,
item_name: item.name,
price: parseFloat(item.price),
quantity: item.count
}]
});
});
}
lastCartCount = currentCount;
}
}
// Check for cart changes periodically
setInterval(checkCartChanges, 1000);
// Also check on add to cart button clicks
document.addEventListener('click', function(e) {
if (e.target.matches('.w-commerce-commerceaddtocartbutton') ||
e.target.closest('.w-commerce-commerceaddtocartbutton')) {
setTimeout(checkCartChanges, 500);
}
});
});
</script>
Alternative: Button Click Tracking
A simpler approach that tracks the button click (not the actual cart addition):
<script>
document.addEventListener('click', function(e) {
const addToCartButton = e.target.closest('.w-commerce-commerceaddtocartbutton');
if (addToCartButton) {
// Get product data from the page
const productName = document.querySelector('.product-name')?.textContent.trim() || 'Unknown';
const productPrice = document.querySelector('.product-price')?.textContent.replace(/[^0-9.]/g, '') || '0';
const productId = window.location.pathname.split('/').pop();
gtag('event', 'add_to_cart', {
currency: 'USD',
value: parseFloat(productPrice),
items: [{
item_id: productId,
item_name: productName,
price: parseFloat(productPrice),
quantity: 1
}]
});
}
});
</script>
4. Remove from Cart (remove_from_cart)
Track when users remove items from their cart.
Implementation
<script>
window.Webflow = window.Webflow || [];
window.Webflow.push(function() {
let previousCart = [];
function trackCartChanges() {
const cart = window.Webflow?.commerce?.cart;
if (cart && cart.items) {
// Check for removed items
previousCart.forEach(function(prevItem) {
const currentItem = cart.items.find(item => item.productId === prevItem.productId);
// Item was removed
if (!currentItem) {
gtag('event', 'remove_from_cart', {
currency: cart.currency || 'USD',
value: parseFloat(prevItem.price) * prevItem.count,
items: [{
item_id: prevItem.sku || prevItem.productId,
item_name: prevItem.name,
price: parseFloat(prevItem.price),
quantity: prevItem.count
}]
});
}
// Quantity decreased
else if (currentItem.count < prevItem.count) {
const removedCount = prevItem.count - currentItem.count;
gtag('event', 'remove_from_cart', {
currency: cart.currency || 'USD',
value: parseFloat(prevItem.price) * removedCount,
items: [{
item_id: prevItem.sku || prevItem.productId,
item_name: prevItem.name,
price: parseFloat(prevItem.price),
quantity: removedCount
}]
});
}
});
// Update previous cart state
previousCart = JSON.parse(JSON.stringify(cart.items));
}
}
// Monitor cart changes
setInterval(trackCartChanges, 1000);
});
</script>
5. View Cart (view_cart)
Track when users view their cart page.
Implementation
Add this code to the Cart Page (/checkout) as an Embed element or page-level custom code:
<script>
document.addEventListener('DOMContentLoaded', function() {
// Check if we're on the cart page (not checkout)
if (window.location.pathname === '/checkout' && !window.location.search.includes('payment')) {
window.Webflow = window.Webflow || [];
window.Webflow.push(function() {
const cart = window.Webflow?.commerce?.cart;
if (cart && cart.items && cart.items.length > 0) {
const items = cart.items.map(function(item) {
return {
item_id: item.sku || item.productId,
item_name: item.name,
price: parseFloat(item.price),
quantity: item.count
};
});
gtag('event', 'view_cart', {
currency: cart.currency || 'USD',
value: parseFloat(cart.subtotal),
items: items
});
}
});
}
});
</script>
6. Begin Checkout (begin_checkout)
Track when users start the checkout process.
Implementation
<script>
document.addEventListener('DOMContentLoaded', function() {
// Detect checkout start (when payment/shipping form is visible)
const checkoutForm = document.querySelector('.w-commerce-commercecheckoutform');
if (checkoutForm) {
window.Webflow = window.Webflow || [];
window.Webflow.push(function() {
const cart = window.Webflow?.commerce?.cart;
if (cart && cart.items && cart.items.length > 0) {
const items = cart.items.map(function(item) {
return {
item_id: item.sku || item.productId,
item_name: item.name,
price: parseFloat(item.price),
quantity: item.count
};
});
gtag('event', 'begin_checkout', {
currency: cart.currency || 'USD',
value: parseFloat(cart.subtotal),
items: items
});
}
});
}
});
</script>
7. Purchase (purchase)
Track completed purchases on the order confirmation page.
Implementation
Add this code to Project Settings > Custom Code > Footer Code or as page-level code on the order confirmation page:
<script>
document.addEventListener('DOMContentLoaded', function() {
// Check if we're on the order confirmation page
if (window.location.pathname.includes('/order-confirmation') ||
document.querySelector('.w-commerce-commerceorderconfirmationcontainer')) {
window.Webflow = window.Webflow || [];
window.Webflow.push(function() {
// Get order data from Webflow
const orderData = window.Webflow?.commerce?.order;
if (orderData) {
const items = orderData.userItems.map(function(item) {
return {
item_id: item.sku || item.productId,
item_name: item.name,
price: parseFloat(item.price),
quantity: item.count
};
});
gtag('event', 'purchase', {
transaction_id: orderData.orderId,
value: parseFloat(orderData.total),
tax: parseFloat(orderData.tax || 0),
shipping: parseFloat(orderData.shipping || 0),
currency: orderData.currency || 'USD',
coupon: orderData.coupon || '',
items: items
});
}
});
}
});
</script>
Prevent Duplicate Purchase Events
Add a flag to prevent duplicate tracking if users refresh the confirmation page:
<script>
document.addEventListener('DOMContentLoaded', function() {
if (window.location.pathname.includes('/order-confirmation')) {
// Check if we've already tracked this order
const trackingKey = 'webflow_order_tracked';
const trackedOrders = JSON.parse(localStorage.getItem(trackingKey) || '[]');
window.Webflow = window.Webflow || [];
window.Webflow.push(function() {
const orderData = window.Webflow?.commerce?.order;
if (orderData && !trackedOrders.includes(orderData.orderId)) {
const items = orderData.userItems.map(function(item) {
return {
item_id: item.sku || item.productId,
item_name: item.name,
price: parseFloat(item.price),
quantity: item.count
};
});
gtag('event', 'purchase', {
transaction_id: orderData.orderId,
value: parseFloat(orderData.total),
tax: parseFloat(orderData.tax || 0),
shipping: parseFloat(orderData.shipping || 0),
currency: orderData.currency || 'USD',
items: items
});
// Mark this order as tracked
trackedOrders.push(orderData.orderId);
localStorage.setItem(trackingKey, JSON.stringify(trackedOrders.slice(-10))); // Keep last 10
}
});
}
});
</script>
Complete Implementation Example
Here's a complete, production-ready implementation combining all events:
<!-- Add to Project Settings > Custom Code > Footer Code -->
<script>
(function() {
'use strict';
// Configuration
const config = {
currency: 'USD',
debug: false // Set to true for console logging
};
function log(message, data) {
if (config.debug) {
console.log('[Webflow GA4]', message, data);
}
}
// Helper: Get product data from page
function getProductDataFromPage() {
return {
id: window.location.pathname.split('/').pop(),
name: document.querySelector('.product-name')?.textContent.trim() || 'Unknown',
price: document.querySelector('.product-price')?.textContent.replace(/[^0-9.]/g, '') || '0',
category: document.querySelector('.product-category')?.textContent.trim() || 'Uncategorized'
};
}
// Helper: Format cart items for GA4
function formatCartItems(items) {
return items.map(function(item) {
return {
item_id: item.sku || item.productId,
item_name: item.name,
price: parseFloat(item.price),
quantity: item.count
};
});
}
// Track product page view
function trackProductView() {
if (document.querySelector('.w-commerce-commerceproducttemplate')) {
const product = getProductDataFromPage();
gtag('event', 'view_item', {
currency: config.currency,
value: parseFloat(product.price),
items: [{
item_id: product.id,
item_name: product.name,
item_category: product.category,
price: parseFloat(product.price),
quantity: 1
}]
});
log('Product view tracked', product);
}
}
// Track add to cart
function trackAddToCart() {
document.addEventListener('click', function(e) {
const button = e.target.closest('.w-commerce-commerceaddtocartbutton');
if (button && !button.disabled) {
const product = getProductDataFromPage();
gtag('event', 'add_to_cart', {
currency: config.currency,
value: parseFloat(product.price),
items: [{
item_id: product.id,
item_name: product.name,
price: parseFloat(product.price),
quantity: 1
}]
});
log('Add to cart tracked', product);
}
});
}
// Track cart view and checkout
function trackCartAndCheckout() {
if (!window.location.pathname.includes('/checkout')) return;
window.Webflow = window.Webflow || [];
window.Webflow.push(function() {
const cart = window.Webflow?.commerce?.cart;
if (!cart || !cart.items || cart.items.length === 0) return;
const items = formatCartItems(cart.items);
const value = parseFloat(cart.subtotal);
// View cart
if (!window.location.search) {
gtag('event', 'view_cart', {
currency: cart.currency || config.currency,
value: value,
items: items
});
log('View cart tracked', { value, items });
}
// Begin checkout
if (document.querySelector('.w-commerce-commercecheckoutform')) {
gtag('event', 'begin_checkout', {
currency: cart.currency || config.currency,
value: value,
items: items
});
log('Begin checkout tracked', { value, items });
}
});
}
// Track purchase
function trackPurchase() {
if (!window.location.pathname.includes('/order-confirmation')) return;
const trackingKey = 'webflow_tracked_orders';
const trackedOrders = JSON.parse(localStorage.getItem(trackingKey) || '[]');
window.Webflow = window.Webflow || [];
window.Webflow.push(function() {
const order = window.Webflow?.commerce?.order;
if (!order || trackedOrders.includes(order.orderId)) return;
const items = order.userItems.map(function(item) {
return {
item_id: item.sku || item.productId,
item_name: item.name,
price: parseFloat(item.price),
quantity: item.count
};
});
gtag('event', 'purchase', {
transaction_id: order.orderId,
value: parseFloat(order.total),
tax: parseFloat(order.tax || 0),
shipping: parseFloat(order.shipping || 0),
currency: order.currency || config.currency,
items: items
});
log('Purchase tracked', order);
// Prevent duplicate tracking
trackedOrders.push(order.orderId);
localStorage.setItem(trackingKey, JSON.stringify(trackedOrders.slice(-10)));
});
}
// Initialize tracking
document.addEventListener('DOMContentLoaded', function() {
trackProductView();
trackAddToCart();
trackCartAndCheckout();
trackPurchase();
log('Webflow GA4 Ecommerce tracking initialized');
});
})();
</script>
Testing Your Implementation
1. Use GA4 DebugView
Enable debug mode in your GA4 config:
<script>
gtag('config', 'G-XXXXXXXXXX', {
'debug_mode': true
});
</script>
Then check Admin > DebugView in GA4 to see events in real-time.
2. Test Each Event
Create a test checklist:
- view_item: Visit a product page
- add_to_cart: Add a product to cart
- view_cart: View cart page
- begin_checkout: Start checkout
- purchase: Complete a test order
- Verify all events appear in DebugView
- Verify item data (name, price, quantity) is correct
- Verify currency and values are correct
3. Browser Console Testing
Check browser console for errors:
// Verify Webflow commerce object
console.log(window.Webflow?.commerce);
// Check cart data
console.log(window.Webflow?.commerce?.cart);
// Check order data (on confirmation page)
console.log(window.Webflow?.commerce?.order);
// Verify gtag is defined
console.log(typeof gtag);
4. Test Purchases in Webflow
Webflow provides test mode for ecommerce:
- Enable Test Mode in Ecommerce settings
- Use test credit card:
4242 4242 4242 4242 - Complete a test purchase
- Verify
purchaseevent fires with correct data - Check that
transaction_idmatches order ID
Common Issues
Purchase Event Not Firing
Problem: Purchase event doesn't fire on order confirmation page.
Solutions:
- Verify order data: Check
window.Webflow?.commerce?.orderin console - Wait for Webflow to load: Wrap in
window.Webflow.push(function() {}) - Check page detection: Verify URL includes
/order-confirmation - Remove duplicate prevention: Temporarily disable localStorage check
- Test on published site: Order confirmation only works on published sites
Cart Data Unavailable
Problem: window.Webflow?.commerce?.cart returns undefined.
Solutions:
- Ecommerce plan required: Ensure you have a Webflow Ecommerce plan
- Wait for Webflow: Use
window.Webflow.push() - Products configured: Ensure products exist and are published
- Check page type: Cart object only available on ecommerce pages
Incorrect Product Prices
Problem: Product prices are wrong in GA4.
Solutions:
- Check selectors: Update
.product-priceto match your class names - Parse correctly: Use
replace(/[^0-9.]/g, '')to remove currency symbols - Use CMS fields: Insert price directly from CMS when possible
- Check decimal places: Ensure prices are floats, not strings
Advanced Scenarios
Multiple Currencies
If your store supports multiple currencies:
<script>
// Detect currency from Webflow
window.Webflow = window.Webflow || [];
window.Webflow.push(function() {
const cart = window.Webflow?.commerce?.cart;
const currency = cart?.currency || 'USD';
// Use detected currency in all events
gtag('event', 'view_item', {
currency: currency,
// ... rest of event
});
});
</script>
Product Variants
Track product variants (size, color, etc.):
<script>
const variant = document.querySelector('.w-commerce-commerceproductoption')?.value || 'default';
gtag('event', 'view_item', {
items: [{
item_id: productData.id,
item_name: productData.name,
item_variant: variant, // e.g., "Large / Blue"
price: parseFloat(productData.price),
quantity: 1
}]
});
</script>
Custom Product Categories
Organize products by custom categories:
<script>
gtag('event', 'view_item', {
items: [{
item_id: productData.id,
item_name: productData.name,
item_category: 'Clothing',
item_category2: 'Shirts',
item_category3: 'T-Shirts',
item_category4: 'Men',
price: parseFloat(productData.price),
quantity: 1
}]
});
</script>
Next Steps
- Track Custom Events for forms and interactions
- Set Up Google Tag Manager for advanced tag management
- Configure GTM Data Layer for ecommerce events
- Troubleshoot Tracking Issues