GA4 Event Tracking on NopCommerce | OpsBlu Docs

GA4 Event Tracking on NopCommerce

Implement comprehensive Google Analytics 4 event tracking on NopCommerce including custom events, user interactions, form submissions, and advanced C#...

Track user interactions and custom events in Google Analytics 4 on your NopCommerce store using JavaScript, C# server-side tracking, and Razor view integration.

Before You Begin

Prerequisites:

  • GA4 setup complete (see GA4 Setup Guide)
  • Active GA4 Measurement ID configured
  • Basic understanding of GA4 event structure
  • Access to NopCommerce theme files or plugin development

GA4 Event Structure:

gtag('event', 'event_name', {
  'parameter_1': 'value_1',
  'parameter_2': 'value_2',
  // Additional parameters
});

Standard E-commerce Events

These events are covered in detail in the Ecommerce Tracking Guide:

  • view_item - Product page view
  • add_to_cart - Add product to cart
  • remove_from_cart - Remove from cart
  • begin_checkout - Start checkout process
  • add_payment_info - Payment method selected
  • add_shipping_info - Shipping method selected
  • purchase - Order completed

Custom NopCommerce Events

Additional events specific to NopCommerce functionality:

  • search - Product search
  • view_item_list - Category/manufacturer page view
  • select_item - Product clicked from listing
  • add_to_wishlist - Add to wishlist
  • share - Product share
  • login - Customer login
  • sign_up - New customer registration
  • generate_lead - Newsletter subscription
  • view_promotion - View promotional banner

Method 1: JavaScript Event Tracking in Razor Views

Product Search Tracking

Add to /Themes/YourTheme/Views/Catalog/Search.cshtml:

@model SearchModel

@section scripts {
    <script>
        // Track search event when search results load
        @if (!string.IsNullOrEmpty(Model.q))
        {
            <text>
            gtag('event', 'search', {
                'search_term': '@Html.Raw(Model.q)',
                'number_of_results': @Model.Products.Count,
                'search_type': 'product_search'
            });
            </text>
        }
    </script>
}

Add to Wishlist Tracking

Add to /Themes/YourTheme/Views/Product/ProductTemplate.Simple.cshtml (or your product template):

@model ProductDetailsModel

<script>
    // Track add to wishlist
    function trackWishlistAdd(productId, productName, productPrice) {
        gtag('event', 'add_to_wishlist', {
            'currency': '@Model.ProductPrice.CurrencyCode',
            'value': productPrice,
            'items': [{
                'item_id': productId,
                'item_name': productName,
                'price': productPrice
            }]
        });
    }

    // Attach to wishlist button
    document.addEventListener('DOMContentLoaded', function() {
        var wishlistBtn = document.querySelector('.add-to-wishlist-button');
        if (wishlistBtn) {
            wishlistBtn.addEventListener('click', function() {
                trackWishlistAdd(
                    '@Model.Id',
                    '@Html.Raw(System.Text.Json.JsonSerializer.Serialize(Model.Name))',
                    @Model.ProductPrice.PriceValue
                );
            });
        }
    });
</script>

Product Comparison Tracking

@* Add to comparison tracking *@
<script>
    function trackCompareAdd(productId, productName) {
        gtag('event', 'add_to_compare', {
            'event_category': 'Product Comparison',
            'event_label': productName,
            'item_id': productId
        });
    }

    document.addEventListener('DOMContentLoaded', function() {
        var compareBtn = document.querySelector('.add-to-compare-list-button');
        if (compareBtn) {
            compareBtn.addEventListener('click', function() {
                var productId = this.getAttribute('data-productid');
                var productName = this.getAttribute('data-productname');
                trackCompareAdd(productId, productName);
            });
        }
    });
</script>

Newsletter Subscription Tracking

Add to /Themes/YourTheme/Views/Newsletter/SubscriptionActivation.cshtml:

@model SubscriptionActivationModel

@if (Model.Result)
{
    <script>
        gtag('event', 'generate_lead', {
            'event_category': 'Newsletter',
            'event_label': 'Subscription Confirmed',
            'method': 'email'
        });
    </script>
}

