Learn how to implement Google Analytics 4 e-commerce tracking with Kentico's built-in E-commerce module, including product views, add to cart, checkout, and purchase tracking.
Prerequisites
- GA4 Setup completed
- Kentico E-commerce module enabled
- Understanding of Kentico shopping cart and checkout process
- Access to Kentico MVC or Portal Engine development files
GA4 E-commerce Event Overview
GA4 e-commerce tracking uses these key events:
view_item_list- Product listing pagesview_item- Product detail pageadd_to_cart- Adding product to cartremove_from_cart- Removing product from cartview_cart- Viewing shopping cartbegin_checkout- Starting checkout processadd_payment_info- Adding payment informationadd_shipping_info- Adding shipping informationpurchase- Completed transaction
E-commerce Setup
1. View Item List (Product Listing Pages)
Track when users view product category or listing pages:
MVC Implementation
@using CMS.Ecommerce
@model IEnumerable<SKUInfo>
<div class="product-list">
@foreach (var product in Model)
{
<div class="product-item" data-product-id="@product.SKUID">
@* Product display *@
</div>
}
</div>
<script>
// Track product list view
gtag('event', 'view_item_list', {
'item_list_id': '@ViewBag.CategoryID',
'item_list_name': '@ViewBag.CategoryName',
'items': [
@foreach (var product in Model)
{
@:{
'item_id': '@product.SKUNumber',
'item_name': '@product.SKUName',
'item_brand': '@(product.Brand?.BrandDisplayName ?? "")',
'item_category': '@ViewBag.CategoryName',
'price': @product.SKUPrice,
'currency': '@CurrencyInfoProvider.GetMainCurrency(SiteContext.CurrentSiteID).CurrencyCode'
}@(product != Model.Last() ? "," : "")
}
]
});
</script>
Portal Engine / Web Part Implementation
Create a custom transformation or web part:
<script runat="server">
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
var products = GetProducts(); // Your method to get products
RegisterProductListView(products);
}
}
private void RegisterProductListView(IEnumerable<SKUInfo> products)
{
var items = products.Select(p => new
{
item_id = p.SKUNumber,
item_name = p.SKUName,
price = p.SKUPrice
});
var script = $@"
gtag('event', 'view_item_list', {{
'items': {JsonConvert.SerializeObject(items)}
}});
";
ScriptHelper.RegisterStartupScript(this, typeof(string), "ProductListView", script, true);
}
</script>
2. View Item (Product Detail Page)
Track product detail page views:
@using CMS.Ecommerce
@model SKUInfo
<div class="product-detail" itemscope itemtype="https://schema.org/Product">
<h1 itemprop="name">@Model.SKUName</h1>
<div class="price" itemprop="price">@Model.SKUPrice</div>
@* Rest of product details *@
</div>
<script>
gtag('event', 'view_item', {
'currency': '@CurrencyInfoProvider.GetMainCurrency(SiteContext.CurrentSiteID).CurrencyCode',
'value': @Model.SKUPrice,
'items': [{
'item_id': '@Model.SKUNumber',
'item_name': '@Model.SKUName',
'item_brand': '@(Model.Brand?.BrandDisplayName ?? "")',
'item_category': '@(Model.PrimaryCategory?.CategoryDisplayName ?? "")',
'item_variant': '@Model.SKUNumber',
'price': @Model.SKUPrice,
'quantity': 1
}]
});
</script>
3. Add to Cart Tracking
Track when products are added to the shopping cart:
Client-Side Tracking (AJAX Add to Cart)
<script>
function addToCart(productId, quantity) {
// Make AJAX call to add product
$.ajax({
url: '/ShoppingCart/AddItem',
method: 'POST',
data: {
skuId: productId,
quantity: quantity
},
success: function(response) {
// Track add to cart
gtag('event', 'add_to_cart', {
'currency': response.currency,
'value': response.totalPrice,
'items': [{
'item_id': response.product.skuNumber,
'item_name': response.product.skuName,
'item_brand': response.product.brand,
'item_category': response.product.category,
'price': response.product.price,
'quantity': quantity
}]
});
// Update cart UI
updateCartDisplay(response);
}
});
}
</script>
<button 1)">Add to Cart</button>
Server-Side Tracking (Traditional Form Post)
In your controller:
using CMS.Ecommerce;
using Newtonsoft.Json;
public ActionResult AddToCart(int skuId, int quantity)
{
var sku = SKUInfoProvider.GetSKUInfo(skuId);
var cart = ECommerceContext.CurrentShoppingCart;
// Add item to cart
cart.AddItem(skuId, quantity);
cart.Evaluate();
// Prepare tracking data
var trackingData = new
{
currency = cart.Currency.CurrencyCode,
value = sku.SKUPrice * quantity,
items = new[]
{
new
{
item_id = sku.SKUNumber,
item_name = sku.SKUName,
item_brand = sku.Brand?.BrandDisplayName ?? "",
item_category = sku.PrimaryCategory?.CategoryDisplayName ?? "",
price = sku.SKUPrice,
quantity = quantity
}
}
};
ViewBag.AddToCartTracking = JsonConvert.SerializeObject(trackingData);
return View("Cart");
}
In your view:
@if (ViewBag.AddToCartTracking != null)
{
<script>
gtag('event', 'add_to_cart', @Html.Raw(ViewBag.AddToCartTracking));
</script>
}
4. Remove from Cart
Track product removal from cart:
<script>
function removeFromCart(itemId, itemData) {
$.ajax({
url: '/ShoppingCart/RemoveItem',
method: 'POST',
data: { cartItemId: itemId },
success: function(response) {
gtag('event', 'remove_from_cart', {
'currency': itemData.currency,
'value': itemData.price * itemData.quantity,
'items': [{
'item_id': itemData.skuNumber,
'item_name': itemData.skuName,
'price': itemData.price,
'quantity': itemData.quantity
}]
});
}
});
}
</script>
5. View Cart
Track shopping cart page views:
@using CMS.Ecommerce
@{
var cart = ECommerceContext.CurrentShoppingCart;
}
<script>
gtag('event', 'view_cart', {
'currency': '@cart.Currency.CurrencyCode',
'value': @cart.TotalPrice,
'items': [
@foreach (var item in cart.CartItems)
{
var sku = item.SKU;
@:{
'item_id': '@sku.SKUNumber',
'item_name': '@sku.SKUName',
'item_brand': '@(sku.Brand?.BrandDisplayName ?? "")',
'item_category': '@(sku.PrimaryCategory?.CategoryDisplayName ?? "")',
'price': @item.UnitPrice,
'quantity': @item.CartItemUnits
}@(item != cart.CartItems.Last() ? "," : "")
}
]
});
</script>
6. Begin Checkout
Track when customers start the checkout process:
@using CMS.Ecommerce
@{
var cart = ECommerceContext.CurrentShoppingCart;
}
@* Checkout page *@
<h1>Checkout</h1>
<script>
gtag('event', 'begin_checkout', {
'currency': '@cart.Currency.CurrencyCode',
'value': @cart.TotalPrice,
'coupon': '@(cart.CouponCodes.FirstOrDefault()?.Code ?? "")',
'items': [
@foreach (var item in cart.CartItems)
{
var sku = item.SKU;
@:{
'item_id': '@sku.SKUNumber',
'item_name': '@sku.SKUName',
'price': @item.UnitPrice,
'quantity': @item.CartItemUnits
}@(item != cart.CartItems.Last() ? "," : "")
}
]
});
</script>
7. Add Shipping Info
Track when shipping information is added:
<script>
// When shipping method is selected
function selectShippingMethod(shippingId, shippingName, shippingCost) {
gtag('event', 'add_shipping_info', {
'currency': '@cart.Currency.CurrencyCode',
'value': @cart.TotalPrice,
'shipping_tier': shippingName,
'items': @Html.Raw(GetCartItemsJson())
});
// Submit shipping selection
submitShippingMethod(shippingId);
}
</script>
8. Add Payment Info
Track when payment method is selected:
<script>
function selectPaymentMethod(paymentType) {
gtag('event', 'add_payment_info', {
'currency': '@cart.Currency.CurrencyCode',
'value': @cart.TotalPrice,
'payment_type': paymentType,
'items': @Html.Raw(GetCartItemsJson())
});
}
</script>
9. Purchase Event (Most Important!)
Track completed transactions:
Method 1: On Order Confirmation Page
@using CMS.Ecommerce
@model OrderInfo
<h1>Thank You for Your Order!</h1>
<p>Order #@Model.OrderID</p>
<script>
gtag('event', 'purchase', {
'transaction_id': '@Model.OrderID',
'affiliation': '@SiteContext.CurrentSite.SiteName',
'value': @Model.OrderGrandTotal,
'tax': @Model.OrderTotalTax,
'shipping': @Model.OrderTotalShipping,
'currency': '@Model.OrderCurrency.CurrencyCode',
'coupon': '@(Model.OrderCouponCodes ?? "")',
'items': [
@foreach (var item in Model.OrderItems)
{
var sku = item.OrderItemSKU;
@:{
'item_id': '@sku.SKUNumber',
'item_name': '@sku.SKUName',
'item_brand': '@(sku.Brand?.BrandDisplayName ?? "")',
'item_category': '@(sku.PrimaryCategory?.CategoryDisplayName ?? "")',
'price': @item.OrderItemUnitPrice,
'quantity': @item.OrderItemUnitCount
}@(item != Model.OrderItems.Last() ? "," : "")
}
]
});
</script>
Method 2: Server-Side Event Handler
Create a custom module to track purchases automatically:
using CMS;
using CMS.Base;
using CMS.DataEngine;
using CMS.Ecommerce;
using Newtonsoft.Json;
[assembly: RegisterModule(typeof(EcommerceTrackingModule))]
public class EcommerceTrackingModule : Module
{
public EcommerceTrackingModule() : base("EcommerceTracking") { }
protected override void OnInit()
{
base.OnInit();
// Subscribe to order paid event
OrderInfoProvider.OnOrderPaid += OrderInfoProvider_OnOrderPaid;
}
private void OrderInfoProvider_OnOrderPaid(object sender, OrderEventArgs e)
{
var order = e.Order;
// Build tracking script
var items = order.OrderItems.Select(item => new
{
item_id = item.OrderItemSKU.SKUNumber,
item_name = item.OrderItemSKU.SKUName,
item_brand = item.OrderItemSKU.Brand?.BrandDisplayName ?? "",
item_category = item.OrderItemSKU.PrimaryCategory?.CategoryDisplayName ?? "",
price = item.OrderItemUnitPrice,
quantity = item.OrderItemUnitCount
});
var purchaseData = new
{
transaction_id = order.OrderID.ToString(),
affiliation = order.OrderSite.SiteName,
value = order.OrderGrandTotal,
tax = order.OrderTotalTax,
shipping = order.OrderTotalShipping,
currency = order.OrderCurrency.CurrencyCode,
items = items
};
// Store in session for rendering on confirmation page
SessionHelper.SetValue("GA4PurchaseData", JsonConvert.SerializeObject(purchaseData));
}
}
Then in your confirmation page:
@{
var purchaseData = SessionHelper.GetValue("GA4PurchaseData") as string;
if (!string.IsNullOrEmpty(purchaseData))
{
SessionHelper.Remove("GA4PurchaseData");
}
}
@if (!string.IsNullOrEmpty(purchaseData))
{
<script>
gtag('event', 'purchase', @Html.Raw(purchaseData));
</script>
}
Advanced E-commerce Tracking
Product Impressions with List Position
<script>
gtag('event', 'view_item_list', {
'item_list_id': 'related_products',
'item_list_name': 'Related Products',
'items': [
@for (int i = 0; i < Model.Count(); i++)
{
var product = Model.ElementAt(i);
@:{
'item_id': '@product.SKUNumber',
'item_name': '@product.SKUName',
'index': @i,
'item_list_name': 'Related Products',
'price': @product.SKUPrice
}@(i < Model.Count() - 1 ? "," : "")
}
]
});
</script>
Select Item (Click from List)
<script>
document.querySelectorAll('.product-item').forEach(function(item, index) {
item.addEventListener('click', function(e) {
var productData = JSON.parse(this.getAttribute('data-product'));
gtag('event', 'select_item', {
'item_list_id': '@ViewBag.CategoryID',
'item_list_name': '@ViewBag.CategoryName',
'items': [{
'item_id': productData.skuNumber,
'item_name': productData.skuName,
'index': index,
'price': productData.price
}]
});
});
});
</script>
Refund Tracking
Track order refunds:
// In your refund controller
public ActionResult ProcessRefund(int orderId)
{
var order = OrderInfoProvider.GetOrderInfo(orderId);
// Process refund logic...
var refundData = new
{
transaction_id = order.OrderID.ToString(),
value = order.OrderGrandTotal,
currency = order.OrderCurrency.CurrencyCode,
items = order.OrderItems.Select(item => new
{
item_id = item.OrderItemSKU.SKUNumber,
quantity = item.OrderItemUnitCount
})
};
ViewBag.RefundTracking = JsonConvert.SerializeObject(refundData);
return View("RefundConfirmation");
}
@if (ViewBag.RefundTracking != null)
{
<script>
gtag('event', 'refund', @Html.Raw(ViewBag.RefundTracking));
</script>
}
Testing E-commerce Tracking
1. Use GA4 DebugView
- Enable debug mode in GA4 config
- Navigate GA4 → Configure → DebugView
- Complete a test purchase
- Verify all events fire in sequence
2. Check Event Parameters
Ensure all required parameters are present:
transaction_idfor purchasecurrencyfor all monetary eventsvaluefor all monetary eventsitemsarray with proper structure
3. Test Different Scenarios
- Complete purchase flow
- Add/remove items
- Use discount codes
- Different payment methods
- Different shipping options
4. Verify in GA4 Reports
- Navigate to Monetization → E-commerce purchases
- Check for test transactions
- Verify revenue matches order total
- Confirm product data is correct
Common Issues
Purchase Event Not Firing
- Check if event is on order confirmation page
- Verify page loads after payment success
- Check for JavaScript errors
- Ensure not firing on page refresh (use session storage)
Duplicate Purchases
Prevent duplicate tracking on refresh:
@{
var purchaseTracked = SessionHelper.GetValue("PurchaseTracked_" + Model.OrderID);
}
@if (purchaseTracked == null)
{
SessionHelper.SetValue("PurchaseTracked_" + Model.OrderID, true);
<script>
gtag('event', 'purchase', {
// purchase data
});
</script>
}
Missing Product Data
- Verify SKU information is complete
- Check for null values in brand/category
- Use default values for optional fields
Currency Mismatch
- Ensure consistent currency code across all events
- Use Kentico's main currency or order-specific currency
- Format: ISO 4217 (e.g., 'USD', 'EUR', 'GBP')
Best Practices
Track All E-commerce Events: Implement the complete funnel for better insights
Use Consistent Item IDs: Use SKUNumber as item_id across all events
Include Product Hierarchy: Add brand, category, and variant data when available
Test Before Production: Always test the full purchase flow in staging
Prevent Duplicate Transactions: Use session flags to prevent re-tracking on refresh
Add Custom Dimensions: Include Kentico-specific data (customer type, site ID, etc.)
Document Revenue Logic: Clearly document what's included in value/revenue calculations
Helper Function for Cart Items JSON
Create a helper method in your controller or view:
@functions {
public string GetCartItemsJson()
{
var cart = ECommerceContext.CurrentShoppingCart;
var items = cart.CartItems.Select(item => new
{
item_id = item.SKU.SKUNumber,
item_name = item.SKU.SKUName,
item_brand = item.SKU.Brand?.BrandDisplayName ?? "",
item_category = item.SKU.PrimaryCategory?.CategoryDisplayName ?? "",
price = item.UnitPrice,
quantity = item.CartItemUnits
});
return JsonConvert.SerializeObject(items);
}
}
Next Steps
- Implement GTM for E-commerce (recommended for easier management)
- Set Up Enhanced Conversions
- Create E-commerce Reports in GA4
- Troubleshoot Tracking Issues