GA4 Event Tracking on Sitecore
Learn how to track custom events in Google Analytics 4 on Sitecore websites, from simple click tracking to advanced xDB integrations and Sitecore Forms.
Prerequisites
- GA4 installed on Sitecore
- Basic understanding of GA4 events
- Access to modify Sitecore views and JavaScript
Event Tracking Methods
1. Basic JavaScript Events
Simple client-side tracking in Razor views:
@* Button click tracking *@
<button onclick="gtag('event', 'cta_click', {
'event_category': 'CTA',
'event_label': '@Model.Item["Button Text"]',
'value': 1
});">
@Model.Item["Button Text"]
</button>
2. Centralized Event Tracking Script
Create reusable tracking script:
// /scripts/analytics/sitecore-events.js
(function(window) {
'use strict';
window.SitecoreAnalytics = window.SitecoreAnalytics || {};
// Track generic event
SitecoreAnalytics.trackEvent = function(eventName, params) {
if (typeof gtag === 'function') {
gtag('event', eventName, params);
}
};
// Track CTA clicks
SitecoreAnalytics.trackCTA = function(ctaName, ctaLocation) {
this.trackEvent('cta_click', {
'cta_name': ctaName,
'cta_location': ctaLocation,
'page_path': window.location.pathname
});
};
// Track downloads
SitecoreAnalytics.trackDownload = function(fileName, fileType) {
this.trackEvent('file_download', {
'file_name': fileName,
'file_extension': fileType,
'link_url': arguments[2] || ''
});
};
// Track outbound links
SitecoreAnalytics.trackOutbound = function(url) {
this.trackEvent('outbound_click', {
'link_url': url,
'link_domain': new URL(url).hostname
});
};
// Track video interactions
SitecoreAnalytics.trackVideo = function(action, videoName) {
this.trackEvent('video_' + action, {
'video_title': videoName,
'video_url': arguments[2] || ''
});
};
// Track search
SitecoreAnalytics.trackSearch = function(searchTerm, resultCount) {
this.trackEvent('search', {
'search_term': searchTerm,
'search_results': resultCount
});
};
})(window);
Include in layout:
@* /Views/Shared/_Layout.cshtml *@
<script src="/scripts/analytics/sitecore-events.js"></script>
3. Automatic Event Tracking
Automatically track all downloads and outbound links:
// /scripts/analytics/auto-tracking.js
document.addEventListener('DOMContentLoaded', function() {
// Track all download links
document.querySelectorAll('a[href]').forEach(function(link) {
var href = link.getAttribute('href');
// Check if it's a download (pdf, doc, zip, etc.)
var downloadExtensions = /\.(pdf|doc|docx|xls|xlsx|ppt|pptx|zip|rar|txt|csv)$/i;
if (downloadExtensions.test(href)) {
link.addEventListener('click', function() {
var fileName = href.split('/').pop();
var extension = fileName.split('.').pop();
SitecoreAnalytics.trackDownload(fileName, extension, href);
});
}
// Check if it's an outbound link
if (href.startsWith('http') && !href.includes(window.location.hostname)) {
link.addEventListener('click', function() {
SitecoreAnalytics.trackOutbound(href);
});
}
});
// Track mailto links
document.querySelectorAll('a[href^="mailto:"]').forEach(function(link) {
link.addEventListener('click', function() {
gtag('event', 'email_click', {
'email_address': link.getAttribute('href').replace('mailto:', '')
});
});
});
// Track tel links
document.querySelectorAll('a[href^="tel:"]').forEach(function(link) {
link.addEventListener('click', function() {
gtag('event', 'phone_click', {
'phone_number': link.getAttribute('href').replace('tel:', '')
});
});
});
});
Sitecore Component Event Tracking
Track CTA Component Clicks
Controller:
// /Controllers/CTAController.cs
using System.Web.Mvc;
using Sitecore.Mvc.Controllers;
namespace YourProject.Controllers
{
public class CTAController : SitecoreController
{
public ActionResult CTA()
{
return View();
}
}
}
View:
@* /Views/CTA/CTA.cshtml *@
@using Sitecore.Mvc
@{
var buttonText = Html.Sitecore().Field("Button Text").ToString();
var buttonLink = Html.Sitecore().Field("Button Link");
var ctaName = Html.Sitecore().Field("CTA Name").ToString();
var componentId = Model.RenderingItem.ID.ToString();
}
<div class="cta-component" data-component-id="@componentId">
<a href="@buttonLink"
class="cta-button"
onclick="SitecoreAnalytics.trackCTA('@ctaName', '@Model.PageItem.Name');">
@buttonText
</a>
</div>
Track Hero Banner Interactions
@* /Views/Hero/HeroBanner.cshtml *@
@{
var heroTitle = Html.Sitecore().Field("Title").ToString();
var heroImage = Html.Sitecore().Field("Background Image");
}
<div class="hero-banner"
data-tracking-component="hero_banner"
data-tracking-title="@heroTitle">
@Html.Sitecore().Field("Background Image")
<div class="hero-content">
@Html.Sitecore().Field("Title", new { @class = "hero-title" })
@Html.Sitecore().Field("Subtitle")
<button class="hero-cta"
onclick="gtag('event', 'hero_cta_click', {
'hero_title': '@heroTitle',
'cta_text': this.innerText
});">
@Html.Sitecore().Field("CTA Text")
</button>
</div>
</div>
<script>
// Track hero banner view
gtag('event', 'hero_view', {
'hero_title': '@heroTitle',
'page_path': window.location.pathname
});
</script>
Sitecore Forms Event Tracking
Track Form Submissions
Method 1: Submit Action (Recommended)
Create custom submit action:
// /Forms/SubmitActions/GoogleAnalyticsSubmitAction.cs
using System;
using Sitecore.Diagnostics;
using Sitecore.ExperienceForms.Models;
using Sitecore.ExperienceForms.Processing;
using Sitecore.ExperienceForms.Processing.Actions;
namespace YourProject.Forms.SubmitActions
{
public class GoogleAnalyticsSubmitAction : SubmitActionBase<string>
{
public GoogleAnalyticsSubmitAction(ISubmitActionData submitActionData)
: base(submitActionData)
{
}
protected override bool Execute(string data, FormSubmitContext formSubmitContext)
{
Assert.ArgumentNotNull(formSubmitContext, nameof(formSubmitContext));
var formName = formSubmitContext.FormName;
var formId = formSubmitContext.FormId.ToString();
// Track in Sitecore Analytics (xDB)
if (Sitecore.Analytics.Tracker.Current != null &&
Sitecore.Analytics.Tracker.Current.IsActive)
{
var currentPage = Sitecore.Analytics.Tracker.Current.Session.Interaction?.CurrentPage;
if (currentPage != null)
{
var pageEventId = Sitecore.Data.ID.Parse("{YOUR-FORM-SUBMIT-EVENT-ID}");
var pageEvent = currentPage.Register(pageEventId);
pageEvent.Data = formName;
pageEvent.Text = $"Form Submitted: {formName}";
}
}
return true;
}
}
}
Register submit action:
<!-- App_Config/Include/Forms/CustomSubmitActions.config -->
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
<sitecore>
<submitActions>
<submitAction name="Google Analytics Track"
type="YourProject.Forms.SubmitActions.GoogleAnalyticsSubmitAction, YourProject"/>
</submitActions>
</sitecore>
</configuration>
Method 2: Client-Side Form Tracking
// /scripts/analytics/form-tracking.js
document.addEventListener('DOMContentLoaded', function() {
// Track Sitecore Forms submission
var sitecoreForms = document.querySelectorAll('form[data-sc-fxb-form]');
sitecoreForms.forEach(function(form) {
var formName = form.getAttribute('data-sc-fxb-form') || 'Unknown Form';
form.addEventListener('submit', function(e) {
// Get form data
var formData = new FormData(form);
var formFields = {};
for (var pair of formData.entries()) {
if (pair[0] && pair[1]) {
formFields[pair[0]] = pair[1];
}
}
// Track form submission
gtag('event', 'form_submit', {
'form_name': formName,
'form_id': form.id,
'form_location': window.location.pathname
});
// Track form start if not already tracked
if (!sessionStorage.getItem('form_started_' + formName)) {
gtag('event', 'form_start', {
'form_name': formName
});
}
});
// Track form start (first interaction)
var formInputs = form.querySelectorAll('input, textarea, select');
var formStarted = false;
formInputs.forEach(function(input) {
input.addEventListener('focus', function() {
if (!formStarted) {
formStarted = true;
sessionStorage.setItem('form_started_' + formName, 'true');
gtag('event', 'form_start', {
'form_name': formName,
'form_id': form.id
});
}
}, { once: true });
});
});
});
Track Form Abandonment
// Track form abandonment
var formAbandonment = (function() {
var formInteractions = {};
function trackAbandonment(formName, formId) {
gtag('event', 'form_abandonment', {
'form_name': formName,
'form_id': formId,
'time_spent': formInteractions[formId] || 0
});
}
// Monitor form interactions
document.querySelectorAll('form[data-sc-fxb-form]').forEach(function(form) {
var formId = form.id;
var formName = form.getAttribute('data-sc-fxb-form');
var startTime = null;
form.addEventListener('focus', function() {
if (!startTime) {
startTime = Date.now();
}
}, true);
// Track on page unload if form not submitted
window.addEventListener('beforeunload', function() {
if (startTime && !form.hasAttribute('data-submitted')) {
formInteractions[formId] = Math.floor((Date.now() - startTime) / 1000);
trackAbandonment(formName, formId);
}
});
// Mark as submitted
form.addEventListener('submit', function() {
form.setAttribute('data-submitted', 'true');
});
});
})();
Integration with Sitecore xDB
Sync Events with xDB
Track GA4 events as Sitecore Goals:
// /Analytics/GoalHelper.cs
using Sitecore.Analytics;
using Sitecore.Data;
namespace YourProject.Analytics
{
public static class GoalHelper
{
public static void TriggerGoal(ID goalId, string additionalData = null)
{
if (Tracker.Current != null && Tracker.Current.IsActive)
{
var currentPage = Tracker.Current.Session.Interaction?.CurrentPage;
if (currentPage != null)
{
var goal = currentPage.Register(goalId);
if (!string.IsNullOrEmpty(additionalData))
{
goal.Data = additionalData;
}
}
}
}
}
}
Use in Controller:
// Track both GA4 and Sitecore Goal
public ActionResult DownloadPDF(string fileName)
{
// Trigger Sitecore Goal
var downloadGoalId = ID.Parse("{YOUR-DOWNLOAD-GOAL-ID}");
GoalHelper.TriggerGoal(downloadGoalId, fileName);
// Return file
return File(filePath, "application/pdf", fileName);
}
Corresponding client-side GA4 event:
gtag('event', 'file_download', {
'file_name': fileName,
'file_type': 'PDF'
});
Track Personalization with GA4
Track which personalization rules fire:
@using Sitecore.Analytics
@using Sitecore.Mvc.Analytics.Extensions
@{
if (Tracker.Current != null && Tracker.Current.IsActive)
{
var currentPage = Tracker.Current.Session.Interaction?.CurrentPage;
if (currentPage != null)
{
var personalizationData = Html.Sitecore().PersonalizationData();
if (personalizationData != null)
{
<script>
gtag('event', 'personalization_applied', {
'rule_name': '@personalizationData.RuleName',
'component_name': '@Model.RenderingItem.Name'
});
</script>
}
}
}
}
Track Content Engagement
Scroll Depth Tracking
// /scripts/analytics/scroll-tracking.js
(function() {
var scrollDepths = [25, 50, 75, 100];
var tracked = {};
function getScrollPercentage() {
var scrollTop = window.pageYOffset || document.documentElement.scrollTop;
var scrollHeight = document.documentElement.scrollHeight - document.documentElement.clientHeight;
return (scrollTop / scrollHeight) * 100;
}
function trackScrollDepth() {
var scrollPercent = getScrollPercentage();
scrollDepths.forEach(function(depth) {
if (scrollPercent >= depth && !tracked[depth]) {
tracked[depth] = true;
gtag('event', 'scroll_depth', {
'percent_scrolled': depth,
'page_path': window.location.pathname,
'page_title': document.title
});
}
});
}
// Throttle scroll events
var scrollTimeout;
window.addEventListener('scroll', function() {
clearTimeout(scrollTimeout);
scrollTimeout = setTimeout(trackScrollDepth, 100);
}, { passive: true });
})();
Time on Page
// Track engaged time
var engagementTracker = (function() {
var startTime = Date.now();
var isEngaged = true;
var totalEngagedTime = 0;
var lastCheckTime = startTime;
// Update engaged time
function updateEngagedTime() {
if (isEngaged) {
var now = Date.now();
totalEngagedTime += (now - lastCheckTime);
lastCheckTime = now;
}
}
// Track visibility changes
document.addEventListener('visibilitychange', function() {
if (document.hidden) {
updateEngagedTime();
isEngaged = false;
} else {
lastCheckTime = Date.now();
isEngaged = true;
}
});
// Track on page unload
window.addEventListener('beforeunload', function() {
updateEngagedTime();
gtag('event', 'engaged_time', {
'value': Math.floor(totalEngagedTime / 1000), // seconds
'page_path': window.location.pathname
});
});
})();
Element Visibility Tracking
// Track when elements come into view
function trackElementVisibility(selector, eventName) {
var elements = document.querySelectorAll(selector);
var observer = new IntersectionObserver(function(entries) {
entries.forEach(function(entry) {
if (entry.isIntersecting && !entry.target.hasAttribute('data-tracked')) {
entry.target.setAttribute('data-tracked', 'true');
gtag('event', eventName, {
'element_id': entry.target.id,
'element_text': entry.target.innerText.substring(0, 100)
});
}
});
}, { threshold: 0.5 });
elements.forEach(function(el) {
observer.observe(el);
});
}
// Track CTA visibility
trackElementVisibility('.cta-button', 'cta_view');
// Track product visibility
trackElementVisibility('.product-card', 'product_view');
SXA Component Tracking
Track SXA Component Views
@* Track SXA component rendering *@
@using Sitecore.XA.Foundation.Mvc.Extensions
@{
var componentName = Model.RenderingItem.Name;
var componentId = Model.RenderingItem.ID.ToString();
}
<div data-sxa-component="@componentName" data-component-id="@componentId">
@Html.Sitecore().Placeholder("component-content")
</div>
<script>
// Track component view
gtag('event', 'component_view', {
'component_name': '@componentName',
'component_id': '@componentId',
'page_path': window.location.pathname
});
</script>
Track SXA Search
// Track SXA search results
document.addEventListener('DOMContentLoaded', function() {
var searchForms = document.querySelectorAll('.search-box form');
searchForms.forEach(function(form) {
form.addEventListener('submit', function(e) {
var searchInput = form.querySelector('input[type="text"]');
var searchTerm = searchInput ? searchInput.value : '';
if (searchTerm) {
gtag('event', 'search', {
'search_term': searchTerm
});
// Track results after page loads
setTimeout(function() {
var resultsCount = document.querySelectorAll('.search-result-item').length;
gtag('event', 'view_search_results', {
'search_term': searchTerm,
'results_count': resultsCount
});
}, 1000);
}
});
});
});
JSS/Headless Event Tracking
Next.js Event Tracking
// /src/lib/analytics.ts
export const trackEvent = (eventName: string, params?: Record<string, any>) => {
if (typeof window !== 'undefined' && window.gtag) {
window.gtag('event', eventName, params);
}
};
export const trackCTA = (ctaName: string, ctaLocation: string) => {
trackEvent('cta_click', {
cta_name: ctaName,
cta_location: ctaLocation,
});
};
export const trackFormSubmit = (formName: string) => {
trackEvent('form_submit', {
form_name: formName,
});
};
export const trackDownload = (fileName: string, fileType: string) => {
trackEvent('file_download', {
file_name: fileName,
file_extension: fileType,
});
};
Use in Component:
// /src/components/CTA/CTA.tsx
import { trackCTA } from '@/lib/analytics';
import { Text, Link } from '@sitecore-jss/sitecore-jss-nextjs';
const CTA = ({ fields }: CTAProps): JSX.Element => {
const handleClick = () => {
trackCTA(fields.ctaName.value, fields.ctaLocation.value);
};
return (
<div className="cta-component">
<Link field={fields.ctaLink} onClick={handleClick}>
<Text field={fields.ctaText} />
</Link>
</div>
);
};
export default CTA;
React JSS Hooks
// /src/hooks/useAnalytics.js
import { useEffect } from 'react';
import { useSitecoreContext } from '@sitecore-jss/sitecore-jss-react';
export const usePageView = () => {
const { sitecoreContext } = useSitecoreContext();
useEffect(() => {
if (!sitecoreContext.pageEditing && window.gtag) {
window.gtag('event', 'page_view', {
page_path: sitecoreContext.route?.itemPath,
page_title: sitecoreContext.route?.name,
page_template: sitecoreContext.route?.templateName,
});
}
}, [sitecoreContext]);
};
export const useEventTracking = () => {
return {
trackEvent: (eventName, params) => {
if (window.gtag) {
window.gtag('event', eventName, params);
}
},
trackCTA: (ctaName, ctaLocation) => {
if (window.gtag) {
window.gtag('event', 'cta_click', {
cta_name: ctaName,
cta_location: ctaLocation,
});
}
},
};
};
Use in Component:
import { useEventTracking } from '@/hooks/useAnalytics';
const CTAComponent = ({ fields }) => {
const { trackCTA } = useEventTracking();
const handleClick = () => {
trackCTA(fields.ctaName.value, 'homepage-hero');
};
return (
<button onClick={handleClick}>
{fields.ctaText.value}
</button>
);
};
Custom Dimensions and Metrics
Configure Custom Dimensions
Set up in GA4:
- Go to Admin > Custom Definitions
- Create custom dimensions for Sitecore-specific data
Implement in Sitecore:
@{
var itemTemplate = Sitecore.Context.Item.TemplateName;
var itemPath = Sitecore.Context.Item.Paths.FullPath;
var siteName = Sitecore.Context.Site.Name;
var language = Sitecore.Context.Language.Name;
}
<script>
gtag('config', 'G-XXXXXXXXXX', {
'custom_map': {
'dimension1': 'sitecore_template',
'dimension2': 'sitecore_path',
'dimension3': 'sitecore_site',
'dimension4': 'sitecore_language'
},
'sitecore_template': '@itemTemplate',
'sitecore_path': '@itemPath',
'sitecore_site': '@siteName',
'sitecore_language': '@language'
});
</script>
Debugging Event Tracking
Debug Mode
// Enable GA4 debug mode
gtag('config', 'G-XXXXXXXXXX', {
'debug_mode': true
});
// Log all events to console
var originalGtag = window.gtag;
window.gtag = function() {
console.log('GA4 Event:', arguments);
originalGtag.apply(this, arguments);
};
Test Events
Use GA4 DebugView:
- Enable debug mode (above)
- Visit your Sitecore site
- Trigger events
- Check GA4 > Admin > DebugView
Sitecore Analytics Debugging
// Log GA4 events to Sitecore log
using Sitecore.Diagnostics;
public static void LogAnalyticsEvent(string eventName, object eventData)
{
Log.Info($"GA4 Event: {eventName} - Data: {Newtonsoft.Json.JsonConvert.SerializeObject(eventData)}", "Analytics");
}
Next Steps
- Set Up E-commerce Tracking for Sitecore Commerce
- Google Tag Manager Setup for centralized event management
- Debug Tracking Issues