Product Share Tracking

@* Track social sharing *@
<script>
    function trackProductShare(method, productId, productName) {
        gtag('event', 'share', {
            'method': method,
            'content_type': 'product',
            'item_id': productId,
            'content_name': productName
        });
    }

    // Example: Facebook share
    document.querySelector('.share-facebook')?.addEventListener('click', function() {
        trackProductShare('Facebook', '@Model.Id', '@Html.Raw(Model.Name)');
    });

    // Example: Twitter share
    document.querySelector('.share-twitter')?.addEventListener('click', function() {
        trackProductShare('Twitter', '@Model.Id', '@Html.Raw(Model.Name)');
    });

    // Example: Email share
    document.querySelector('.email-a-friend')?.addEventListener('click', function() {
        trackProductShare('Email', '@Model.Id', '@Html.Raw(Model.Name)');
    });
</script>

Method 2: Server-Side Event Tracking with C#

Creating an Analytics Service

Step 1: Create Interface

// Services/IAnalyticsService.cs
using Nop.Core.Domain.Customers;
using Nop.Core.Domain.Orders;
using Nop.Core.Domain.Catalog;

namespace YourCompany.Plugin.Analytics.GoogleGA4.Services
{
    public interface IAnalyticsService
    {
        /// <summary>
        /// Track custom event
        /// </summary>
        Task TrackEventAsync(string eventName, Dictionary<string, object> parameters);

        /// <summary>
        /// Track customer login
        /// </summary>
        Task TrackLoginAsync(Customer customer);

        /// <summary>
        /// Track customer registration
        /// </summary>
        Task TrackRegistrationAsync(Customer customer);

        /// <summary>
        /// Track product view
        /// </summary>
        Task TrackProductViewAsync(Product product, Customer customer);

        /// <summary>
        /// Track category view
        /// </summary>
        Task TrackCategoryViewAsync(int categoryId, string categoryName, int productCount);

        /// <summary>
        /// Track search
        /// </summary>
        Task TrackSearchAsync(string searchTerm, int resultCount);
    }
}

Step 2: Implement Service

// Services/AnalyticsService.cs
using Microsoft.AspNetCore.Http;
using Nop.Core;
using Nop.Core.Domain.Catalog;
using Nop.Core.Domain.Customers;
using Nop.Services.Configuration;
using System.Text.Json;

namespace YourCompany.Plugin.Analytics.GoogleGA4.Services
{
    public class AnalyticsService : IAnalyticsService
    {
        private readonly IHttpContextAccessor _httpContextAccessor;
        private readonly ISettingService _settingService;
        private readonly IStoreContext _storeContext;

        public AnalyticsService(
            IHttpContextAccessor httpContextAccessor,
            ISettingService settingService,
            IStoreContext storeContext)
        {
            _httpContextAccessor = httpContextAccessor;
            _settingService = settingService;
            _storeContext = storeContext;
        }

        public async Task TrackEventAsync(string eventName, Dictionary<string, object> parameters)
        {
            var store = await _storeContext.GetCurrentStoreAsync();
            var settings = await _settingService.LoadSettingAsync<GoogleGA4Settings>(store.Id);

            if (string.IsNullOrEmpty(settings.MeasurementId))
                return;

            // Add event to ViewData for client-side tracking
            var httpContext = _httpContextAccessor.HttpContext;
            if (httpContext?.Items != null)
            {
                if (!httpContext.Items.ContainsKey("GA4Events"))
                    httpContext.Items["GA4Events"] = new List<(string, Dictionary<string, object>)>();

                var events = (List<(string, Dictionary<string, object>)>)httpContext.Items["GA4Events"];
                events.Add((eventName, parameters));
            }
        }

        public async Task TrackLoginAsync(Customer customer)
        {
            var parameters = new Dictionary<string, object>
            {
                { "method", "Email" },
                { "customer_id", customer.Id },
                { "customer_group", customer.CustomerRoles.FirstOrDefault()?.SystemName ?? "Guest" }
            };

            await TrackEventAsync("login", parameters);
        }

