Google Analytics 4 Setup on Umbraco | OpsBlu Docs

Google Analytics 4 Setup on Umbraco

Install GA4 on Umbraco using NuGet packages, manual Razor views, or IIS integration

Umbraco provides multiple methods to implement Google Analytics 4, each suited to different .NET environments and deployment strategies. This guide covers NuGet package integration, manual Razor implementation, and IIS-level configuration.

Prerequisites

Before implementing GA4 on Umbraco:

  • GA4 Property Created - Set up your GA4 property in Google Analytics
  • Measurement ID - Obtain your GA4 measurement ID (format: G-XXXXXXXXXX)
  • Umbraco Backoffice Access - Administrator access required
  • Development Environment - Visual Studio 2022+ or VS Code with C# extension
  • .NET Version - .NET 6.0+ (Umbraco 10+) or .NET Framework 4.7.2+ (Umbraco 8)

Direct Razor integration provides maximum control and works across all Umbraco versions.

Step 1: Create Analytics Partial View

Navigate to your Umbraco project and create a partial view:

Location: /Views/Partials/Analytics/GoogleAnalytics.cshtml

@using Umbraco.Cms.Core.Models.PublishedContent
@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage

@{
    var gaId = "G-XXXXXXXXXX"; // Replace with your GA4 Measurement ID

    // Get GA ID from Umbraco settings if stored in config
    if (Model?.Value<string>("googleAnalyticsId") != null)
    {
        gaId = Model.Value<string>("googleAnalyticsId");
    }
}

@if (!string.IsNullOrEmpty(gaId))
{
    <!-- Google Analytics 4 -->
    <script async src="https://www.googletagmanager.com/gtag/js?id=@gaId"></script>
    <script>
        window.dataLayer = window.dataLayer || [];
        function gtag(){dataLayer.push(arguments);}
        gtag('js', new Date());

        gtag('config', '@gaId', {
            'send_page_view': true,
            'custom_map': {
                'dimension1': 'content_type',
                'dimension2': 'node_id',
                'dimension3': 'template_name'
            }
        });

        // Send Umbraco-specific data
        gtag('event', 'page_view', {
            'content_type': '@Model?.ContentType.Alias',
            'node_id': '@Model?.Id',
            'template_name': '@ViewData.GetType()?.Name'
        });
    </script>
}

Step 2: Include Partial in Master Layout

Edit your master layout template (usually Master.cshtml or Layout.cshtml):

Location: /Views/Master.cshtml

@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage
@{
    Layout = null;
}

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>@Model?.Value("title") ?? Model?.Name</title>

    @* Include Google Analytics before closing head *@
    @await Html.PartialAsync("~/Views/Partials/Analytics/GoogleAnalytics.cshtml")

    @RenderSection("head", required: false)
</head>
<body>
    @RenderBody()
</body>
</html>

Step 3: Configure GA4 ID in Umbraco Settings

Option A: Store in appsettings.json

{
  "Analytics": {
    "GoogleAnalytics": {
      "MeasurementId": "G-XXXXXXXXXX",
      "Enabled": true
    }
  }
}

Access in Razor:

@inject IConfiguration Configuration

@{
    var gaId = Configuration["Analytics:GoogleAnalytics:MeasurementId"];
}

Option B: Create Document Type Property

  1. Navigate to Settings → Document Types
  2. Select your Site Settings document type (or create one)
  3. Add new property:
    • Name: Google Analytics Measurement ID
    • Alias: googleAnalyticsId
    • Editor: Textstring
  4. Save document type
  5. Navigate to Content → Site Settings
  6. Enter your GA4 Measurement ID
  7. Save and publish

Step 4: Verify Installation

  1. Build and run your Umbraco site
  2. Open your site in a browser
  3. Open DevTools → Network tab
  4. Look for requests to google-analytics.com or googletagmanager.com
  5. Check Google Analytics → Realtime to see active users

Method 2: NuGet Package Integration

Use community packages for simplified GA4 integration.

Step 1: Install NuGet Package

Via Package Manager Console:

Install-Package Our.Umbraco.GoogleAnalytics

Via .NET CLI:

dotnet add package Our.Umbraco.GoogleAnalytics

Via Package Reference (.csproj):

<ItemGroup>
  <PackageReference Include="Our.Umbraco.GoogleAnalytics" Version="3.*" />
</ItemGroup>

Step 2: Configure Package

Register in Startup.cs/Program.cs:

For Umbraco 10+ (.NET 6+):

using Our.Umbraco.GoogleAnalytics.Extensions;

var builder = WebApplication.CreateBuilder(args);

builder.CreateUmbracoBuilder()
    .AddBackOffice()
    .AddWebsite()
    .AddDeliveryApi()
    .AddComposers()
    .AddGoogleAnalytics() // Add this line
    .Build();

