Installing Google Tag Manager on Sitecore | Blue Frog Docs

Installing Google Tag Manager on Sitecore

Complete guide to implementing GTM on Sitecore XP, XM, SXA, and JSS using MVC, Razor, and headless approaches

Installing Google Tag Manager on Sitecore

General GTM Concepts: See Google Tag Manager documentation for universal GTM concepts and tag configuration.

This guide covers Sitecore-specific methods for implementing Google Tag Manager (GTM), from traditional MVC implementations to headless JSS setups, with support for multi-site configurations and Experience Editor compatibility.

Prerequisites

Before installing GTM on Sitecore:

  1. Create GTM Container

  2. Verify Sitecore Version

    • Sitecore XP/XM 10.x (recommended)
    • Sitecore 9.x (supported)
    • Sitecore JSS 21.x+ (for headless)
  3. Backend Access Requirements

    • Content Editor access
    • Development environment (Visual Studio)
    • Deploy permissions to Sitecore instance

Method 1: Layout File Integration (MVC)

Best for: Quick setup, standard Sitecore MVC sites

Basic Layout Implementation

Edit your main layout file:

@* /Views/Shared/_Layout.cshtml *@
<!DOCTYPE html>
<html lang="@Sitecore.Context.Language.Name">
<head>
    <meta charset="utf-8" />
    <title>@Html.Sitecore().Field("Title", new { DisableWebEdit = true })</title>

    @if (!Sitecore.Context.PageMode.IsExperienceEditorEditing)
    {
        <!-- 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','GTM-XXXXXXX');</script>
        <!-- End Google Tag Manager -->
    }

    @Html.Sitecore().Placeholder("head")
</head>
<body>
    @if (!Sitecore.Context.PageMode.IsExperienceEditorEditing)
    {
        <!-- Google Tag Manager (noscript) -->
        <noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-XXXXXXX"
        height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
        <!-- End Google Tag Manager (noscript) -->
    }

    @Html.Sitecore().Placeholder("main")
    @RenderBody()
</body>
</html>

Multi-Site Configuration

1. Add site configuration:

<!-- App_Config/Include/Sites/Sites.config -->
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <sites>
      <site name="website1"
            hostName="www.site1.com"
            database="web"
            gtmContainerId="GTM-XXXXXXX1" />

      <site name="website2"
            hostName="www.site2.com"
            database="web"
            gtmContainerId="GTM-XXXXXXX2" />
    </sites>
  </sitecore>
</configuration>

2. Access in Layout:

@{
    var gtmId = Sitecore.Context.Site.Properties["gtmContainerId"];
}

@if (!string.IsNullOrEmpty(gtmId) && !Sitecore.Context.PageMode.IsExperienceEditorEditing)
{
    <!-- 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 -->
}

Method 2: View Rendering Component

Best for: Reusable component, managed through Sitecore Content Editor

Step 1: Create Data Template

Template Structure:

/sitecore/templates/Feature/Analytics/GTM Settings
├── Fields
│   ├── Container ID (Single-Line Text)
│   ├── Enable GTM (Checkbox)
│   ├── Data Layer Name (Single-Line Text) - Default: "dataLayer"
│   └── Environment Variables (Multi-Line Text)

Step 2: Create Settings Item

Content Editor:

  1. Navigate to /sitecore/content/[Your Site]/Settings
  2. Insert new item from GTM Settings template
  3. Fill in Container ID: GTM-XXXXXXX
  4. Check Enable GTM
  5. Data Layer Name: dataLayer

Step 3: Create View

@* /Views/Analytics/GoogleTagManager.cshtml *@
@using Sitecore.Data.Items

