Google Tag Manager Setup on Umbraco | OpsBlu Docs

Google Tag Manager Setup on Umbraco

Step-by-step Google Tag Manager installation and configuration guide for Umbraco. Covers container code placement, data layer implementation, and.

Implement Google Tag Manager (GTM) on your Umbraco site to centralize all tracking tags and manage analytics without code deployments. This guide covers Razor view integration, NuGet packages, and IIS-level configuration for .NET environments.

Prerequisites

Before implementing GTM:

  • GTM Account Created - Sign up at tagmanager.google.com
  • GTM Container Created - Set up a web container
  • Container ID - Obtain your GTM ID (format: GTM-XXXXXXX)
  • Umbraco Backoffice Access - Administrator access required
  • Development Environment - Visual Studio 2022+ or VS Code

Step 1: Create GTM Partial View

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

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

@{
    var gtmId = "GTM-XXXXXXX"; // Replace with your GTM Container ID

    // Get GTM ID from Umbraco settings if stored
    if (Model?.Value<string>("gtmContainerId") != null)
    {
        gtmId = Model.Value<string>("gtmContainerId");
    }
}

@if (!string.IsNullOrEmpty(gtmId))
{
    <!-- Google Tag Manager -->
    <script>
        (function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
        new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
        j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
        'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
        })(window,document,'script','dataLayer','@gtmId');
    </script>
    <!-- End Google Tag Manager -->
}

Step 2: Create GTM NoScript Partial

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

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

@{
    var gtmId = "GTM-XXXXXXX"; // Must match the ID in GoogleTagManager.cshtml

    if (Model?.Value<string>("gtmContainerId") != null)
    {
        gtmId = Model.Value<string>("gtmContainerId");
    }
}

@if (!string.IsNullOrEmpty(gtmId))
{
    <!-- Google Tag Manager (noscript) -->
    <noscript>
        <iframe src="https://www.googletagmanager.com/ns.html?id=@gtmId"
                height="0" width="0" style="display:none;visibility:hidden">
        </iframe>
    </noscript>
    <!-- End Google Tag Manager (noscript) -->
}

Step 3: Include in Master Layout

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 GTM in head - as high as possible *@
    @await Html.PartialAsync("~/Views/Partials/Analytics/GoogleTagManager.cshtml")

    @RenderSection("head", required: false)
</head>
<body>
    @* Include GTM noscript immediately after opening body tag *@
    @await Html.PartialAsync("~/Views/Partials/Analytics/GoogleTagManagerNoScript.cshtml")

    @RenderBody()
</body>
</html>

Step 4: Configure GTM ID in Umbraco

Option A: Store in appsettings.json

{
  "Analytics": {
    "GoogleTagManager": {
      "ContainerId": "GTM-XXXXXXX",
      "Enabled": true,
      "Environment": {
        "Development": "GTM-DEV123",
        "Production": "GTM-PROD456"
      }
    }
  }
}

Access in Razor:

@inject IConfiguration Configuration
@inject Microsoft.Extensions.Hosting.IHostEnvironment HostEnvironment

@{
    var gtmId = HostEnvironment.IsProduction()
        ? Configuration["Analytics:GoogleTagManager:Environment:Production"]
        : Configuration["Analytics:GoogleTagManager:Environment:Development"];

    var enabled = Configuration.GetValue<bool>("Analytics:GoogleTagManager:Enabled");
}

@if (enabled && !string.IsNullOrEmpty(gtmId))
{
    <!-- GTM Script -->
}

Option B: Create Document Type Property

  1. Navigate to Settings → Document Types
  2. Select Site Settings document type
  3. Add property:
    • Name: GTM Container ID
    • Alias: gtmContainerId
    • Editor: Textstring
  4. Save document type
  5. Navigate to Content → Site Settings
  6. Enter GTM Container ID: GTM-XXXXXXX
  7. Save and publish

Step 5: Verify Installation

  1. Build and run Umbraco site
  2. Open site in browser
  3. Open DevTools → Console
  4. Type: dataLayer and press Enter
  5. Verify array exists with GTM initialization
  6. Install Google Tag Assistant Chrome extension
  7. Verify GTM container detected

Method 2: NuGet Package Integration

Install GTM Package

Install-Package Our.Umbraco.GoogleTagManager

Or via .NET CLI:

dotnet add package Our.Umbraco.GoogleTagManager

Configure Package

In Program.cs (Umbraco 10+):

using Our.Umbraco.GoogleTagManager.Extensions;

