GA4 E-commerce Tracking on Sitecore Commerce | Blue Frog Docs

GA4 E-commerce Tracking on Sitecore Commerce

Implement GA4 enhanced e-commerce tracking for Sitecore Commerce including product views, cart actions, and checkout funnel

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

  1. view_item_list - Product list views
  2. select_item - Product click from list
  3. view_item - Product detail page view
  4. add_to_cart - Add product to cart
  5. remove_from_cart - Remove product from cart
  6. view_cart - View shopping cart
  7. begin_checkout - Start checkout process
  8. add_shipping_info - Shipping information added
  9. add_payment_info - Payment information added
  10. 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

  1. Add items to cart
  2. View cart
  3. Begin checkout
  4. Complete purchase
  5. 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'

Next Steps

// SYS.FOOTER