@{
    var settingsPath = $"/sitecore/content/{Sitecore.Context.Site.Name}/Settings/GTM Settings";
    var settingsItem = Sitecore.Context.Database.GetItem(settingsPath);

    if (settingsItem != null && !Sitecore.Context.PageMode.IsExperienceEditorEditing)
    {
        var containerId = settingsItem["Container ID"];
        var enableGTM = settingsItem["Enable GTM"] == "1";
        var dataLayerName = settingsItem["Data Layer Name"];

        if (string.IsNullOrEmpty(dataLayerName))
        {
            dataLayerName = "dataLayer";
        }

        if (enableGTM && !string.IsNullOrEmpty(containerId))
        {
            @* Head Section *@
            <text>
            <!-- 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!='@dataLayerName'?'&l='+l:'';j.async=true;j.src=
            'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
            })(window,document,'script','@dataLayerName','@containerId');</script>
            <!-- End Google Tag Manager -->
            </text>
        }
    }
}

Step 4: Create Separate Noscript Component

@* /Views/Analytics/GoogleTagManagerNoScript.cshtml *@
@using Sitecore.Data.Items

@{
    var settingsPath = $"/sitecore/content/{Sitecore.Context.Site.Name}/Settings/GTM Settings";
    var settingsItem = Sitecore.Context.Database.GetItem(settingsPath);

    if (settingsItem != null && !Sitecore.Context.PageMode.IsExperienceEditorEditing)
    {
        var containerId = settingsItem["Container ID"];
        var enableGTM = settingsItem["Enable GTM"] == "1";

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

Step 5: Add Renderings to Presentation

Add both renderings to your layout's presentation details:

  • GTM Head in <head> placeholder
  • GTM NoScript at start of <body>

Method 3: Controller Rendering with Model

Best for: Complex logic, environment-specific configs

Create Model

// /Models/Analytics/GTMModel.cs
namespace YourProject.Models.Analytics
{
    public class GTMModel
    {
        public string ContainerId { get; set; }
        public bool Enabled { get; set; }
        public string DataLayerName { get; set; } = "dataLayer";
        public string EnvironmentAuth { get; set; }
        public string EnvironmentPreview { get; set; }
        public bool IsExperienceEditor { get; set; }

        public bool ShouldRender =>
            Enabled &&
            !string.IsNullOrEmpty(ContainerId) &&
            !IsExperienceEditor;

        public string GetGTMUrl()
        {
            var url = $"https://www.googletagmanager.com/gtm.js?id={ContainerId}";

            if (!string.IsNullOrEmpty(EnvironmentAuth) && !string.IsNullOrEmpty(EnvironmentPreview))
            {
                url += $"&gtm_auth={EnvironmentAuth}&gtm_preview={EnvironmentPreview}&gtm_cookies_win=x";
            }

            return url;
        }
    }
}

Create Controller

// /Controllers/AnalyticsController.cs
using System.Web.Mvc;
using Sitecore.Data.Items;
using YourProject.Models.Analytics;

namespace YourProject.Controllers
{
    public class AnalyticsController : Controller
    {
        public ActionResult GoogleTagManager()
        {
            var model = new GTMModel
            {
                IsExperienceEditor = Sitecore.Context.PageMode.IsExperienceEditorEditing
            };

            var settingsPath = $"/sitecore/content/{Sitecore.Context.Site.Name}/Settings/GTM Settings";
            var settingsItem = Sitecore.Context.Database.GetItem(settingsPath);

            if (settingsItem != null)
            {
                model.ContainerId = settingsItem["Container ID"];
                model.Enabled = settingsItem["Enable GTM"] == "1";
                model.DataLayerName = settingsItem["Data Layer Name"];
                model.EnvironmentAuth = settingsItem["Environment Auth"];
                model.EnvironmentPreview = settingsItem["Environment Preview"];

                if (string.IsNullOrEmpty(model.DataLayerName))
                {
                    model.DataLayerName = "dataLayer";
                }
            }

            return View("/Views/Analytics/GoogleTagManager.cshtml", model);
        }
    }
}

Create View

@* /Views/Analytics/GoogleTagManager.cshtml *@
@model YourProject.Models.Analytics.GTMModel

@if (Model.ShouldRender)
{
    <!-- 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!='@Model.DataLayerName'?'&l='+l:'';j.async=true;j.src=
    '@Html.Raw(Model.GetGTMUrl())';f.parentNode.insertBefore(j,f);
    })(window,document,'script','@Model.DataLayerName','@Model.ContainerId');</script>
    <!-- End Google Tag Manager -->
}

