GA4 E-commerce Tracking on Sitecore Commerce
Learn how to implement Google Analytics 4 e-commerce tracking on Sitecore Commerce (formerly Sitecore Experience Commerce) to track product views, add to cart, checkout, and purchases.
Prerequisites
- GA4 installed on Sitecore
- Sitecore Commerce 10.x or Sitecore XC 9.x
- E-commerce enabled in GA4 property
- Access to Sitecore Commerce controllers and views
Understanding Sitecore Commerce Architecture
Sitecore Commerce uses:
- Commerce Engine: Headless commerce services
- Storefront: ASP.NET MVC or headless frontend
- Catalog: Product information management
- Orders: Order management system
GA4 e-commerce events integrate at the storefront level.
GA4 E-commerce Events
Required Events
- view_item_list - Product list views
- select_item - Product click from list
- view_item - Product detail page view
- add_to_cart - Add product to cart
- remove_from_cart - Remove product from cart
- view_cart - View shopping cart
- begin_checkout - Start checkout process
- add_shipping_info - Shipping information added
- add_payment_info - Payment information added
- purchase - Completed transaction
Configuration Setup
Create Commerce Helper Class
// /Helpers/CommerceAnalyticsHelper.cs
using Newtonsoft.Json;
using Sitecore.Commerce.Engine.Connect.Entities;
using Sitecore.Commerce.Entities.Prices;
using System.Collections.Generic;
using System.Linq;
namespace YourProject.Helpers
{
public static class CommerceAnalyticsHelper
{
public static string GetProductJson(CommerceCartProduct product, int index = 0, string listName = "")
{
var productData = new
{
item_id = product.ProductId,
item_name = product.DisplayName,
item_brand = product.Properties["Brand"]?.ToString() ?? "",
item_category = product.Properties["Category"]?.ToString() ?? "",
item_variant = product.ProductVariantId ?? "",
price = GetPrice(product.Price),
quantity = (int)product.Quantity,
index = index,
item_list_name = listName
};
return JsonConvert.SerializeObject(productData);
}
public static string GetProductsArrayJson(IEnumerable<CommerceCartProduct> products, string listName = "")
{
var items = products.Select((p, index) => new
{
item_id = p.ProductId,
item_name = p.DisplayName,
item_brand = p.Properties.ContainsKey("Brand") ? p.Properties["Brand"]?.ToString() : "",
item_category = p.Properties.ContainsKey("Category") ? p.Properties["Category"]?.ToString() : "",
item_variant = p.ProductVariantId ?? "",
price = GetPrice(p.Price),
quantity = (int)p.Quantity,
index = index,
item_list_name = listName
}).ToList();
return JsonConvert.SerializeObject(items);
}
public static decimal GetPrice(Total priceTotal)
{
return priceTotal?.Amount ?? 0m;
}
public static string GetTransactionJson(CommerceOrder order)
{
var transactionData = new
{
transaction_id = order.OrderID,
value = (double)order.Total.Amount,
currency = order.Total.CurrencyCode,
tax = (double)(order.TaxTotal?.Amount ?? 0m),
shipping = (double)(order.ShippingTotal?.Amount ?? 0m),
items = order.Lines.Select((line, index) => new
{
item_id = line.Product.ProductId,
item_name = line.Product.DisplayName,
item_brand = line.Product.Properties.ContainsKey("Brand") ? line.Product.Properties["Brand"]?.ToString() : "",
item_category = line.Product.Properties.ContainsKey("Category") ? line.Product.Properties["Category"]?.ToString() : "",
item_variant = line.Product.ProductVariantId ?? "",
price = (double)GetPrice(line.Product.Price),
quantity = (int)line.Quantity,
index = index
}).ToList()
};
return JsonConvert.SerializeObject(transactionData);
}
}
}
Product List View Tracking
Track Category/Search Results Pages
Controller:
// /Controllers/CatalogController.cs
using Sitecore.Commerce.Engine.Connect.Entities;
using System.Web.Mvc;
using YourProject.Helpers;
namespace YourProject.Controllers
{
public class CatalogController : BaseCommerceController
{
public ActionResult ProductList(string category)
{
var products = GetCategoryProducts(category);
ViewBag.ProductListJson = CommerceAnalyticsHelper.GetProductsArrayJson(
products,
$"Category: {category}"
);
ViewBag.ListName = category;
return View(products);
}
}
}
View:
@* /Views/Catalog/ProductList.cshtml *@
@model IEnumerable<CommerceCartProduct>
<div class="product-list" data-list-name="@ViewBag.ListName">
@foreach (var product in Model)
{
<div class="product-card"
data-product-id="@product.ProductId"
data-product-name="@product.DisplayName">
<a href="@Url.Action("ProductDetail", new { id = product.ProductId })"
onclick="trackProductClick('@product.ProductId', '@product.DisplayName', @Model.ToList().IndexOf(product))">
<img src="@product.ImageUrl" alt="@product.DisplayName" />
<h3>@product.DisplayName</h3>
<p class="price">@product.Price.Amount.ToString("C")</p>
</a>
</div>
}
</div>
<script>
// Track product list view
var products = @Html.Raw(ViewBag.ProductListJson);
gtag('event', 'view_item_list', {
item_list_id: '@ViewBag.ListName',
item_list_name: '@ViewBag.ListName',
items: products
});
// Track individual product clicks
function trackProductClick(productId, productName, index) {
gtag('event', 'select_item', {
item_list_id: '@ViewBag.ListName',
item_list_name: '@ViewBag.ListName',
items: [{
item_id: productId,
item_name: productName,
index: index
}]
});
}
</script>
Product Detail Page Tracking
Controller:
// /Controllers/ProductController.cs
public ActionResult ProductDetail(string id)
{
var product = GetProduct(id);
if (product != null)
{
ViewBag.ProductJson = CommerceAnalyticsHelper.GetProductJson(product);
}
return View(product);
}
View:
@* /Views/Product/ProductDetail.cshtml *@
@model CommerceCartProduct
<div class="product-detail" data-product-id="@Model.ProductId">
<div class="product-images">
<img src="@Model.ImageUrl" alt="@Model.DisplayName" />
</div>
<div class="product-info">
<h1>@Model.DisplayName</h1>
<p class="price">@Model.Price.Amount.ToString("C")</p>
<div class="product-description">
@Html.Raw(Model.Description)
</div>
<div class="product-actions">
<input type="number" id="quantity" value="1" min="1" />
<button onclick="addToCart('@Model.ProductId', '@Model.DisplayName', @Model.Price.Amount)">
Add to Cart
</button>
</div>
</div>
</div>
<script>
// Track product detail view
var product = @Html.Raw(ViewBag.ProductJson);
gtag('event', 'view_item', {
currency: '@Model.Price.CurrencyCode',
value: @Model.Price.Amount,
items: [product]
});
</script>
Add to Cart Tracking
Client-Side Implementation:
// /scripts/commerce/cart-tracking.js
function addToCart(productId, productName, price, variant) {
var quantity = parseInt(document.getElementById('quantity').value) || 1;
// Call Sitecore Commerce API
fetch('/api/commerce/cart/add', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
productId: productId,
quantity: quantity,
variantId: variant
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
// Track add to cart event
gtag('event', 'add_to_cart', {
currency: 'USD',
value: price * quantity,
items: [{
item_id: productId,
item_name: productName,
item_variant: variant || '',
price: price,
quantity: quantity
}]
});
// Update cart UI
updateCartCount(data.cartItemCount);
}
});
}
Server-Side Controller:
// /Controllers/CartApiController.cs
using System.Web.Http;
using Sitecore.Commerce.Engine.Connect;
using YourProject.Helpers;
namespace YourProject.Controllers
{
public class CartApiController : ApiController
{
[HttpPost]
[Route("api/commerce/cart/add")]
public IHttpActionResult AddToCart([FromBody] AddToCartRequest request)
{
var cartManager = new CommerceCartManager();
var cart = cartManager.GetCurrentCart();
var result = cartManager.AddLineItem(
cart,
request.ProductId,
request.Quantity,
request.VariantId
);
if (result.Success)
{
// Get added product for tracking
var addedLine = result.Cart.Lines.FirstOrDefault(
l => l.Product.ProductId == request.ProductId
);
return Ok(new
{
success = true,
cartItemCount = result.Cart.Lines.Count,
product = addedLine != null ?
CommerceAnalyticsHelper.GetProductJson(addedLine.Product) : null
});
}
return BadRequest("Failed to add item to cart");
}
}
}
Remove from Cart Tracking
// /scripts/commerce/cart-tracking.js
function removeFromCart(lineItemId, productId, productName, price, quantity) {
fetch('/api/commerce/cart/remove', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
lineItemId: lineItemId
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
// Track remove from cart
gtag('event', 'remove_from_cart', {
currency: 'USD',
value: price * quantity,
items: [{
item_id: productId,
item_name: productName,
price: price,
quantity: quantity
}]
});
// Update UI
document.getElementById('line-item-' + lineItemId).remove();
updateCartTotal();
}
});
}
View Cart Tracking
Controller:
// /Controllers/CartController.cs
public ActionResult ViewCart()
{
var cart = GetCurrentCart();
if (cart != null && cart.Lines.Any())
{
ViewBag.CartItemsJson = CommerceAnalyticsHelper.GetProductsArrayJson(
cart.Lines.Select(l => l.Product),
"Shopping Cart"
);
ViewBag.CartValue = cart.Total.Amount;
ViewBag.Currency = cart.Total.CurrencyCode;
}
return View(cart);
}
View:
@* /Views/Cart/ViewCart.cshtml *@
@model CommerceCart
@if (Model != null && Model.Lines.Any())
{
<div class="shopping-cart">
<h1>Shopping Cart</h1>
<div class="cart-items">
@foreach (var line in Model.Lines)
{
<div class="cart-item" id="line-item-@line.Id">
<img src="@line.Product.ImageUrl" alt="@line.Product.DisplayName" />
<div class="item-details">
<h3>@line.Product.DisplayName</h3>
<p class="price">@line.Product.Price.Amount.ToString("C")</p>
<p class="quantity">Qty: @line.Quantity</p>
</div>
<button onclick="removeFromCart('@line.Id', '@line.Product.ProductId', '@line.Product.DisplayName', @line.Product.Price.Amount, @line.Quantity)">
Remove
</button>
</div>
}
</div>
<div class="cart-summary">
<p class="total">Total: @Model.Total.Amount.ToString("C")</p>
<a href="@Url.Action("Checkout")" class="checkout-btn">Proceed to Checkout</a>
</div>
</div>
<script>
// Track view cart
var cartItems = @Html.Raw(ViewBag.CartItemsJson);
gtag('event', 'view_cart', {
currency: '@ViewBag.Currency',
value: @ViewBag.CartValue,
items: cartItems
});
</script>
}
Checkout Funnel Tracking
Begin Checkout
// /Controllers/CheckoutController.cs
public ActionResult Checkout()
{
var cart = GetCurrentCart();
if (cart != null && cart.Lines.Any())
{
ViewBag.CartItemsJson = CommerceAnalyticsHelper.GetProductsArrayJson(
cart.Lines.Select(l => l.Product),
"Checkout"
);
ViewBag.CartValue = cart.Total.Amount;
ViewBag.Currency = cart.Total.CurrencyCode;
}
return View(cart);
}
@* /Views/Checkout/Checkout.cshtml *@
<script>
// Track begin checkout
var cartItems = @Html.Raw(ViewBag.CartItemsJson);
gtag('event', 'begin_checkout', {
currency: '@ViewBag.Currency',
value: @ViewBag.CartValue,
items: cartItems
});
</script>
Add Shipping Info
// Track shipping info step
function submitShippingInfo(shippingMethod, shippingCost) {
gtag('event', 'add_shipping_info', {
currency: 'USD',
value: getCartTotal(),
shipping_tier: shippingMethod,
items: getCartItems()
});
// Proceed to payment
window.location.href = '/checkout/payment';
}
Add Payment Info
// Track payment info step
function submitPaymentInfo(paymentMethod) {
gtag('event', 'add_payment_info', {
currency: 'USD',
value: getCartTotal(),
payment_type: paymentMethod,
items: getCartItems()
});
// Submit order
submitOrder();
}
Purchase Tracking
Controller Implementation
// /Controllers/CheckoutController.cs
[HttpPost]
public ActionResult CompleteOrder(OrderModel orderModel)
{
var cart = GetCurrentCart();
var order = SubmitOrder(cart, orderModel);
if (order != null && order.Status == "Completed")
{
// Store order data for confirmation page
ViewBag.OrderJson = CommerceAnalyticsHelper.GetTransactionJson(order);
ViewBag.OrderId = order.OrderID;
// Clear cart
ClearCart();
return View("OrderConfirmation", order);
}
return View("Error");
}
Order Confirmation Page
@* /Views/Checkout/OrderConfirmation.cshtml *@
@model CommerceOrder
<div class="order-confirmation">
<h1>Order Confirmed!</h1>
<p>Thank you for your purchase.</p>
<p>Order Number: <strong>@Model.OrderID</strong></p>
<div class="order-summary">
<h2>Order Details</h2>
@foreach (var line in Model.Lines)
{
<div class="order-item">
<span>@line.Product.DisplayName</span>
<span>Qty: @line.Quantity</span>
<span>@line.Product.Price.Amount.ToString("C")</span>
</div>
}
<div class="order-totals">
<p>Subtotal: @Model.Subtotal.Amount.ToString("C")</p>
<p>Shipping: @Model.ShippingTotal.Amount.ToString("C")</p>
<p>Tax: @Model.TaxTotal.Amount.ToString("C")</p>
<p class="total"><strong>Total: @Model.Total.Amount.ToString("C")</strong></p>
</div>
</div>
</div>
<script>
// Track purchase
var orderData = @Html.Raw(ViewBag.OrderJson);
gtag('event', 'purchase', {
transaction_id: orderData.transaction_id,
value: orderData.value,
currency: orderData.currency,
tax: orderData.tax,
shipping: orderData.shipping,
items: orderData.items
});
// Clear ecommerce data after tracking
gtag('event', 'clear_ecommerce');
</script>
Refund Tracking
For order refunds:
// /Controllers/OrderController.cs
public ActionResult ProcessRefund(string orderId)
{
var order = GetOrder(orderId);
var refund = ProcessOrderRefund(order);
if (refund.Success)
{
ViewBag.RefundJson = new
{
transaction_id = order.OrderID,
value = order.Total.Amount,
currency = order.Total.CurrencyCode,
items = order.Lines.Select(line => new
{
item_id = line.Product.ProductId,
item_name = line.Product.DisplayName,
price = line.Product.Price.Amount,
quantity = line.Quantity
})
};
return View("RefundConfirmation");
}
return View("Error");
}
@* Refund tracking *@
<script>
var refundData = @Html.Raw(ViewBag.RefundJson);
gtag('event', 'refund', {
transaction_id: refundData.transaction_id,
value: refundData.value,
currency: refundData.currency,
items: refundData.items
});
</script>
Promotion Tracking
Track promotional campaigns:
@if (Model.HasPromotion)
{
<script>
gtag('event', 'view_promotion', {
creative_name: '@Model.Promotion.Name',
creative_slot: '@Model.Promotion.Slot',
promotion_id: '@Model.Promotion.Id',
promotion_name: '@Model.Promotion.DisplayName',
items: @Html.Raw(ViewBag.ProductsJson)
});
</script>
}
Promotion Click
function trackPromotionClick(promotionId, promotionName, creativeName) {
gtag('event', 'select_promotion', {
creative_name: creativeName,
promotion_id: promotionId,
promotion_name: promotionName
});
}
Coupon Code Tracking
Track when users apply coupon codes:
function applyCoupon(couponCode) {
fetch('/api/commerce/cart/apply-coupon', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
couponCode: couponCode
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
// Track coupon application
gtag('event', 'add_to_cart', {
currency: 'USD',
value: getCartTotal(),
coupon: couponCode,
items: getCartItems()
});
// Update cart totals
updateCartDisplay(data.cart);
}
});
}
User ID and Customer Tracking
Link GA4 with Sitecore Commerce customer data:
@using Sitecore.Commerce.Engine.Connect
@{
var commerceUser = Sitecore.Commerce.Contacts.CommerceUserManager.GetUser();
var customerId = commerceUser?.CustomerId;
}
<script>
@if (!string.IsNullOrEmpty(customerId))
{
<text>
gtag('config', 'G-XXXXXXXXXX', {
'user_id': '@customerId',
'customer_type': '@(commerceUser.IsNewCustomer ? "new" : "returning")',
'customer_lifetime_value': @(commerceUser.LifetimeValue ?? 0)
});
</text>
}
</script>
Enhanced Measurement
Enable automatic tracking for:
Scroll Tracking:
// Automatically tracks scrolls to 90%
gtag('config', 'G-XXXXXXXXXX', {
'send_page_view': true
});
Site Search:
@* Track commerce search *@
@if (!string.IsNullOrEmpty(Request.QueryString["q"]))
{
<script>
gtag('event', 'search', {
'search_term': '@Request.QueryString["q"]'
});
</script>
}
Integration with Sitecore CDP
For unified customer data:
// Sync GA4 and Sitecore CDP
public void SyncCustomerData(string customerId, CommerceOrder order)
{
// Track in both systems
var cdpClient = new SitecoreCDPClient();
cdpClient.TrackPurchase(new
{
CustomerId = customerId,
OrderId = order.OrderID,
OrderValue = order.Total.Amount,
Currency = order.Total.CurrencyCode,
Items = order.Lines.Select(l => new
{
ProductId = l.Product.ProductId,
ProductName = l.Product.DisplayName,
Quantity = l.Quantity,
Price = l.Product.Price.Amount
})
});
}
Testing E-commerce Tracking
1. Test with GA4 DebugView
Enable debug mode:
gtag('config', 'G-XXXXXXXXXX', {
'debug_mode': true
});
2. Test Purchase Flow
- Add items to cart
- View cart
- Begin checkout
- Complete purchase
- Verify all events in GA4 DebugView
3. Validate Data
Check that all fields are populated:
- Transaction ID
- Revenue
- Tax
- Shipping
- Product details
- Quantities
Common Issues
Issue: Duplicate Purchase Events
Cause: User refreshing confirmation page
Solution:
// Track purchase only once
if (!sessionStorage.getItem('order_tracked_' + orderData.transaction_id)) {
gtag('event', 'purchase', orderData);
sessionStorage.setItem('order_tracked_' + orderData.transaction_id, 'true');
}
Issue: Missing Product Data
Cause: Null product properties
Solution:
// Safe property access
item_brand = product.Properties?.ContainsKey("Brand") == true ?
product.Properties["Brand"]?.ToString() : "Unknown"
Issue: Currency Mismatch
Cause: Hardcoded currency
Solution: Always use dynamic currency from cart/order:
currency: '@Model.Total.CurrencyCode'