Google Analytics Event Tracking on Umbraco | OpsBlu Docs

Google Analytics Event Tracking on Umbraco

How to implement custom GA4 event tracking on Umbraco. Covers recommended events, custom event parameters, ecommerce data layer events, and DebugView.

Track custom events in Umbraco to measure user interactions, content engagement, and business-specific actions. This guide covers client-side JavaScript tracking, server-side C# tracking, and Umbraco-specific event patterns.

Prerequisites

Before tracking custom events:

  • GA4 Setup Complete - Follow GA4 Setup Guide
  • Measurement ID Configured - GA4 tracking code installed
  • Umbraco Backoffice Access - For testing and validation
  • Development Environment - Visual Studio or VS Code

Client-Side Event Tracking (JavaScript)

Basic Event Tracking in Razor Views

Track events directly in your Umbraco Razor views:

@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage

<button id="download-brochure" data-file-name="Product-Brochure.pdf">
    Download Brochure
</button>

<script>
    document.getElementById('download-brochure').addEventListener('click', function() {
        gtag('event', 'file_download', {
            'file_name': this.getAttribute('data-file-name'),
            'content_type': '@Model?.ContentType.Alias',
            'node_id': '@Model?.Id'
        });
    });
</script>

Track Umbraco Content Interactions

Track Article Scroll Depth:

<script>
    var scrollDepths = [25, 50, 75, 100];
    var triggeredDepths = [];

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

        scrollDepths.forEach(function(depth) {
            if (scrollPercent >= depth && !triggeredDepths.includes(depth)) {
                triggeredDepths.push(depth);

                gtag('event', 'scroll', {
                    'event_category': 'engagement',
                    'event_label': depth + '%',
                    'value': depth,
                    'content_name': '@Model?.Name',
                    'content_type': '@Model?.ContentType.Alias'
                });
            }
        });
    });
</script>

Track Video Plays:

<video id="product-video" controls>
    <source src="@Model?.Value("videoUrl")" type="video/mp4">
</video>

<script>
    var video = document.getElementById('product-video');

    video.addEventListener('play', function() {
        gtag('event', 'video_start', {
            'video_title': '@Model?.Value("videoTitle")',
            'video_url': '@Model?.Value("videoUrl")',
            'content_id': '@Model?.Id'
        });
    });

    video.addEventListener('ended', function() {
        gtag('event', 'video_complete', {
            'video_title': '@Model?.Value("videoTitle")',
            'video_url': '@Model?.Value("videoUrl")'
        });
    });
</script>
<script>
    document.addEventListener('DOMContentLoaded', function() {
        // Track all external links
        document.querySelectorAll('a[href^="http"]').forEach(function(link) {
            if (link.hostname !== window.location.hostname) {
                link.addEventListener('click', function(e) {
                    gtag('event', 'click', {
                        'event_category': 'outbound',
                        'event_label': this.href,
                        'transport_type': 'beacon',
                        'source_page': '@Model?.Name'
                    });
                });
            }
        });
    });
</script>