Method 4: Pipeline Processor (Site-Wide)

Best for: Enterprise implementations, centralized control

Create Pipeline Processor

// /Pipelines/HttpRequest/InjectGoogleTagManager.cs
using Sitecore.Pipelines.HttpRequest;
using System.Web;

namespace YourProject.Pipelines.HttpRequest
{
    public class InjectGoogleTagManager : HttpRequestProcessor
    {
        public override void Process(HttpRequestArgs args)
        {
            // Skip if in Experience Editor
            if (Sitecore.Context.PageMode.IsExperienceEditorEditing)
                return;

            // Skip if no context item
            if (Sitecore.Context.Item == null)
                return;

            // Get container ID from site properties
            var containerId = Sitecore.Context.Site.Properties["gtmContainerId"];

            if (string.IsNullOrEmpty(containerId))
                return;

            // Store for rendering in layout
            args.Context.Items["GTMContainerId"] = containerId;
            args.Context.Items["GTMEnabled"] = true;
        }
    }
}

Register Pipeline

<!-- App_Config/Include/Analytics/GTM.config -->
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <httpRequestBegin>
        <processor type="YourProject.Pipelines.HttpRequest.InjectGoogleTagManager, YourProject"
                   patch:after="processor[@type='Sitecore.Pipelines.HttpRequest.ItemResolver, Sitecore.Kernel']"/>
      </httpRequestBegin>
    </pipelines>
  </sitecore>
</configuration>

Access in Layout

@{
    var gtmEnabled = HttpContext.Current.Items["GTMEnabled"] as bool?;
    var gtmId = HttpContext.Current.Items["GTMContainerId"] as string;
}

@if (gtmEnabled == true && !string.IsNullOrEmpty(gtmId))
{
    <!-- GTM code using @gtmId -->
}

Method 5: SXA (Sitecore Experience Accelerator)

Best for: SXA-based sites

Extend SXA Site Settings

1. Add fields to SXA Site Settings:

# Extend template: /sitecore/templates/Foundation/Experience Accelerator/Sites/Site
Fields:
- ID: "gtm-container-id"
  Name: "GTM Container ID"
  Type: "Single-Line Text"
  Section: "Analytics"

- ID: "gtm-enabled"
  Name: "GTM Enabled"
  Type: "Checkbox"
  Section: "Analytics"

2. Create SXA Component:

Create a new SXA component or rendering variant:

@* SXA GTM Component *@
@using Sitecore.XA.Foundation.SitecoreExtensions.Extensions

@{
    var siteSettings = Html.Sxa().SiteSettings;
    var gtmId = siteSettings?.GetItem()["GTM Container ID"];
    var gtmEnabled = siteSettings?.GetItem()["GTM Enabled"] == "1";
}

@if (gtmEnabled && !string.IsNullOrEmpty(gtmId) && !Sitecore.Context.PageMode.IsExperienceEditorEditing)
{
    <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>
}

3. Add to SXA Partial Design:

Add the GTM component to your site's partial design for automatic inclusion on all pages.

Method 6: Headless/JSS Implementation

Best for: Sitecore JSS (React, Vue, Angular, Next.js)

Next.js Implementation

1. Create GTM Component:

// /src/components/Analytics/GoogleTagManager.tsx
import Script from 'next/script';
import { useSitecoreContext } from '@sitecore-jss/sitecore-jss-nextjs';

interface GTMProps {
  containerId: string;
  dataLayerName?: string;
}

const GoogleTagManager = ({ containerId, dataLayerName = 'dataLayer' }: GTMProps): JSX.Element | null => {
  const { sitecoreContext } = useSitecoreContext();

  // Don't render in Experience Editor
  if (sitecoreContext?.pageEditing) {
    return null;
  }

  if (!containerId) {
    return null;
  }

  return (
    <>
      {/* GTM Script */}
      <Script
        id="google-tag-manager"
        strategy="afterInteractive"
        dangerouslySetInnerHTML={{
          __html: `
            (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!='${dataLayerName}'?'&l='+l:'';j.async=true;j.src=
            'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
            })(window,document,'script','${dataLayerName}','${containerId}');
          `,
        }}
      />

      {/* GTM NoScript */}
      <noscript>
        <iframe
          src={`https://www.googletagmanager.com/ns.html?id=${containerId}`}
          height="0"
          width="0"
          style={{ display: 'none', visibility: 'hidden' }}
        />
      </noscript>
    </>
  );
};