        public async Task TrackRegistrationAsync(Customer customer)
        {
            var parameters = new Dictionary<string, object>
            {
                { "method", "Email" },
                { "customer_id", customer.Id }
            };

            await TrackEventAsync("sign_up", parameters);
        }

        public async Task TrackProductViewAsync(Product product, Customer customer)
        {
            var parameters = new Dictionary<string, object>
            {
                { "items", new[]
                    {
                        new
                        {
                            item_id = product.Id.ToString(),
                            item_name = product.Name,
                            item_category = product.ProductCategories.FirstOrDefault()?.Category?.Name ?? ""
                        }
                    }
                }
            };

            await TrackEventAsync("view_item", parameters);
        }

        public async Task TrackCategoryViewAsync(int categoryId, string categoryName, int productCount)
        {
            var parameters = new Dictionary<string, object>
            {
                { "item_list_id", categoryId.ToString() },
                { "item_list_name", categoryName },
                { "number_of_items", productCount }
            };

            await TrackEventAsync("view_item_list", parameters);
        }

        public async Task TrackSearchAsync(string searchTerm, int resultCount)
        {
            var parameters = new Dictionary<string, object>
            {
                { "search_term", searchTerm },
                { "number_of_results", resultCount }
            };

            await TrackEventAsync("search", parameters);
        }
    }
}

Step 3: Render Events in Layout

Create a ViewComponent to render queued events:

// Components/AnalyticsEventsViewComponent.cs
using Microsoft.AspNetCore.Mvc;
using Nop.Web.Framework.Components;

namespace YourCompany.Plugin.Analytics.GoogleGA4.Components
{
    public class AnalyticsEventsViewComponent : NopViewComponent
    {
        private readonly IHttpContextAccessor _httpContextAccessor;

        public AnalyticsEventsViewComponent(IHttpContextAccessor httpContextAccessor)
        {
            _httpContextAccessor = httpContextAccessor;
        }

        public IViewComponentResult Invoke()
        {
            var httpContext = _httpContextAccessor.HttpContext;
            var events = httpContext?.Items["GA4Events"] as List<(string, Dictionary<string, object>)>;

            return View("~/Plugins/YourCompany.Plugin.Analytics.GoogleGA4/Views/Events.cshtml", events ?? new List<(string, Dictionary<string, object>)>());
        }
    }
}

Step 4: Events View Template

@* Views/Events.cshtml *@
@model List<(string EventName, Dictionary<string, object> Parameters)>

@if (Model.Any())
{
    <script>
        @foreach (var (eventName, parameters) in Model)
        {
            <text>
            gtag('event', '@eventName', @Html.Raw(System.Text.Json.JsonSerializer.Serialize(parameters)));
            </text>
        }
    </script>
}

Step 5: Use in Controllers

// Example: In a custom controller
using YourCompany.Plugin.Analytics.GoogleGA4.Services;

public class CustomController : BasePublicController
{
    private readonly IAnalyticsService _analyticsService;

    public CustomController(IAnalyticsService analyticsService)
    {
        _analyticsService = analyticsService;
    }

    public async Task<IActionResult> ProductDetails(int productId)
    {
        var product = await _productService.GetProductByIdAsync(productId);
        var customer = await _workContext.GetCurrentCustomerAsync();

        // Track product view
        await _analyticsService.TrackProductViewAsync(product, customer);

        return View(product);
    }
}

Method 3: Event Tracking via JavaScript Module

Create a reusable JavaScript module for consistent event tracking.

Analytics Module

// wwwroot/js/analytics-tracking.js