var app = builder.Build();

// ... rest of configuration

For Umbraco 8 (.NET Framework):

using Our.Umbraco.GoogleAnalytics;

public class ApplicationEventHandler : IApplicationEventHandler
{
    public void OnApplicationStarted(UmbracoApplicationBase umbracoApplication,
        ApplicationContext applicationContext)
    {
        GoogleAnalyticsHelper.Register();
    }
}

Step 3: Configure in Backoffice

  1. Log in to Umbraco Backoffice (/umbraco)
  2. Navigate to Settings → Google Analytics
  3. Enter Measurement ID: G-XXXXXXXXXX
  4. Select Tracking Type: GA4
  5. Enable Enhanced Measurement (optional)
  6. Click Save

Step 4: Test Implementation

  1. Clear browser cache
  2. Visit your site
  3. Check Google Analytics → Realtime
  4. Verify page views appear

Method 3: IIS Application-Level Integration

Implement GA4 at IIS level for all applications in a site.

Step 1: Create IIS HTTP Module

Create a custom HTTP module:

GoogleAnalyticsModule.cs:

using System;
using System.Web;

namespace YourProject.Modules
{
    public class GoogleAnalyticsModule : IHttpModule
    {
        private const string MeasurementId = "G-XXXXXXXXXX";

        public void Init(HttpApplication context)
        {
            context.PreRequestHandlerExecute += OnPreRequestHandlerExecute;
        }

        private void OnPreRequestHandlerExecute(object sender, EventArgs e)
        {
            HttpApplication app = (HttpApplication)sender;
            HttpContext context = app.Context;

            // Only inject for HTML responses
            if (context.Response.ContentType == "text/html")
            {
                context.Response.Filter = new GoogleAnalyticsFilter(
                    context.Response.Filter,
                    MeasurementId
                );
            }
        }

        public void Dispose() { }
    }

    public class GoogleAnalyticsFilter : System.IO.Stream
    {
        private System.IO.Stream _responseStream;
        private string _measurementId;

        public GoogleAnalyticsFilter(System.IO.Stream stream, string measurementId)
        {
            _responseStream = stream;
            _measurementId = measurementId;
        }

        public override void Write(byte[] buffer, int offset, int count)
        {
            string html = System.Text.Encoding.UTF8.GetString(buffer, offset, count);

            // Inject GA4 before </head>
            string gaScript = $@"
                <script async src='https://www.googletagmanager.com/gtag/js?id={_measurementId}'></script>
                <script>
                    window.dataLayer = window.dataLayer || [];
                    function gtag(){{dataLayer.push(arguments);}}
                    gtag('js', new Date());
                    gtag('config', '{_measurementId}');
                </script>
            ";

            html = html.Replace("</head>", gaScript + "</head>");

            byte[] data = System.Text.Encoding.UTF8.GetBytes(html);
            _responseStream.Write(data, 0, data.Length);
        }

        // Implement required abstract members
        public override bool CanRead => _responseStream.CanRead;
        public override bool CanSeek => _responseStream.CanSeek;
        public override bool CanWrite => _responseStream.CanWrite;
        public override long Length => _responseStream.Length;
        public override long Position
        {
            get => _responseStream.Position;
            set => _responseStream.Position = value;
        }
        public override void Flush() => _responseStream.Flush();
        public override int Read(byte[] buffer, int offset, int count)
            => _responseStream.Read(buffer, offset, count);
        public override long Seek(long offset, System.IO.SeekOrigin origin)
            => _responseStream.Seek(offset, origin);
        public override void SetLength(long value)
            => _responseStream.SetLength(value);
    }
}

Step 2: Register Module in Web.config

For IIS Integrated Pipeline:

<configuration>
  <system.webServer>
    <modules>
      <add name="GoogleAnalyticsModule"
           type="YourProject.Modules.GoogleAnalyticsModule, YourProject" />
    </modules>
  </system.webServer>
</configuration>

For IIS Classic Pipeline:

<configuration>
  <system.web>
    <httpModules>
      <add name="GoogleAnalyticsModule"
           type="YourProject.Modules.GoogleAnalyticsModule, YourProject" />
    </httpModules>
  </system.web>
</configuration>

Enhanced Umbraco Integration

Track Umbraco Member Data

@using Umbraco.Cms.Core.Security
@inject IMemberManager MemberManager

@{
    var member = await MemberManager.GetCurrentMemberAsync();
}

<script>
    @if (member != null)
    {
        <text>
        gtag('set', 'user_properties', {
            'member_status': 'logged_in',
            'member_id': '@member.Key',
            'member_type': '@member.ContentType.Alias'
        });
        </text>
    }
    else
    {
        <text>
        gtag('set', 'user_properties', {
            'member_status': 'visitor'
        });
        </text>
    }