export default GoogleTagManager;

2. Add to Layout:

// /src/Layout.tsx
import GoogleTagManager from './components/Analytics/GoogleTagManager';

const Layout = ({ layoutData }: LayoutProps): JSX.Element => {
  const GTM_ID = process.env.NEXT_PUBLIC_GTM_ID || '';

  return (
    <>
      <GoogleTagManager containerId={GTM_ID} />
      <div>
        {/* Your layout content */}
      </div>
    </>
  );
};

3. Environment Configuration:

# .env.local
NEXT_PUBLIC_GTM_ID=GTM-XXXXXXX

# .env.production
NEXT_PUBLIC_GTM_ID=GTM-PRODXXX

React JSS Implementation

// /src/components/Analytics/GoogleTagManager.js
import { useEffect } from 'react';
import { useSitecoreContext } from '@sitecore-jss/sitecore-jss-react';

const GoogleTagManager = ({ fields }) => {
  const { sitecoreContext } = useSitecoreContext();
  const containerId = fields?.containerId?.value || process.env.REACT_APP_GTM_ID;
  const dataLayerName = fields?.dataLayerName?.value || 'dataLayer';

  useEffect(() => {
    // Don't load in Experience Editor
    if (sitecoreContext.pageEditing || !containerId) {
      return;
    }

    // Inject GTM 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!=dataLayerName?'&l='+l:'';j.async=true;j.src=
    'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
    })(window,document,'script',dataLayerName,containerId);

  }, [containerId, dataLayerName, sitecoreContext.pageEditing]);

  // Render noscript
  if (sitecoreContext.pageEditing || !containerId) {
    return null;
  }

  return (
    <noscript>
      <iframe
        src={`https://www.googletagmanager.com/ns.html?id=${containerId}`}
        height="0"
        width="0"
        style={{ display: 'none', visibility: 'hidden' }}
      />
    </noscript>
  );
};

export default GoogleTagManager;

Sitecore Personalization Integration

Tracking Personalization Rules

Capture which personalization rules are active on the current page:

@using Sitecore.Analytics
@using Sitecore.Analytics.Model
@using Sitecore.Mvc.Analytics.Extensions

@{
    var personalizedComponents = new List<object>();
    var isPersonalized = Html.IsPersonalized();

    if (Tracker.Current != null && Tracker.Current.IsActive)
    {
        var interaction = Tracker.Current.Interaction;
        // Track personalization data
    }
}

<script>
    window.dataLayer = window.dataLayer || [];
    dataLayer.push({
        'event': 'sitecore_personalization',
        'personalization': {
            'is_personalized': @isPersonalized.ToString().ToLower(),
            'components': @Html.Raw(Newtonsoft.Json.JsonConvert.SerializeObject(personalizedComponents))
        }
    });
</script>

A/B Testing Integration

Track Sitecore content testing (A/B tests):

@using Sitecore.Analytics.Testing
@using Sitecore.ContentTesting

@{
    var testingData = new Dictionary<string, object>();

    // Check if current page is part of a test
    if (Tracker.Current != null && Tracker.Current.IsActive)
    {
        var currentItem = Sitecore.Context.Item;
        var testingProvider = TestingProvider.Current;

        if (testingProvider != null)
        {
            var activeTests = testingProvider.GetActiveTests(currentItem);

            foreach (var test in activeTests)
            {
                testingData.Add(test.TestDefinitionId.ToString(), new
                {
                    testId = test.TestDefinitionId.ToString(),
                    testName = test.TestDefinitionName,
                    variationId = test.VariationId?.ToString(),
                    variationName = test.VariationId != null ? currentItem.Database.GetItem(test.VariationId)?.Name : "Original"
                });
            }
        }
    }
}