var NopAnalytics = (function() {
    'use strict';

    // Check if gtag is available
    function isGtagAvailable() {
        return typeof gtag === 'function';
    }

    // Track generic event
    function trackEvent(eventName, parameters) {
        if (!isGtagAvailable()) {
            console.warn('Google Analytics not initialized');
            return;
        }

        gtag('event', eventName, parameters || {});
    }

    // Track form submission
    function trackFormSubmit(formName, formType) {
        trackEvent('form_submit', {
            'form_name': formName,
            'form_type': formType
        });
    }

    // Track button click
    function trackButtonClick(buttonName, buttonLocation) {
        trackEvent('button_click', {
            'button_name': buttonName,
            'button_location': buttonLocation
        });
    }

    // Track file download
    function trackDownload(fileName, fileType) {
        trackEvent('file_download', {
            'file_name': fileName,
            'file_type': fileType,
            'file_extension': fileName.split('.').pop()
        });
    }

    // Track external link click
    function trackOutboundLink(url, linkText) {
        trackEvent('click', {
            'event_category': 'outbound',
            'event_label': url,
            'link_text': linkText,
            'transport_type': 'beacon'
        });
    }

    // Track video interaction
    function trackVideo(action, videoTitle, videoUrl) {
        trackEvent('video_' + action, {
            'video_title': videoTitle,
            'video_url': videoUrl
        });
    }

    // Track product filter usage
    function trackProductFilter(filterType, filterValue) {
        trackEvent('filter_products', {
            'filter_type': filterType,
            'filter_value': filterValue
        });
    }

    // Track product sort
    function trackProductSort(sortType) {
        trackEvent('sort_products', {
            'sort_type': sortType
        });
    }

    // Track review submission
    function trackReviewSubmit(productId, productName, rating) {
        trackEvent('submit_review', {
            'item_id': productId,
            'item_name': productName,
            'rating': rating
        });
    }

    // Track coupon entry
    function trackCouponEntry(couponCode) {
        trackEvent('coupon_entry', {
            'coupon_code': couponCode
        });
    }

    // Automatically track all file downloads
    function initDownloadTracking() {
        document.addEventListener('click', function(e) {
            var target = e.target.closest('a[href]');
            if (!target) return;

            var href = target.getAttribute('href');
            var fileExtensions = /\.(pdf|doc|docx|xls|xlsx|zip|rar|tar|gz|exe|dmg)$/i;

            if (fileExtensions.test(href)) {
                var fileName = href.split('/').pop();
                trackDownload(fileName, href.split('.').pop());
            }
        });
    }

    // Automatically track outbound links
    function initOutboundLinkTracking() {
        document.addEventListener('click', function(e) {
            var target = e.target.closest('a[href]');
            if (!target) return;

            var href = target.getAttribute('href');
            if (href && (href.startsWith('http://') || href.startsWith('https://'))
                && !href.includes(window.location.hostname)) {
                trackOutboundLink(href, target.textContent);
            }
        });
    }

    // Initialize automatic tracking
    function init() {
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', function() {
                initDownloadTracking();
                initOutboundLinkTracking();
            });
        } else {
            initDownloadTracking();
            initOutboundLinkTracking();
        }
    }

    // Public API
    return {
        init: init,
        trackEvent: trackEvent,
        trackFormSubmit: trackFormSubmit,
        trackButtonClick: trackButtonClick,
        trackDownload: trackDownload,
        trackOutboundLink: trackOutboundLink,
        trackVideo: trackVideo,
        trackProductFilter: trackProductFilter,
        trackProductSort: trackProductSort,
        trackReviewSubmit: trackReviewSubmit,
        trackCouponEntry: trackCouponEntry
    };
})();

// Auto-initialize
NopAnalytics.init();

Using the Analytics Module

@* Include in your theme layout *@
<script src="~/js/analytics-tracking.js"></script>

@* Use in templates *@
<script>
    // Track custom button click
    document.querySelector('.special-offer-btn')?.addEventListener('click', function() {
        NopAnalytics.trackButtonClick('Special Offer', 'Homepage Hero');
    });

    // Track product filter
    document.querySelector('.filter-price')?.addEventListener('change', function() {
        NopAnalytics.trackProductFilter('price', this.value);
    });

    // Track product sort
    document.querySelector('.product-sorting')?.addEventListener('change', function() {
        NopAnalytics.trackProductSort(this.value);
    });

    // Track review submission
    document.querySelector('.write-review-form')?.addEventListener('submit', function() {
        var rating = document.querySelector('input[name="rating"]:checked')?.value;
        NopAnalytics.trackReviewSubmit(
            '@Model.ProductId',
            '@Model.ProductName',
            rating
        );
    });
