Google Analytics 4 Setup on Episerver | OpsBlu Docs

Google Analytics 4 Setup on Episerver

Complete guide to installing and configuring GA4 on Episerver CMS and Commerce (Optimizely)

This guide covers setting up Google Analytics 4 (GA4) on Episerver CMS and Commerce platforms (now Optimizely).

Prerequisites

  • Episerver CMS 11+ or CMS 12+
  • Google Analytics 4 property created
  • GA4 Measurement ID (format: G-XXXXXXXXXX)
  • Access to Episerver solution and templates

Implementation Methods

Add GA4 directly to your master layout for site-wide tracking.

Step 1: Locate Master Layout

Find your main layout file, typically at:

  • Views/Shared/_Layout.cshtml (Standard MVC)
  • Views/Shared/_Root.cshtml (Episerver Foundation)
  • Views/Shared/Layouts/_Layout.cshtml (Custom structure)

Step 2: Add GA4 Script

Add this code in the <head> section:

@{
    var gaTrackingId = "G-XXXXXXXXXX"; // Replace with your Measurement ID
    var isEditMode = EPiServer.Editor.PageEditing.PageIsInEditMode;
}

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

      gtag('config', '@gaTrackingId', {
        'send_page_view': true
      });
    </script>
}

Key Points:

  • The isEditMode check prevents tracking in Episerver edit mode
  • Replace G-XXXXXXXXXX with your actual Measurement ID
  • Script is loaded asynchronously for performance

Method 2: Configuration-Based Approach

Store the Measurement ID in configuration for easier management across environments.

Step 1: Add to appsettings.json (CMS 12+)

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

For CMS 11, use web.config:

<configuration>
  <appSettings>
    <add key="GoogleAnalytics:MeasurementId" value="G-XXXXXXXXXX" />
    <add key="GoogleAnalytics:Enabled" value="true" />
  </appSettings>
</configuration>

Step 2: Create Configuration Class

public class GoogleAnalyticsSettings
{
    public string MeasurementId { get; set; }
    public bool Enabled { get; set; }
}

Step 3: Register in Startup (CMS 12+)

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<GoogleAnalyticsSettings>(
        Configuration.GetSection("GoogleAnalytics"));
}

Step 4: Update Layout Template

@using Microsoft.Extensions.Options
@inject IOptions<GoogleAnalyticsSettings> GASettings

@{
    var gaSettings = GASettings.Value;
    var isEditMode = EPiServer.Editor.PageEditing.PageIsInEditMode;
}

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

      gtag('config', '@gaSettings.MeasurementId');
    </script>
}

Method 3: Multi-Site Configuration

For managing multiple sites with different GA4 properties.

Step 1: Add Site Definition Extensions

public static class SiteDefinitionExtensions
{
    public static string GetGAMeasurementId(this SiteDefinition site)
    {
        return site.SiteUrl.Host switch
        {
            "www.site1.com" => "G-XXXXXXXXX1",
            "www.site2.com" => "G-XXXXXXXXX2",
            _ => "G-DEFAULTXXXX"
        };
    }
}

Or store in custom site properties:

[UIHint("Textarea")]
public virtual string GAMeasurementId { get; set; }

Step 2: Use in Template

@{
    var currentSite = SiteDefinition.Current;
    var gaId = currentSite.GetGAMeasurementId();
    var isEditMode = EPiServer.Editor.PageEditing.PageIsInEditMode;
}

@if (!string.IsNullOrEmpty(gaId) && !isEditMode)
{
    <!-- Google tag (gtag.js) -->
    <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');
    </script>
}

Method 4: View Component Approach

Create a reusable view component for cleaner templates.

Step 1: Create View Component

using Microsoft.AspNetCore.Mvc;
using EPiServer;
using EPiServer.Web;

public class GoogleAnalyticsViewComponent : ViewComponent
{
    private readonly IContentLoader _contentLoader;
    private readonly ISiteDefinitionRepository _siteRepository;

    public GoogleAnalyticsViewComponent(
        IContentLoader contentLoader,
        ISiteDefinitionRepository siteRepository)
    {
        _contentLoader = contentLoader;
        _siteRepository = siteRepository;
    }

    public IViewComponentResult Invoke()
    {
        // Don't load in edit mode
        if (EPiServer.Editor.PageEditing.PageIsInEditMode)
        {
            return Content(string.Empty);
        }

        var currentSite = _siteRepository.Get(SiteDefinition.Current.Id);
        var measurementId = currentSite.GetGAMeasurementId();

        if (string.IsNullOrEmpty(measurementId))
        {
            return Content(string.Empty);
        }

        return View("~/Views/Shared/Components/GoogleAnalytics/Default.cshtml",
            measurementId);
    }
}