<script>
    @if (testingData.Any())
    {
        <text>
        dataLayer.push({
            'event': 'sitecore_ab_test',
            'experiment': {
                'id': '@testingData.First().Value.testId',
                'name': '@testingData.First().Value.testName',
                'variant': '@testingData.First().Value.variationName'
            }
        });
        </text>
    }
</script>

Profile Card Tracking

Track visitor profile cards and pattern matches:

@using Sitecore.Analytics
@using Sitecore.Analytics.Tracking

@{
    var profileData = new Dictionary<string, object>();

    if (Tracker.Current != null && Tracker.Current.IsActive)
    {
        var contact = Tracker.Current.Contact;
        if (contact != null)
        {
            // Get behavior profile data
            var behaviorProfiles = contact.GetBehaviorProfiles();

            foreach (var profile in behaviorProfiles.Profiles)
            {
                profileData[profile.ProfileName] = new
                {
                    profileName = profile.ProfileName,
                    patternMatch = profile.PatternLabel,
                    totalScore = profile.Total,
                    values = profile.Values.Select(v => new { key = v.Key, value = v.Value })
                };
            }
        }
    }
}

<script>
    @if (profileData.Any())
    {
        <text>
        dataLayer.push({
            'event': 'sitecore_profile_data',
            'visitor_profile': @Html.Raw(Newtonsoft.Json.JsonConvert.SerializeObject(profileData))
        });
        </text>
    }
</script>

Goal Tracking

Automatically push Sitecore goals to GTM:

@using Sitecore.Analytics
@using Sitecore.Analytics.Model

@{
    var triggeredGoals = new List<object>();

    if (Tracker.Current != null && Tracker.Current.IsActive)
    {
        var currentPage = Tracker.Current.Interaction.CurrentPage;

        if (currentPage != null)
        {
            var goals = currentPage.PageEvents.Where(e => e.IsGoal);

            foreach (var goal in goals)
            {
                triggeredGoals.Add(new
                {
                    goalId = goal.PageEventDefinitionId,
                    goalName = goal.Name,
                    goalValue = goal.Value,
                    timestamp = goal.DateTime
                });
            }
        }
    }
}

<script>
    @foreach (var goal in triggeredGoals)
    {
        <text>
        dataLayer.push({
            'event': 'sitecore_goal',
            'goal': {
                'id': '@goal.goalId',
                'name': '@goal.goalName',
                'value': @goal.goalValue
            }
        });
        </text>
    }
</script>

Campaign Tracking

Track Sitecore campaign information:

@using Sitecore.Analytics

@{
    var campaignInfo = new Dictionary<string, string>();

    if (Tracker.Current != null && Tracker.Current.IsActive)
    {
        var interaction = Tracker.Current.Interaction;
        var campaignId = interaction?.CampaignId;

        if (campaignId != null && campaignId != Guid.Empty)
        {
            var campaign = Sitecore.Context.Database.GetItem(new Sitecore.Data.ID(campaignId));

            if (campaign != null)
            {
                campaignInfo["id"] = campaignId.ToString();
                campaignInfo["name"] = campaign.Name;
                campaignInfo["group"] = campaign.Parent?.Name ?? "";
            }
        }
    }
}

<script>
    @if (campaignInfo.Any())
    {
        <text>
        dataLayer.push({
            'event': 'sitecore_campaign',
            'campaign': {
                'id': '@campaignInfo["id"]',
                'name': '@campaignInfo["name"]',
                'group': '@campaignInfo["group"]'
            }
        });
        </text>
    }
</script>

Data Layer Setup for Sitecore Content

Page Context Data Layer

Push comprehensive Sitecore page context to GTM:

@using Sitecore.Data.Fields
@using Sitecore.Data.Items

@{
    var currentItem = Sitecore.Context.Item;
    var site = Sitecore.Context.Site;
}