var builder = WebApplication.CreateBuilder(args);

builder.CreateUmbracoBuilder()
    .AddBackOffice()
    .AddWebsite()
    .AddDeliveryApi()
    .AddComposers()
    .AddGoogleTagManager(options =>
    {
        options.ContainerId = builder.Configuration["Analytics:GoogleTagManager:ContainerId"];
        options.InitializeDataLayer = true;
        options.IncludeNoScript = true;
    })
    .Build();

var app = builder.Build();

// ... rest of configuration

Configure in Backoffice

  1. Log in to Umbraco Backoffice
  2. Navigate to Settings → Google Tag Manager
  3. Enter Container ID: GTM-XXXXXXX
  4. Enable Auto-Initialize Data Layer
  5. Click Save

Method 3: IIS Application-Level Integration

Create GTM HTTP Module

GoogleTagManagerModule.cs:

using System;
using System.Web;
using System.Text;

namespace YourProject.Modules
{
    public class GoogleTagManagerModule : IHttpModule
    {
        private const string ContainerId = "GTM-XXXXXXX";

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

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

            if (context.Response.ContentType == "text/html")
            {
                context.Response.Filter = new GoogleTagManagerFilter(
                    context.Response.Filter,
                    ContainerId
                );
            }
        }

        public void Dispose() { }
    }

    public class GoogleTagManagerFilter : System.IO.Stream
    {
        private System.IO.Stream _responseStream;
        private string _containerId;

        public GoogleTagManagerFilter(System.IO.Stream stream, string containerId)
        {
            _responseStream = stream;
            _containerId = containerId;
        }

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

            // GTM head script
            string gtmHead = $@"
                <script>
                    (function(w,d,s,l,i){{w[l]=w[l]||[];w[l].push({{'gtm.start':
                    new Date().getTime(),event:'gtm.js'}});var f=d.getElementsByTagName(s)[0],
                    j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
                    'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
                    }})(window,document,'script','dataLayer','{_containerId}');
                </script>
            ";

            // GTM noscript
            string gtmNoScript = $@"
                <noscript>
                    <iframe src='https://www.googletagmanager.com/ns.html?id={_containerId}'
                            height='0' width='0' style='display:none;visibility:hidden'>
                    </iframe>
                </noscript>
            ";

            html = html.Replace("</head>", gtmHead + "</head>");
            html = html.Replace("<body>", "<body>" + gtmNoScript);

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

        // 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);
    }
}

Register in Web.config

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

Data Layer Integration

Initialize Data Layer with Umbraco Context

Enhanced GoogleTagManager.cshtml:

@using Umbraco.Cms.Core.Models.PublishedContent
@using Umbraco.Cms.Core.Security
@inject IMemberManager MemberManager
@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage

@{
    var gtmId = "GTM-XXXXXXX";
    var member = await MemberManager.GetCurrentMemberAsync();
}

<!-- Google Tag Manager -->
<script>
    // Initialize Data Layer with Umbraco context
    window.dataLayer = window.dataLayer || [];
    window.dataLayer.push({
        'pageType': '@Model?.ContentType.Alias',
        'pageName': '@Model?.Name',
        'pageId': '@Model?.Id',
        'pageTemplate': '@ViewData["TemplateAlias"]',
        'pageUrl': '@Context.Request.Path',
        'contentCulture': '@Model?.GetCultureFromDomains()?.Culture',
        @if (member != null)
        {
            <text>
            'userStatus': 'logged_in',
            'memberType': '@member.ContentType.Alias',
            </text>
        }
        else
        {
            <text>
            'userStatus': 'visitor',
            </text>
        }
        'environment': '@HostEnvironment.EnvironmentName'
    });

    // GTM initialization
    (function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
    new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
    j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
    'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
    })(window,document,'script','dataLayer','@gtmId');
</script>
<!-- End Google Tag Manager -->

Push E-commerce Data to Data Layer

See GTM Data Layer Guide for detailed e-commerce tracking.

Environment-Specific GTM Containers

Configure Multiple Environments

appsettings.Development.json:

{
  "Analytics": {
    "GoogleTagManager": {
      "ContainerId": "GTM-DEV123",
      "Enabled": true
    }
  }
}

appsettings.Production.json:

{
  "Analytics": {
    "GoogleTagManager": {
      "ContainerId": "GTM-PROD456",
      "Enabled": true
    }
  }
}

In Razor view:

@inject IConfiguration Configuration

@{
    var gtmId = Configuration["Analytics:GoogleTagManager:ContainerId"];
}