</script>

Track Content Type and Template

<script>
    gtag('event', 'page_view', {
        'content_type': '@Model?.ContentType.Alias',
        'content_name': '@Model?.Name',
        'template_alias': '@ViewData["TemplateAlias"]',
        'node_path': '@Model?.Path',
        'culture': '@Model?.GetCultureFromDomains()?.Culture'
    });
</script>

Track Umbraco Forms Submissions

@using Umbraco.Forms.Core.Services
@inject IFormService FormService

<script>
    // Track form submission
    document.addEventListener('DOMContentLoaded', function() {
        document.querySelectorAll('form.umbraco-forms-form').forEach(function(form) {
            form.addEventListener('submit', function(e) {
                gtag('event', 'form_submit', {
                    'form_id': form.getAttribute('data-form-id'),
                    'form_name': form.getAttribute('data-form-name')
                });
            });
        });
    });
</script>

Performance Optimization

Preconnect to GA4 Domains

<head>
    <link rel="preconnect" href="https://www.google-analytics.com">
    <link rel="preconnect" href="https://www.googletagmanager.com">

    @await Html.PartialAsync("~/Views/Partials/Analytics/GoogleAnalytics.cshtml")
</head>

Defer Loading for Better LCP

<script>
    // Defer GA4 until after page load
    window.addEventListener('load', function() {
        var script = document.createElement('script');
        script.src = 'https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX';
        script.async = true;
        document.head.appendChild(script);

        window.dataLayer = window.dataLayer || [];
        function gtag(){dataLayer.push(arguments);}
        gtag('js', new Date());
        gtag('config', 'G-XXXXXXXXXX');
    });
</script>

Conditional Loading by Environment

@inject Microsoft.Extensions.Hosting.IHostEnvironment HostEnvironment

@if (HostEnvironment.IsProduction())
{
    @await Html.PartialAsync("~/Views/Partials/Analytics/GoogleAnalytics.cshtml")
}

IIS-Specific Configuration

Enable IIS Compression for Analytics Scripts

In Web.config:

<configuration>
  <system.webServer>
    <urlCompression doStaticCompression="true" doDynamicCompression="true" />
    <httpCompression>
      <scheme name="gzip" dll="%Windir%\system32\inetsrv\gzip.dll" />
      <dynamicTypes>
        <add mimeType="application/javascript" enabled="true" />
        <add mimeType="text/javascript" enabled="true" />
      </dynamicTypes>
      <staticTypes>
        <add mimeType="application/javascript" enabled="true" />
      </staticTypes>
    </httpCompression>
  </system.webServer>
</configuration>

Set Cache Headers for GA4 Scripts

<configuration>
  <system.webServer>
    <staticContent>
      <clientCache cacheControlMode="UseMaxAge" cacheControlMaxAge="365.00:00:00" />
    </staticContent>
  </system.webServer>
</configuration>

Validation and Testing

Debug Mode

Enable GA4 debug mode for development:

@inject Microsoft.Extensions.Hosting.IHostEnvironment HostEnvironment

<script>
    gtag('config', 'G-XXXXXXXXXX', {
        'debug_mode': @(HostEnvironment.IsDevelopment() ? "true" : "false")
    });
</script>

Real-Time Testing

  1. Open Google Analytics → Realtime → Overview
  2. Navigate your Umbraco site in incognito mode
  3. Verify events appear in real-time report
  4. Check custom dimensions populate correctly

Browser DevTools Testing

  1. Open DevTools → Console
  2. Type: dataLayer and press Enter
  3. Verify data layer contains expected events
  4. Check for GA4 errors in console

Common Issues

GA4 Not Tracking

Cause: Incorrect Measurement ID Solution:

  • Verify G-XXXXXXXXXX format in Razor view
  • Check for typos in configuration
  • Ensure ID matches GA4 property

Duplicate Tracking

Cause: Multiple GA4 implementations Solution:

  • Search solution for duplicate gtag references
  • Remove redundant partial views
  • Check for package + manual implementation conflict

Umbraco Cache Preventing Updates

Cause: Umbraco cache not cleared after code changes Solution:

# Clear Umbraco cache
Remove-Item -Path "App_Data\TEMP\*" -Recurse -Force

# Or use Umbraco Backoffice
# Settings → Examine Management → Rebuild all indexes

IIS Module Not Firing

Cause: Module not registered or wrong pipeline mode Solution:

  • Verify Web.config registration
  • Check IIS application pool mode (Integrated vs Classic)
  • Restart IIS: iisreset

Next Steps