Step 2: Create View

Create Views/Shared/Components/GoogleAnalytics/Default.cshtml:

@model string

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

  gtag('config', '@Model');
</script>

Step 3: Add to Layout

@await Component.InvokeAsync("GoogleAnalytics")

Enhanced Configuration

User Properties

Track Episerver-specific user information:

<script>
  gtag('config', '@gaId', {
    'user_properties': {
      'cms_version': '@EPiServer.Framework.FrameworkVersion.Current',
      'site_name': '@SiteDefinition.Current.Name'
    }
  });
</script>

Custom Dimensions via User Properties

@{
    var currentPage = Model as IContent;
    var contentType = currentPage?.GetOriginalType().Name ?? "Unknown";
}

<script>
  gtag('config', '@gaId', {
    'user_properties': {
      'content_type': '@contentType',
      'language': '@Model.Language?.Name',
      'is_commerce': '@(Model is EPiServer.Commerce.Catalog.ContentTypes.EntryContentBase ? "true" : "false")'
    }
  });
</script>

Debug Mode

Enable debug mode in development:

@{
    var isDebug = HostingEnvironment.IsDevelopment();
}

<script>
  gtag('config', '@gaId', {
    'debug_mode': @(isDebug ? "true" : "false")
  });
</script>

Content Delivery API (Headless)

For headless Episerver implementations:

Server-Side Setup

Add GA Measurement ID to API responses:

public class ContentApiModelConverter : IContentApiModelConverter
{
    public ConvertedContentApiModel Convert(IContent content, ConverterContext converterContext)
    {
        var model = new ConvertedContentApiModel();

        // Add GA tracking ID to metadata
        model.Properties.Add("gaTrackingId", "G-XXXXXXXXXX");

        return model;
    }
}

Client-Side Implementation

In your frontend framework:

// React example
import { useEffect } from 'react';

function useGA4(measurementId) {
  useEffect(() => {
    if (!measurementId) return;

    // Load gtag script
    const script = document.createElement('script');
    script.src = `https://www.googletagmanager.com/gtag/js?id=${measurementId}`;
    script.async = true;
    document.head.appendChild(script);

    // Initialize GA4
    window.dataLayer = window.dataLayer || [];
    function gtag(){dataLayer.push(arguments);}
    gtag('js', new Date());
    gtag('config', measurementId);
  }, [measurementId]);
}

// In your component
function App({ content }) {
  useGA4(content.gaTrackingId);
  // ...
}

Verification

1. Check Script Loading

Open your site and view source:

  1. Look for gtag/js?id=G-XXXXXXXXXX
  2. Verify the script is NOT present in edit mode
  3. Check that Measurement ID is correct

2. Browser Developer Tools

  1. Open DevTools Network tab
  2. Filter for google-analytics.com or gtag
  3. Look for successful requests
  4. Check the collect endpoint calls

3. GA4 DebugView

Enable debug mode and check DebugView:

<script>
  gtag('config', '@gaId', {
    'debug_mode': true
  });
</script>

Visit your site and check GA4 > Configure > DebugView

4. Tag Assistant

  1. Install Google Tag Assistant
  2. Connect to your site
  3. Verify GA4 tag is firing
  4. Check for errors or warnings

Troubleshooting

Script Not Loading in Edit Mode

Cause: Edit mode check missing or incorrect

Solution: Add edit mode check:

var isEditMode = EPiServer.Editor.PageEditing.PageIsInEditMode;

Multiple GA4 Tags Firing

Cause: Script added in multiple places

Solution:

  1. Search for gtag/js in all templates
  2. Remove duplicate implementations
  3. Use a single, centralized location

Preview Mode Tracking

Problem: Tracking fires in preview mode

Solution: Add preview mode check:

var isPreview = EPiServer.Web.ContextMode.Current == EPiServer.Web.ContextMode.Preview;
if (!isEditMode && !isPreview)
{
    // Add tracking
}

Content Security Policy Issues

Error: Refused to load script from Google

Solution: Update CSP headers:

<system.webServer>
  <httpProtocol>
    <customHeaders>
      <add name="Content-Security-Policy"
           value="script-src 'self' 'unsafe-inline' https://*.googletagmanager.com https://*.google-analytics.com; connect-src 'self' https://*.google-analytics.com https://*.analytics.google.com;" />
    </customHeaders>
  </httpProtocol>
</system.webServer>

Next Steps

Additional Resources