<script>
    window.dataLayer = window.dataLayer || [];
    dataLayer.push({
        'event': 'page_data',
        'page': {
            // Item information
            'item_id': '@currentItem.ID',
            'item_name': '@currentItem.Name',
            'item_path': '@currentItem.Paths.FullPath',
            'display_name': '@currentItem.DisplayName',

            // Template information
            'template_id': '@currentItem.TemplateID',
            'template_name': '@currentItem.TemplateName',

            // Version and language
            'language': '@Sitecore.Context.Language.Name',
            'version': '@currentItem.Version.Number',

            // Site context
            'site_name': '@site.Name',
            'hostname': '@site.HostName',
            'database': '@Sitecore.Context.Database.Name',

            // Page metadata
            'page_title': '@Html.Raw(currentItem["Title"] ?? currentItem.DisplayName)',
            'page_type': '@currentItem.TemplateName',

            // Publishing info
            'created_date': '@currentItem.Statistics.Created.ToString("yyyy-MM-dd")',
            'updated_date': '@currentItem.Statistics.Updated.ToString("yyyy-MM-dd")',
            'created_by': '@currentItem.Statistics.CreatedBy',
            'updated_by': '@currentItem.Statistics.UpdatedBy'
        }
    });
</script>

User Engagement Data Layer

Track user interaction depth:

@using Sitecore.Analytics

@{
    var engagementValue = 0;
    var visitNumber = 0;
    var pageViews = 0;

    if (Tracker.Current != null && Tracker.Current.IsActive)
    {
        var interaction = Tracker.Current.Interaction;
        var contact = Tracker.Current.Contact;

        if (interaction != null)
        {
            engagementValue = interaction.Value;
            pageViews = interaction.GetPages().Count();
        }

        if (contact != null)
        {
            visitNumber = contact.System.VisitCount;
        }
    }
}

<script>
    dataLayer.push({
        'event': 'user_engagement',
        'engagement': {
            'value': @engagementValue,
            'visit_number': @visitNumber,
            'page_views_session': @pageViews,
            'is_returning_visitor': @(visitNumber > 1).ToString().ToLower()
        }
    });
</script>

Multi-Language Site Data Layer

For multi-language Sitecore sites:

@{
    var currentLanguage = Sitecore.Context.Language;
    var availableLanguages = Sitecore.Context.Database.GetItem(Sitecore.Context.Item.ID)
        ?.Languages
        .Select(l => l.Name)
        .ToList() ?? new List<string>();
}

<script>
    dataLayer.push({
        'event': 'language_context',
        'language': {
            'current': '@currentLanguage.Name',
            'iso_code': '@currentLanguage.CultureInfo.TwoLetterISOLanguageName',
            'culture': '@currentLanguage.CultureInfo.Name',
            'display_name': '@currentLanguage.CultureInfo.DisplayName',
            'available_languages': @Html.Raw(Newtonsoft.Json.JsonConvert.SerializeObject(availableLanguages))
        }
    });
</script>

Sitecore Commerce Data Layer

For Sitecore Commerce implementations:

@using Sitecore.Commerce.Entities.Carts
@using Sitecore.Commerce.Services.Carts

@{
    // Get current cart
    var cartService = new CartServiceProvider();
    var cart = cartService.GetCurrentCart();
}

<script>
    @if (cart != null && cart.Lines.Any())
    {
        <text>
        dataLayer.push({
            'event': 'ecommerce_context',
            'ecommerce': {
                'currency': '@cart.CurrencyCode',
                'value': @cart.Lines.Sum(l => l.Total.Amount),
                'items': [
                    @foreach (var line in cart.Lines)
                    {
                        <text>{
                            'item_id': '@line.Product.ProductId',
                            'item_name': '@line.Product.DisplayName',
                            'item_category': '@line.Product.ProductCatalog',
                            'price': @line.Product.ListPrice,
                            'quantity': @line.Quantity
                        },</text>
                    }
                ]
            }
        });
        </text>
    }
</script>

GTM Environment Configuration

For staging and production environments:

1. Configure environments in GTM:

  • Create environment in GTM
  • Get auth and preview tokens

2. Use in Sitecore:

@{
    var gtmId = "GTM-XXXXXXX";
    var gtmAuth = Sitecore.Configuration.Settings.GetSetting("GTM.Auth");
    var gtmPreview = Sitecore.Configuration.Settings.GetSetting("GTM.Preview");

    var gtmUrl = $"https://www.googletagmanager.com/gtm.js?id={gtmId}";

    if (!string.IsNullOrEmpty(gtmAuth) && !string.IsNullOrEmpty(gtmPreview))
    {
        gtmUrl += $"&gtm_auth={gtmAuth}&gtm_preview={gtmPreview}&gtm_cookies_win=x";
    }
}

<script>
    // Use @gtmUrl instead of standard URL
</script>

3. Configure in web.config:

<!-- Web.Release.config -->
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
  <appSettings>
    <add key="GTM.ContainerId" value="GTM-PRODXXX" xdt:Transform="SetAttributes" xdt:Locator="Match(key)"/>
    <add key="GTM.Auth" value="" xdt:Transform="SetAttributes" xdt:Locator="Match(key)"/>
    <add key="GTM.Preview" value="" xdt:Transform="SetAttributes" xdt:Locator="Match(key)"/>
  </appSettings>
</configuration>

<!-- Web.Staging.config -->
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
  <appSettings>
    <add key="GTM.ContainerId" value="GTM-STGXXX" xdt:Transform="SetAttributes" xdt:Locator="Match(key)"/>
    <add key="GTM.Auth" value="your-auth-token" xdt:Transform="SetAttributes" xdt:Locator="Match(key)"/>
    <add key="GTM.Preview" value="env-123" xdt:Transform="SetAttributes" xdt:Locator="Match(key)"/>
  </appSettings>
</configuration>

Server-Side GTM (sGTM)

For server-side tagging:

@{
    var serverContainerUrl = "https://your-gtm-server.com";
    var containerId = "GTM-XXXXXXX";
}

<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=
'@serverContainerUrl/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','@containerId');</script>

Implement consent before loading GTM:

<script>
    // Check consent before loading GTM
    function loadGTM() {
        if (localStorage.getItem('cookieConsent') === 'true') {
            (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');
        }
    }

    // Load immediately if consent given
    if (localStorage.getItem('cookieConsent') === 'true') {
        loadGTM();
    }

    // Listen for consent events
    document.addEventListener('cookieConsentGranted', loadGTM);
</script>

Validation and Testing

1. GTM Preview Mode

  1. In GTM, click Preview
  2. Enter your Sitecore site URL
  3. Navigate site to test tags
  4. Verify tags fire correctly

2. Check Experience Editor

Verify GTM doesn't load in Experience Editor:

  • Open page in Experience Editor
  • Check browser console - GTM should not load
  • Verify no errors

3. Browser DevTools

// Check if dataLayer exists (browser console)
console.log(window.dataLayer);

// Monitor dataLayer pushes
const originalPush = window.dataLayer.push;
window.dataLayer.push = function() {
    console.log('dataLayer.push:', arguments);
    return originalPush.apply(this, arguments);
};

4. Google Tag Assistant

Use Tag Assistant:

  • Connect to your Sitecore site
  • Verify GTM container loads
  • Check for configuration errors

Common Sitecore-Specific Issues

Issue: GTM Loads in Experience Editor

Cause: Missing Experience Editor check

Solution:

@if (!Sitecore.Context.PageMode.IsExperienceEditorEditing)
{
    @* GTM code *@
}

Issue: Multi-Site Wrong Container

Cause: Shared GTM configuration

Solution: Use site-specific settings (see Multi-Site Configuration above)

Issue: dataLayer Not Defined

Cause: GTM script loaded too late

Solution: Ensure GTM is in <head> before other scripts that use dataLayer

Issue: Noscript Tag Not Rendering

Cause: Missing body rendering

Solution: Place noscript immediately after opening <body> tag

Performance Optimization

Preconnect to GTM Domains

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

Delayed Loading

// Load GTM after user interaction
let gtmLoaded = false;
const loadGTM = () => {
    if (gtmLoaded) return;
    gtmLoaded = true;

    (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');
};

['mousedown', 'touchstart', 'scroll', 'keydown'].forEach(event => {
    window.addEventListener(event, loadGTM, {once: true, passive: true});
});

// Fallback after 3 seconds
setTimeout(loadGTM, 3000);

Next Steps

// SYS.FOOTER