</script>

Advanced Event Tracking

Scroll Depth Tracking

// Track how far users scroll on product pages
(function() {
    var scrollDepths = [25, 50, 75, 100];
    var tracked = {};

    window.addEventListener('scroll', function() {
        var scrollPercent = Math.round((window.scrollY / (document.documentElement.scrollHeight - window.innerHeight)) * 100);

        scrollDepths.forEach(function(depth) {
            if (scrollPercent >= depth && !tracked[depth]) {
                gtag('event', 'scroll_depth', {
                    'percent_scrolled': depth,
                    'page_location': window.location.pathname
                });
                tracked[depth] = true;
            }
        });
    });
})();

Time on Page Tracking

// Track engaged time on page
(function() {
    var startTime = new Date().getTime();
    var engaged = true;
    var totalEngagedTime = 0;
    var lastCheck = startTime;

    // Track when user becomes inactive
    var inactivityEvents = ['blur', 'hidden'];
    inactivityEvents.forEach(function(event) {
        window.addEventListener(event, function() {
            if (engaged) {
                totalEngagedTime += (new Date().getTime() - lastCheck);
                engaged = false;
            }
        });
    });

    // Track when user becomes active again
    var activityEvents = ['focus', 'visible'];
    activityEvents.forEach(function(event) {
        window.addEventListener(event, function() {
            if (!engaged) {
                lastCheck = new Date().getTime();
                engaged = true;
            }
        });
    });

    // Send engaged time on page unload
    window.addEventListener('beforeunload', function() {
        if (engaged) {
            totalEngagedTime += (new Date().getTime() - lastCheck);
        }

        var engagedSeconds = Math.round(totalEngagedTime / 1000);

        gtag('event', 'engaged_time', {
            'value': engagedSeconds,
            'event_category': 'Engagement',
            'page_location': window.location.pathname
        });
    });
})();

Error Tracking

// Track JavaScript errors
window.addEventListener('error', function(e) {
    gtag('event', 'exception', {
        'description': e.message,
        'fatal': false,
        'error_location': e.filename + ':' + e.lineno
    });
});

// Track AJAX errors
if (typeof $ !== 'undefined') {
    $(document).ajaxError(function(event, jqXHR, settings, thrownError) {
        gtag('event', 'exception', {
            'description': 'AJAX Error: ' + thrownError,
            'fatal': false,
            'error_location': settings.url
        });
    });
}

Testing and Debugging Events

Enable Debug Mode

// Add to page temporarily for testing
gtag('config', 'G-XXXXXXXXXX', {
    'debug_mode': true
});

Using GA4 DebugView

  1. Enable debug mode in your browser:

    // Add to console or page
    window.gtag_enable_debug_mode = true;
    
  2. Or add query parameter:

    https://yourstore.com/?debug_mode=true
    
  3. Open GA4 DebugView:

    • Go to Google Analytics
    • Navigate to Admin > DebugView
    • Browse your site
    • See events in real-time

Console Logging

// Wrap gtag to log all events
var originalGtag = window.gtag;
window.gtag = function() {
    console.log('GA4 Event:', arguments);
    originalGtag.apply(window, arguments);
};

Event Naming Best Practices

Use Google's recommended event names when possible:

  • Use lowercase with underscores: add_to_cart (not AddToCart)
  • Be descriptive: newsletter_signup (not ns)
  • Be consistent across your site
  • Avoid special characters except underscores
  • Keep under 40 characters

Custom Event Naming Convention

For NopCommerce-specific events:

{category}_{action}_{object}

Examples:
- product_filter_price
- product_sort_name
- customer_update_profile
- vendor_contact_form

Next Steps