The correct environment file is automatically loaded based on ASPNETCORE_ENVIRONMENT.

GTM Preview Mode Setup

Enable Preview Mode in Development

@inject Microsoft.Extensions.Hosting.IHostEnvironment HostEnvironment

@if (HostEnvironment.IsDevelopment() && !string.IsNullOrEmpty(Context.Request.Query["gtm_auth"]))
{
    var gtmAuth = Context.Request.Query["gtm_auth"];
    var gtmPreview = Context.Request.Query["gtm_preview"];
    var gtmCookies = Context.Request.Query["gtm_cookies_win"];

    <script>
        // GTM Preview Mode parameters
        (function(w,d,s,l,i){
            w[l]=w[l]||[];w[l].push({'gtm.start': new Date().getTime(),event:'gtm.js'});
            var f=d.getElementsByTagName(s)[0],
            j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';
            j.async=true;
            j.src='https://www.googletagmanager.com/gtm.js?id='+i+dl
                +'&gtm_auth=@gtmAuth'
                +'&gtm_preview=@gtmPreview'
                +'&gtm_cookies_win=@gtmCookies';
            f.parentNode.insertBefore(j,f);
        })(window,document,'script','dataLayer','@gtmId');
    </script>
}

Performance Optimization

Preconnect to GTM Domains

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

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

Defer GTM Loading

For non-critical tracking, defer GTM until after page load:

<script>
    window.addEventListener('load', function() {
        (function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
        new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
        j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
        'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
        })(window,document,'script','dataLayer','GTM-XXXXXXX');
    });
</script>

Note: This may affect tracking accuracy and is not recommended for critical analytics.

IIS-Specific Configuration

Enable Compression for GTM Scripts

Web.config:

<configuration>
  <system.webServer>
    <urlCompression doStaticCompression="true" doDynamicCompression="true" />
    <httpCompression>
      <dynamicTypes>
        <add mimeType="application/javascript" enabled="true" />
      </dynamicTypes>
    </httpCompression>
  </system.webServer>
</configuration>

Set Content Security Policy

Allow GTM domains in CSP:

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

Validation and Testing

Verify GTM Installation

  1. Open your Umbraco site
  2. Open DevTools → Console
  3. Type: dataLayer and press Enter
  4. Verify data layer exists and contains expected data
  5. Type: google_tag_manager and press Enter
  6. Verify GTM container ID is present

Use Tag Assistant

  1. Install Google Tag Assistant Chrome extension
  2. Click extension icon on your site
  3. Verify GTM container detected
  4. Check for errors or warnings
  5. Verify tags fire correctly

Preview Mode Testing

  1. Log in to Google Tag Manager
  2. Click Preview button
  3. Enter your Umbraco site URL
  4. GTM opens in preview mode
  5. Navigate site to test tag firing
  6. Verify variables populate correctly

Common Issues

GTM Not Loading

Cause: Incorrect Container ID or syntax error Solution:

  • Verify Container ID format: GTM-XXXXXXX
  • Check browser console for JavaScript errors
  • Ensure script in <head> section
  • Clear browser cache

Data Layer Not Initialized

Cause: Data layer push after GTM initialization Solution:

  • Initialize data layer BEFORE GTM script
  • Use window.dataLayer = window.dataLayer || []; before pushing

NoScript Tag Not Working

Cause: NoScript iframe not immediately after <body> Solution:

<body>
    @* NoScript must be first element in body *@
    @await Html.PartialAsync("~/Views/Partials/Analytics/GoogleTagManagerNoScript.cshtml")

    @* Rest of content *@
    @RenderBody()
</body>

Umbraco Cache Issues

Cause: Cached views not reflecting GTM changes Solution:

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

IIS Module Not Firing

Cause: Module not registered or pipeline mode issue Solution:

  • Verify Web.config registration
  • Check IIS application pool mode
  • Restart IIS: iisreset
<script>
    // Default consent state
    window.dataLayer = window.dataLayer || [];
    function gtag(){dataLayer.push(arguments);}

    gtag('consent', 'default', {
        'analytics_storage': 'denied',
        'ad_storage': 'denied',
        'wait_for_update': 500
    });

    // GTM initialization
    (function(w,d,s,l,i){/* GTM script */})(window,document,'script','dataLayer','GTM-XXXXXXX');

    // Update consent when user accepts
    function grantConsent() {
        gtag('consent', 'update', {
            'analytics_storage': 'granted',
            'ad_storage': 'granted'
        });
    }
</script>

Next Steps