GTM Data Layer Implementation on Sitecore
Learn how to implement a robust Google Tag Manager data layer on Sitecore that captures page information, user data, Sitecore context, and dynamic content for powerful tag management.
Understanding the Data Layer
The data layer is a JavaScript object that holds information about the page, user, and interactions. GTM uses this data to fire tags conditionally and pass variables.
Basic Structure:
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
'event': 'pageview',
'pageName': 'Home',
'pageType': 'Landing Page'
});
Prerequisites
- GTM installed on Sitecore
- Understanding of Sitecore item structure
- Access to modify Razor views and C# code
Basic Page Data Layer
Initialize Data Layer Before GTM
Always initialize the data layer before GTM loads:
@* /Views/Shared/_Layout.cshtml *@
<!DOCTYPE html>
<html>
<head>
@* Initialize data layer FIRST *@
<script>
window.dataLayer = window.dataLayer || [];
</script>
@* Then load GTM *@
<script>(function(w,d,s,l,i){...})(window,document,'script','dataLayer','GTM-XXXXXXX');</script>
</head>
Sitecore Page Context Data Layer
Capture core Sitecore page information:
@using Sitecore.Data.Items
@using Sitecore.Links
@{
var currentItem = Sitecore.Context.Item;
var language = Sitecore.Context.Language.Name;
var siteName = Sitecore.Context.Site.Name;
}
<script>
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
'event': 'pageview',
'page': {
'id': '@currentItem.ID.ToString()',
'name': '@currentItem.Name',
'displayName': '@currentItem.DisplayName',
'path': '@currentItem.Paths.FullPath',
'url': '@LinkManager.GetItemUrl(currentItem)',
'template': {
'id': '@currentItem.TemplateID.ToString()',
'name': '@currentItem.TemplateName'
},
'language': '@language',
'version': '@currentItem.Version.Number'
},
'site': {
'name': '@siteName',
'language': '@language'
}
});
</script>
Helper Class for Data Layer
Create a reusable helper for consistent data layer implementation:
// /Helpers/DataLayerHelper.cs
using Newtonsoft.Json;
using Sitecore.Data.Items;
using Sitecore.Links;
using System.Collections.Generic;
namespace YourProject.Helpers
{
public static class DataLayerHelper
{
public static string GetPageDataJson()
{
var currentItem = Sitecore.Context.Item;
if (currentItem == null)
return "{}";
var pageData = new
{
page = new
{
id = currentItem.ID.ToString(),
name = currentItem.Name,
displayName = currentItem.DisplayName,
path = currentItem.Paths.FullPath,
url = LinkManager.GetItemUrl(currentItem),
template = new
{
id = currentItem.TemplateID.ToString(),
name = currentItem.TemplateName
},
language = Sitecore.Context.Language.Name,
version = currentItem.Version.Number,
created = currentItem.Statistics.Created.ToString("yyyy-MM-dd"),
updated = currentItem.Statistics.Updated.ToString("yyyy-MM-dd")
},
site = new
{
name = Sitecore.Context.Site?.Name,
language = Sitecore.Context.Language.Name,
country = Sitecore.Context.Language.CultureInfo.TwoLetterISOLanguageName
}
};
return JsonConvert.SerializeObject(pageData);
}
public static string GetUserDataJson()
{
var userData = new Dictionary<string, object>
{
["isAuthenticated"] = Sitecore.Context.User?.IsAuthenticated ?? false,
["userName"] = Sitecore.Context.User?.IsAuthenticated == true ?
Sitecore.Context.User.LocalName : "anonymous"
};
// Add xDB data if available
if (Sitecore.Analytics.Tracker.Current != null &&
Sitecore.Analytics.Tracker.Current.IsActive)
{
var contact = Sitecore.Analytics.Tracker.Current.Contact;
if (contact != null)
{
userData["contactId"] = contact.ContactId.ToString("N");
userData["isNewVisitor"] = contact.System.VisitCount == 0;
userData["visitNumber"] = contact.System.VisitCount;
}
}
return JsonConvert.SerializeObject(userData);
}
public static string GetCustomFieldsJson(Item item, params string[] fieldNames)
{
var fields = new Dictionary<string, string>();
foreach (var fieldName in fieldNames)
{
var field = item.Fields[fieldName];
if (field != null && !string.IsNullOrEmpty(field.Value))
{
fields[fieldName] = field.Value;
}
}
return JsonConvert.SerializeObject(fields);
}
}
}
Use in Razor:
@using YourProject.Helpers
<script>
window.dataLayer = window.dataLayer || [];
window.dataLayer.push(@Html.Raw(DataLayerHelper.GetPageDataJson()));
</script>
User and Authentication Data
Track user status and authentication:
@using Sitecore.Security.Accounts
@{
var user = Sitecore.Context.User;
var isAuthenticated = user?.IsAuthenticated ?? false;
var userName = isAuthenticated ? user.LocalName : "anonymous";
var userRoles = isAuthenticated ? string.Join(",", user.Roles.Select(r => r.Name)) : "";
}
<script>
window.dataLayer.push({
'user': {
'isAuthenticated': @isAuthenticated.ToString().ToLower(),
'userName': '@userName',
'userRoles': '@userRoles',
'userId': '@(isAuthenticated ? user.Profile.Email : "")'
}
});
</script>
Integration with Sitecore xDB
Capture Sitecore Analytics data:
@using Sitecore.Analytics
@{
string contactId = null;
int visitNumber = 0;
bool isNewVisitor = true;
string engagementValue = "0";
if (Tracker.Current != null && Tracker.Current.IsActive)
{
var contact = Tracker.Current.Contact;
if (contact != null)
{
contactId = contact.ContactId.ToString("N");
visitNumber = contact.System.VisitCount;
isNewVisitor = visitNumber == 0;
}
var interaction = Tracker.Current.Session?.Interaction;
if (interaction != null)
{
engagementValue = interaction.Value.ToString();
}
}
}
<script>
window.dataLayer.push({
'sitecore': {
'contactId': '@contactId',
'visitNumber': @visitNumber,
'isNewVisitor': @isNewVisitor.ToString().ToLower(),
'engagementValue': @engagementValue
}
});
</script>
Page Category and Taxonomy
Organize pages by category and taxonomy:
@{
var currentItem = Sitecore.Context.Item;
// Get category from field or parent structure
var category = currentItem["Category"];
var subcategory = currentItem["Subcategory"];
// Determine page type from template
var pageType = GetPageType(currentItem.TemplateName);
// Build breadcrumb from path
var breadcrumb = GetBreadcrumb(currentItem);
}
<script>
window.dataLayer.push({
'page': {
'category': '@category',
'subcategory': '@subcategory',
'type': '@pageType',
'breadcrumb': '@breadcrumb'
}
});
</script>
@functions {
string GetPageType(string templateName)
{
// Map template names to page types
var mapping = new Dictionary<string, string>
{
{ "Article Page", "article" },
{ "Landing Page", "landing" },
{ "Product Page", "product" },
{ "Category Page", "category" }
};
return mapping.ContainsKey(templateName) ? mapping[templateName] : "standard";
}
string GetBreadcrumb(Item item)
{
var ancestors = item.Axes.GetAncestors()
.Where(i => i.Paths.IsContentItem)
.Select(i => i.DisplayName);
return string.Join(" > ", ancestors.Concat(new[] { item.DisplayName }));
}
}
E-commerce Data Layer
For Sitecore Commerce implementations:
@using Sitecore.Commerce.Engine.Connect.Entities
@{
var cart = GetCurrentCart(); // Your method to get cart
}
@if (cart != null && cart.Lines.Any())
{
<script>
window.dataLayer.push({
'ecommerce': {
'cart': {
'total': @cart.Total.Amount,
'currency': '@cart.Total.CurrencyCode',
'itemCount': @cart.Lines.Count,
'items': [
@foreach (var line in cart.Lines)
{
<text>
{
'item_id': '@line.Product.ProductId',
'item_name': '@line.Product.DisplayName',
'price': @line.Product.Price.Amount,
'quantity': @line.Quantity
},
</text>
}
]
}
}
});
</script>
}
Dynamic Content Tracking
Track personalization and dynamic content:
@using Sitecore.Mvc.Analytics.Extensions
@{
var personalizationData = Html.Sitecore().PersonalizationData();
var isPersonalized = personalizationData != null;
var ruleName = personalizationData?.RuleName ?? "default";
}
<script>
window.dataLayer.push({
'personalization': {
'isActive': @isPersonalized.ToString().ToLower(),
'ruleName': '@ruleName',
'componentName': '@Model.RenderingItem?.Name'
}
});
</script>
Campaign and Source Tracking
Track campaign parameters and traffic sources:
@{
var utmSource = Request.QueryString["utm_source"];
var utmMedium = Request.QueryString["utm_medium"];
var utmCampaign = Request.QueryString["utm_campaign"];
var utmContent = Request.QueryString["utm_content"];
var utmTerm = Request.QueryString["utm_term"];
}
@if (!string.IsNullOrEmpty(utmSource))
{
<script>
window.dataLayer.push({
'campaign': {
'source': '@utmSource',
'medium': '@utmMedium',
'name': '@utmCampaign',
'content': '@utmContent',
'term': '@utmTerm'
}
});
</script>
}
Form Tracking Data Layer
Track Sitecore Forms with data layer:
// /scripts/forms/form-tracking.js
document.addEventListener('DOMContentLoaded', function() {
var sitecoreForms = document.querySelectorAll('form[data-sc-fxb-form]');
sitecoreForms.forEach(function(form) {
var formName = form.getAttribute('data-sc-fxb-form') || 'Unknown Form';
var formId = form.id;
// Track form view
window.dataLayer.push({
'event': 'formView',
'form': {
'name': formName,
'id': formId,
'location': window.location.pathname
}
});
// Track form start
var formStarted = false;
form.querySelectorAll('input, textarea, select').forEach(function(input) {
input.addEventListener('focus', function() {
if (!formStarted) {
formStarted = true;
window.dataLayer.push({
'event': 'formStart',
'form': {
'name': formName,
'id': formId
}
});
}
}, { once: true });
});
// Track form submission
form.addEventListener('submit', function() {
window.dataLayer.push({
'event': 'formSubmit',
'form': {
'name': formName,
'id': formId,
'location': window.location.pathname
}
});
});
});
});
Error Tracking Data Layer
Track errors and exceptions:
@if (ViewBag.Error != null)
{
<script>
window.dataLayer.push({
'event': 'error',
'error': {
'type': '@ViewBag.Error.Type',
'message': '@ViewBag.Error.Message',
'page': window.location.pathname
}
});
</script>
}
JavaScript error tracking:
// /scripts/analytics/error-tracking.js
window.addEventListener('error', function(e) {
window.dataLayer.push({
'event': 'javascriptError',
'error': {
'message': e.message,
'filename': e.filename,
'lineno': e.lineno,
'colno': e.colno,
'stack': e.error ? e.error.stack : ''
}
});
});
Search Results Data Layer
Track search interactions:
@* Search results page *@
@{
var searchTerm = Request.QueryString["q"];
var resultsCount = Model.SearchResults?.Count ?? 0;
}
@if (!string.IsNullOrEmpty(searchTerm))
{
<script>
window.dataLayer.push({
'event': 'search',
'search': {
'term': '@searchTerm',
'resultsCount': @resultsCount,
'hasResults': @(resultsCount > 0 ? "true" : "false")
}
});
</script>
}
Video Tracking Data Layer
Track video interactions:
// /scripts/analytics/video-tracking.js
document.querySelectorAll('video').forEach(function(video) {
var videoName = video.getAttribute('data-video-name') || video.src;
var tracked = {
play: false,
progress: {
'25': false,
'50': false,
'75': false,
'100': false
}
};
video.addEventListener('play', function() {
if (!tracked.play) {
tracked.play = true;
window.dataLayer.push({
'event': 'videoPlay',
'video': {
'name': videoName,
'duration': video.duration,
'currentTime': video.currentTime
}
});
}
});
video.addEventListener('timeupdate', function() {
var percentComplete = Math.floor((video.currentTime / video.duration) * 100);
['25', '50', '75', '100'].forEach(function(milestone) {
if (percentComplete >= parseInt(milestone) && !tracked.progress[milestone]) {
tracked.progress[milestone] = true;
window.dataLayer.push({
'event': 'videoProgress',
'video': {
'name': videoName,
'percent': milestone
}
});
}
});
});
});
SXA-Specific Data Layer
For SXA sites, capture component data:
@using Sitecore.XA.Foundation.Mvc.Extensions
@{
var pageDesign = Html.Sxa().PageDesign();
var siteSettings = Html.Sxa().SiteSettings;
var theme = Html.Sxa().Theme();
}
<script>
window.dataLayer.push({
'sxa': {
'theme': '@theme?.Name',
'pageDesign': '@pageDesign?.Name',
'siteName': '@siteSettings?.GetItem()?.Name'
}
});
</script>
JSS/Headless Data Layer
For Sitecore JSS applications:
// /src/lib/dataLayer.ts
import { RouteData } from '@sitecore-jss/sitecore-jss-nextjs';
export const pushPageView = (routeData: RouteData) => {
if (typeof window !== 'undefined' && window.dataLayer) {
window.dataLayer.push({
event: 'pageview',
page: {
id: routeData.itemId,
name: routeData.name,
path: routeData.itemPath,
template: routeData.templateName,
language: routeData.itemLanguage,
},
});
}
};
export const pushEvent = (eventName: string, data?: Record<string, any>) => {
if (typeof window !== 'undefined' && window.dataLayer) {
window.dataLayer.push({
event: eventName,
...data,
});
}
};
Use in Next.js page:
// /src/pages/[[...path]].tsx
import { pushPageView } from '@/lib/dataLayer';
const SitecorePage = ({ layoutData }: SitecorePageProps): JSX.Element => {
useEffect(() => {
if (layoutData?.sitecore?.route) {
pushPageView(layoutData.sitecore.route);
}
}, [layoutData]);
return <Layout layoutData={layoutData} />;
};
Multi-Language Data Layer
Track language-specific data:
@{
var language = Sitecore.Context.Language;
var languageName = language.Name;
var languageCode = language.CultureInfo.TwoLetterISOLanguageName;
var languageDirection = language.CultureInfo.TextInfo.IsRightToLeft ? "rtl" : "ltr";
}
<script>
window.dataLayer.push({
'language': {
'code': '@languageCode',
'name': '@languageName',
'direction': '@languageDirection',
'displayName': '@language.CultureInfo.DisplayName'
}
});
</script>
Performance Metrics Data Layer
Track Core Web Vitals:
// /scripts/analytics/performance-tracking.js
// Track LCP
new PerformanceObserver((list) => {
const entries = list.getEntries();
const lastEntry = entries[entries.length - 1];
window.dataLayer.push({
'event': 'webVitals',
'metric': {
'name': 'LCP',
'value': Math.round(lastEntry.renderTime || lastEntry.loadTime),
'rating': lastEntry.renderTime < 2500 ? 'good' : 'poor'
}
});
}).observe({ type: 'largest-contentful-paint', buffered: true });
// Track CLS
new PerformanceObserver((list) => {
let clsValue = 0;
list.getEntries().forEach((entry) => {
if (!entry.hadRecentInput) {
clsValue += entry.value;
}
});
window.dataLayer.push({
'event': 'webVitals',
'metric': {
'name': 'CLS',
'value': clsValue,
'rating': clsValue < 0.1 ? 'good' : 'poor'
}
});
}).observe({ type: 'layout-shift', buffered: true });
Data Layer Debugging
Debug in Browser Console
// Log all data layer pushes
var originalPush = window.dataLayer.push;
window.dataLayer.push = function() {
console.log('DataLayer Push:', arguments);
return originalPush.apply(this, arguments);
};
// View current dataLayer state
console.table(window.dataLayer);
Sitecore Debugging
// Log data layer to Sitecore log
using Sitecore.Diagnostics;
var dataLayerJson = DataLayerHelper.GetPageDataJson();
Log.Info($"Data Layer: {dataLayerJson}", this);
Best Practices
1. Initialize Early
Always initialize dataLayer before GTM loads:
window.dataLayer = window.dataLayer || [];
2. Use Consistent Naming
Follow a naming convention:
- Use camelCase:
pageName,userId - Use descriptive names:
productIdnotid - Group related data:
page.name,user.id
3. Avoid PII
Don't include personally identifiable information:
// Bad
'email': 'user@example.com'
// Good
'hasEmail': true
4. Use Events
Push events for user actions:
window.dataLayer.push({
'event': 'buttonClick',
'buttonName': 'Subscribe'
});
5. Clear E-commerce Data
Clear e-commerce objects before pushing new data:
window.dataLayer.push({ ecommerce: null });
window.dataLayer.push({
'event': 'purchase',
'ecommerce': { /* new data */ }
});
Testing Data Layer
1. GTM Preview Mode
Use GTM Preview to see dataLayer variables in real-time
2. Browser Extensions
- Google Tag Assistant: Verify dataLayer structure
- dataLayer Inspector: Chrome extension for debugging
3. Console Testing
// Test dataLayer structure
console.log(window.dataLayer);
// Find specific events
window.dataLayer.filter(item => item.event === 'pageview');