Server-Side Event Tracking (C#)

Create Analytics Service

IAnalyticsService.cs:

using System.Threading.Tasks;

namespace YourProject.Services
{
    public interface IAnalyticsService
    {
        Task TrackEvent(string eventName, Dictionary<string, object> parameters);
        Task TrackPageView(string pagePath, string pageTitle);
    }
}

GoogleAnalyticsService.cs:

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;

namespace YourProject.Services
{
    public class GoogleAnalyticsService : IAnalyticsService
    {
        private readonly HttpClient _httpClient;
        private readonly string _measurementId;
        private readonly string _apiSecret;

        public GoogleAnalyticsService(HttpClient httpClient, IConfiguration configuration)
        {
            _httpClient = httpClient;
            _measurementId = configuration["Analytics:GoogleAnalytics:MeasurementId"];
            _apiSecret = configuration["Analytics:GoogleAnalytics:ApiSecret"];
        }

        public async Task TrackEvent(string eventName, Dictionary<string, object> parameters)
        {
            var payload = new
            {
                client_id = Guid.NewGuid().ToString(), // Or use session/member ID
                events = new[]
                {
                    new
                    {
                        name = eventName,
                        @params = parameters
                    }
                }
            };

            var json = JsonSerializer.Serialize(payload);
            var content = new StringContent(json, Encoding.UTF8, "application/json");

            var url = $"https://www.google-analytics.com/mp/collect?measurement_id={_measurementId}&api_secret={_apiSecret}";

            await _httpClient.PostAsync(url, content);
        }

        public async Task TrackPageView(string pagePath, string pageTitle)
        {
            await TrackEvent("page_view", new Dictionary<string, object>
            {
                { "page_path", pagePath },
                { "page_title", pageTitle }
            });
        }
    }
}

Register Service in Startup

Program.cs (Umbraco 10+):

using YourProject.Services;

var builder = WebApplication.CreateBuilder(args);

builder.CreateUmbracoBuilder()
    .AddBackOffice()
    .AddWebsite()
    .AddDeliveryApi()
    .AddComposers()
    .Build();

// Register Analytics Service
builder.Services.AddHttpClient<IAnalyticsService, GoogleAnalyticsService>();

var app = builder.Build();

// ... rest of configuration

Track Events from Controllers

ContactController.cs:

using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Web.Common.Controllers;
using YourProject.Services;

namespace YourProject.Controllers
{
    public class ContactController : SurfaceController
    {
        private readonly IAnalyticsService _analyticsService;

        public ContactController(
            IUmbracoContextAccessor umbracoContextAccessor,
            IUmbracoDatabaseFactory databaseFactory,
            ServiceContext services,
            AppCaches appCaches,
            IProfilingLogger profilingLogger,
            IPublishedUrlProvider publishedUrlProvider,
            IAnalyticsService analyticsService)
            : base(umbracoContextAccessor, databaseFactory, services, appCaches, profilingLogger, publishedUrlProvider)
        {
            _analyticsService = analyticsService;
        }

        [HttpPost]
        public async Task<IActionResult> SubmitForm(ContactFormModel model)
        {
            if (ModelState.IsValid)
            {
                // Process form submission
                // ...

                // Track event
                await _analyticsService.TrackEvent("form_submit", new Dictionary<string, object>
                {
                    { "form_name", "contact_form" },
                    { "form_id", "contact" },
                    { "source_page", CurrentPage?.Name }
                });

                return RedirectToCurrentUmbracoPage();
            }

            return CurrentUmbracoPage();
        }
    }
}

Umbraco Forms Integration

Track Form Submissions

Create FormEventHandler.cs:

using Umbraco.Forms.Core.Services.Notifications;
using Microsoft.Extensions.Logging;
using YourProject.Services;

namespace YourProject.EventHandlers
{
    public class FormEventHandler : INotificationHandler<FormSubmittedNotification>
    {
        private readonly IAnalyticsService _analyticsService;
        private readonly ILogger<FormEventHandler> _logger;

        public FormEventHandler(IAnalyticsService analyticsService, ILogger<FormEventHandler> logger)
        {
            _analyticsService = analyticsService;
            _logger = logger;
        }

        public async Task Handle(FormSubmittedNotification notification, CancellationToken cancellationToken)
        {
            try
            {
                var form = notification.Form;
                var record = notification.Record;

                await _analyticsService.TrackEvent("form_submit", new Dictionary<string, object>
                {
                    { "form_id", form.Id.ToString() },
                    { "form_name", form.Name },
                    { "record_id", record.Id.ToString() },
                    { "page_id", record.PageId.ToString() }
                });

                _logger.LogInformation($"Tracked form submission: {form.Name}");
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Failed to track form submission");
            }
        }
    }
}

Register in Composer:

using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Core.Notifications;

namespace YourProject.Composers
{
    public class FormTrackingComposer : IComposer
    {
        public void Compose(IUmbracoBuilder builder)
        {
            builder.AddNotificationHandler<FormSubmittedNotification, FormEventHandler>();
        }
    }
}

Track Form Field Interactions

In Razor view:

@using Umbraco.Forms.Web
@model Umbraco.Forms.Web.Models.FormViewModel

<script>
    document.addEventListener('DOMContentLoaded', function() {
        var formElement = document.querySelector('.umbraco-forms-form');

        // Track form start (first field interaction)
        var formStarted = false;
        formElement.querySelectorAll('input, textarea, select').forEach(function(field) {
            field.addEventListener('focus', function() {
                if (!formStarted) {
                    formStarted = true;
                    gtag('event', 'form_start', {
                        'form_id': '@Model.FormId',
                        'form_name': '@Model.FormName'
                    });
                }
            });
        });

        // Track form abandonment
        window.addEventListener('beforeunload', function(e) {
            if (formStarted && !formSubmitted) {
                gtag('event', 'form_abandon', {
                    'form_id': '@Model.FormId',
                    'form_name': '@Model.FormName'
                });
            }
        });

        var formSubmitted = false;
        formElement.addEventListener('submit', function() {
            formSubmitted = true;
        });
    });
</script>

Track Content Publishing Events

Create Content Event Handler

ContentPublishingEventHandler.cs:

using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Notifications;
using YourProject.Services;

namespace YourProject.EventHandlers
{
    public class ContentPublishingEventHandler :
        INotificationHandler<ContentPublishedNotification>
    {
        private readonly IAnalyticsService _analyticsService;

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

        public async Task Handle(ContentPublishedNotification notification, CancellationToken cancellationToken)
        {
            foreach (var node in notification.PublishedEntities)
            {
                await _analyticsService.TrackEvent("content_published", new Dictionary<string, object>
                {
                    { "content_type", node.ContentType.Alias },
                    { "content_name", node.Name },
                    { "node_id", node.Id },
                    { "author", node.WriterId }
                });
            }
        }
    }
}

Track Member Actions

Track Member Registration

using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;

namespace YourProject.Controllers
{
    public class MemberController : SurfaceController
    {
        private readonly IMemberManager _memberManager;
        private readonly IAnalyticsService _analyticsService;

        public MemberController(
            IMemberManager memberManager,
            IAnalyticsService analyticsService,
            /* other dependencies */)
        {
            _memberManager = memberManager;
            _analyticsService = analyticsService;
        }

        [HttpPost]
        public async Task<IActionResult> Register(RegisterModel model)
        {
            if (ModelState.IsValid)
            {
                var identityUser = MemberIdentityUser.CreateNew(
                    model.Email,
                    model.Email,
                    "Member",
                    true,
                    model.Name
                );

                var result = await _memberManager.CreateAsync(
                    identityUser,
                    model.Password
                );

                if (result.Succeeded)
                {
                    // Track registration
                    await _analyticsService.TrackEvent("sign_up", new Dictionary<string, object>
                    {
                        { "method", "email" },
                        { "member_type", "Member" }
                    });

                    return RedirectToAction("Welcome");
                }
            }

            return CurrentUmbracoPage();
        }

        [HttpPost]
        public async Task<IActionResult> Login(LoginModel model)
        {
            var result = await _memberManager.SignInAsync(model.Email, model.Password, model.RememberMe);

            if (result.Succeeded)
            {
                await _analyticsService.TrackEvent("login", new Dictionary<string, object>
                {
                    { "method", "email" }
                });

                return RedirectToCurrentUmbracoPage();
            }

            return CurrentUmbracoPage();
        }
    }
}

Track File Downloads

Track Media Downloads

@{
    var pdfFile = Model?.Value<IPublishedContent>("downloadablePdf");
}

@if (pdfFile != null)
{
    <a href="@pdfFile.Url()"
       class="download-link"
       data-file-name="@pdfFile.Name"
       data-file-type="@pdfFile.ContentType.Alias"
       download>
        Download PDF
    </a>

    <script>
        document.querySelectorAll('.download-link').forEach(function(link) {
            link.addEventListener('click', function(e) {
                gtag('event', 'file_download', {
                    'file_name': this.getAttribute('data-file-name'),
                    'file_type': this.getAttribute('data-file-type'),
                    'link_url': this.href,
                    'content_id': '@Model?.Id'
                });
            });
        });
    </script>
}

Track Search Queries

SearchController.cs:

using Examine;
using Umbraco.Cms.Infrastructure.Examine;

namespace YourProject.Controllers
{
    public class SearchController : SurfaceController
    {
        private readonly IExamineManager _examineManager;
        private readonly IAnalyticsService _analyticsService;

        public SearchController(
            IExamineManager examineManager,
            IAnalyticsService analyticsService,
            /* other dependencies */)
        {
            _examineManager = examineManager;
            _analyticsService = analyticsService;
        }

        [HttpGet]
        public async Task<IActionResult> Search(string query)
        {
            if (!string.IsNullOrWhiteSpace(query))
            {
                if (_examineManager.TryGetIndex(Constants.UmbracoIndexes.ExternalIndexName, out var index))
                {
                    var searcher = index.Searcher;
                    var results = searcher.CreateQuery("content")
                        .Field("nodeName", query)
                        .Execute();

                    // Track search
                    await _analyticsService.TrackEvent("search", new Dictionary<string, object>
                    {
                        { "search_term", query },
                        { "results_count", results.TotalItemCount }
                    });

                    return View("SearchResults", results);
                }
            }

            return View("SearchResults", null);
        }
    }
}

Enhanced Event Tracking

Track Time on Page

<script>
    var startTime = new Date().getTime();

    window.addEventListener('beforeunload', function() {
        var endTime = new Date().getTime();
        var timeOnPage = Math.round((endTime - startTime) / 1000); // seconds

        gtag('event', 'time_on_page', {
            'event_category': 'engagement',
            'value': timeOnPage,
            'content_name': '@Model?.Name',
            'content_type': '@Model?.ContentType.Alias',
            'non_interaction': true
        });
    });
</script>

Track Reading Progress

<script>
    var articleContent = document.querySelector('.article-content');
    if (articleContent) {
        var observer = new IntersectionObserver(function(entries) {
            entries.forEach(function(entry) {
                if (entry.isIntersecting) {
                    gtag('event', 'article_read', {
                        'event_category': 'engagement',
                        'event_label': 'Article Read',
                        'article_title': '@Model?.Name',
                        'article_id': '@Model?.Id'
                    });
                    observer.disconnect();
                }
            });
        }, { threshold: 0.9 }); // 90% visible

        observer.observe(articleContent);
    }
</script>

Common Event Patterns

// Page engagement
gtag('event', 'page_view');
gtag('event', 'scroll');
gtag('event', 'click');

// Forms
gtag('event', 'form_start');
gtag('event', 'form_submit');
gtag('event', 'form_abandon');

// Content interaction
gtag('event', 'file_download');
gtag('event', 'video_start');
gtag('event', 'video_complete');

// User actions
gtag('event', 'sign_up');
gtag('event', 'login');
gtag('event', 'search');

// Custom Umbraco events
gtag('event', 'content_published'); // Server-side
gtag('event', 'member_registration'); // Server-side

Testing and Debugging

Enable Debug Mode

@inject Microsoft.Extensions.Hosting.IHostEnvironment HostEnvironment

<script>
    @if (HostEnvironment.IsDevelopment())
    {
        <text>
        window.dataLayer = window.dataLayer || [];
        window.dataLayer.push({'event': 'gtm.js', 'gtm.start': new Date().getTime()});

        // Log all events to console
        var originalGtag = window.gtag;
        window.gtag = function() {
            console.log('GA4 Event:', arguments);
            originalGtag.apply(this, arguments);
        };
        </text>
    }
</script>

Test Events in Realtime

  1. Open Google Analytics → Realtime → Events
  2. Perform action on Umbraco site
  3. Verify event appears within seconds
  4. Check event parameters populate correctly

Next Steps