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
Method 1: Razor View Integration (Recommended)
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
- Navigate to Settings → Document Types
- Select Site Settings document type
- Add property:
- Name: GTM Container ID
- Alias: gtmContainerId
- Editor: Textstring
- Save document type
- Navigate to Content → Site Settings
- Enter GTM Container ID:
GTM-XXXXXXX - Save and publish
Step 5: Verify Installation
- Build and run Umbraco site
- Open site in browser
- Open DevTools → Console
- Type:
dataLayerand press Enter - Verify array exists with GTM initialization
- Install Google Tag Assistant Chrome extension
- 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
- Log in to Umbraco Backoffice
- Navigate to Settings → Google Tag Manager
- Enter Container ID:
GTM-XXXXXXX - Enable Auto-Initialize Data Layer
- 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
+'>m_auth=@gtmAuth'
+'>m_preview=@gtmPreview'
+'>m_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
- Open your Umbraco site
- Open DevTools → Console
- Type:
dataLayerand press Enter - Verify data layer exists and contains expected data
- Type:
google_tag_managerand press Enter - Verify GTM container ID is present
Use Tag Assistant
- Install Google Tag Assistant Chrome extension
- Click extension icon on your site
- Verify GTM container detected
- Check for errors or warnings
- Verify tags fire correctly
Preview Mode Testing
- Log in to Google Tag Manager
- Click Preview button
- Enter your Umbraco site URL
- GTM opens in preview mode
- Navigate site to test tag firing
- 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
GDPR and Consent Management
Implement Consent Mode
<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
- GTM Data Layer Configuration - Configure advanced data layer
- Google Analytics Setup - Configure GA4 via GTM
- Troubleshooting Tracking Issues - Debug GTM problems