Learn how to implement custom event tracking for Google Analytics 4 on Kentico Xperience websites, including form submissions, downloads, clicks, and custom interactions.
Prerequisites
- GA4 Setup completed
- Understanding of GA4 event structure
- Access to Kentico development environment
Event Tracking Basics
GA4 events consist of:
- Event name (required): Action identifier (e.g.,
form_submit) - Event parameters (optional): Additional data about the event
// Basic event syntax
gtag('event', 'event_name', {
'parameter_1': 'value_1',
'parameter_2': 'value_2'
});
Form Submission Tracking
Kentico Forms (Built-in Form Builder)
Track submissions of Kentico's built-in forms:
Method 1: Global Form Tracking (MVC)
Add to your layout file (_Layout.cshtml):
@using CMS.OnlineForms
<script>
// Track Kentico form submissions
document.addEventListener('DOMContentLoaded', function() {
var kenticoForms = document.querySelectorAll('form[data-kentico-form]');
kenticoForms.forEach(function(form) {
form.addEventListener('submit', function(e) {
var formName = form.getAttribute('data-kentico-form') || 'unknown';
gtag('event', 'form_submit', {
'form_name': formName,
'form_location': window.location.pathname,
'form_type': 'kentico_form'
});
});
});
});
</script>
Method 2: Form-Specific Tracking with Custom JavaScript
Create a custom web part or add to your form view:
@model YourFormViewModel
<form id="contactForm" method="post" asp-action="Submit" asp-controller="Form">
@* Form fields here *@
<button type="submit" id="submitBtn">Submit</button>
</form>
<script>
document.getElementById('contactForm').addEventListener('submit', function(e) {
gtag('event', 'form_submit', {
'form_name': 'contact_form',
'form_id': 'contactForm',
'page_location': window.location.href,
'page_title': document.title
});
});
</script>
Method 3: Server-Side Tracking on Success
In your MVC controller:
using CMS.OnlineForms;
using CMS.SiteProvider;
public ActionResult SubmitForm(FormViewModel model)
{
if (ModelState.IsValid)
{
// Process form submission
var formInfo = BizFormInfoProvider.GetBizFormInfo("ContactForm", SiteContext.CurrentSiteID);
// Add tracking script to response
ViewBag.TrackFormSubmission = true;
ViewBag.FormName = formInfo.FormDisplayName;
return View("Success");
}
return View(model);
}
In your success view:
@if (ViewBag.TrackFormSubmission == true)
{
<script>
gtag('event', 'form_submit', {
'form_name': '@ViewBag.FormName',
'form_submission_status': 'success',
'engagement_time_msec': '100'
});
</script>
}
Custom Forms (ASP.NET MVC Forms)
For custom MVC forms:
@using (Html.BeginForm("Submit", "Contact", FormMethod.Post, new { id = "contactForm" }))
{
@Html.LabelFor(m => m.Email)
@Html.TextBoxFor(m => m.Email)
@Html.LabelFor(m => m.Message)
@Html.TextAreaFor(m => m.Message)
<button type="submit">Send</button>
}
<script>
document.getElementById('contactForm').addEventListener('submit', function(e) {
var formData = new FormData(this);
gtag('event', 'generate_lead', {
'form_name': 'contact_form',
'method': 'email',
'form_destination': this.action
});
});
</script>
Download Tracking
Automatic PDF/File Download Tracking
Add to your layout file:
<script>
document.addEventListener('DOMContentLoaded', function() {
// Track file downloads
var downloadLinks = document.querySelectorAll('a[href$=".pdf"], a[href$=".doc"], a[href$=".docx"], a[href$=".zip"], a[href$=".xlsx"]');
downloadLinks.forEach(function(link) {
link.addEventListener('click', function(e) {
var href = this.getAttribute('href');
var fileName = href.split('/').pop();
var fileExtension = fileName.split('.').pop();
gtag('event', 'file_download', {
'file_name': fileName,
'file_extension': fileExtension,
'link_url': href,
'link_text': this.innerText || this.textContent
});
});
});
});
</script>
Kentico Media Library Downloads
Track downloads from Kentico media libraries:
@using CMS.MediaLibrary
@{
var mediaFile = MediaFileInfoProvider.GetMediaFileInfo(mediaFileId);
}
<a href="@mediaFile.FileURL"
class="download-link"
data-file-name="@mediaFile.FileName"
data-file-type="@mediaFile.FileExtension"
data-file-size="@mediaFile.FileSize">
Download @mediaFile.FileTitle
</a>
<script>
document.querySelectorAll('.download-link').forEach(function(link) {
link.addEventListener('click', function(e) {
gtag('event', 'file_download', {
'file_name': this.getAttribute('data-file-name'),
'file_extension': this.getAttribute('data-file-type'),
'file_size': this.getAttribute('data-file-size'),
'source': 'kentico_media_library'
});
});
});
</script>
Attachment Downloads
@using CMS.DocumentEngine
@{
var attachment = DocumentHelper.GetAttachment(attachmentGuid, DocumentContext.CurrentDocument);
}
<a href="@attachment.AttachmentUrl" 'file_download', {
'file_name': '@attachment.AttachmentName',
'file_extension': '@attachment.AttachmentExtension',
'document_type': '@DocumentContext.CurrentDocument.ClassName',
'attachment_title': '@attachment.AttachmentTitle'
});">
@attachment.AttachmentTitle
</a>
Click Tracking
Outbound Link Tracking
<script>
document.addEventListener('DOMContentLoaded', function() {
// Track outbound links
var currentDomain = window.location.hostname;
var allLinks = document.querySelectorAll('a[href^="http"]');
allLinks.forEach(function(link) {
var linkDomain = new URL(link.href).hostname;
// Check if it's an external link
if (linkDomain !== currentDomain) {
link.addEventListener('click', function(e) {
gtag('event', 'click', {
'event_category': 'outbound',
'event_label': this.href,
'link_text': this.innerText || this.textContent,
'link_domain': linkDomain
});
});
}
});
});
</script>
CTA Button Tracking
<button class="cta-button"
data-cta-name="request_demo" 'click', {
'event_category': 'cta',
'event_label': 'request_demo',
'button_text': this.innerText,
'page_location': window.location.pathname
});">
Request Demo
</button>
Navigation Menu Tracking
<script>
// Track main navigation clicks
document.querySelectorAll('.main-nav a').forEach(function(link) {
link.addEventListener('click', function(e) {
gtag('event', 'navigation_click', {
'menu_item': this.innerText,
'menu_url': this.getAttribute('href'),
'menu_position': Array.from(this.parentElement.parentElement.children).indexOf(this.parentElement) + 1
});
});
});
</script>
Video Tracking
YouTube Embedded Videos (Kentico Page Builder)
<script>
// Load YouTube IFrame API
var tag = document.createElement('script');
tag.src = "https://www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
var players = [];
function onYouTubeIframeAPIReady() {
var videoFrames = document.querySelectorAll('iframe[src*="youtube.com"]');
videoFrames.forEach(function(iframe, index) {
// Extract video ID from src
var videoId = iframe.src.match(/embed\/([^?]+)/)[1];
players[index] = new YT.Player(iframe, {
events: {
'onStateChange': function(event) {
trackYouTubeEvent(event, videoId);
}
}
});
});
}
function trackYouTubeEvent(event, videoId) {
var eventAction = '';
switch(event.data) {
case YT.PlayerState.PLAYING:
eventAction = 'play';
break;
case YT.PlayerState.PAUSED:
eventAction = 'pause';
break;
case YT.PlayerState.ENDED:
eventAction = 'complete';
break;
}
if (eventAction) {
gtag('event', 'video_' + eventAction, {
'video_provider': 'youtube',
'video_id': videoId,
'video_url': 'https://www.youtube.com/watch?v=' + videoId
});
}
}
</script>
HTML5 Video Tracking
<video id="myVideo" controls>
<source src="/path/to/video.mp4" type="video/mp4">
</video>
<script>
var video = document.getElementById('myVideo');
var videoTracked = {
started: false,
quarter: false,
half: false,
threeQuarter: false,
completed: false
};
video.addEventListener('play', function() {
if (!videoTracked.started) {
gtag('event', 'video_start', {
'video_title': document.title,
'video_url': this.currentSrc,
'video_duration': this.duration
});
videoTracked.started = true;
}
});
video.addEventListener('timeupdate', function() {
var percentPlayed = (this.currentTime / this.duration) * 100;
if (percentPlayed >= 25 && !videoTracked.quarter) {
gtag('event', 'video_progress', {
'video_title': document.title,
'video_percent': 25
});
videoTracked.quarter = true;
}
// Add similar logic for 50%, 75%, 100%
});
video.addEventListener('ended', function() {
if (!videoTracked.completed) {
gtag('event', 'video_complete', {
'video_title': document.title,
'video_url': this.currentSrc
});
videoTracked.completed = true;
}
});
</script>
Search Tracking
Track Kentico Smart Search results:
@using CMS.Search
<script>
// On search results page
@if (ViewBag.SearchQuery != null)
{
<text>
gtag('event', 'search', {
'search_term': '@ViewBag.SearchQuery',
'search_results': @ViewBag.ResultCount,
'search_type': 'site_search'
});
</text>
}
</script>
Custom Kentico Events
Page Type-Specific Events
Track interactions with specific Kentico page types:
@using CMS.DocumentEngine
@{
var currentPage = DocumentContext.CurrentDocument;
}
@if (currentPage.ClassName == "Custom.Article")
{
<script>
gtag('event', 'article_view', {
'article_title': '@currentPage.DocumentName',
'article_category': '@currentPage.GetValue("ArticleCategory")',
'article_author': '@currentPage.GetValue("ArticleAuthor")',
'publish_date': '@currentPage.DocumentPublishFrom'
});
</script>
}
Product Catalog Events (Non-E-commerce)
Track product page views:
@{
var product = DocumentContext.CurrentDocument;
}
<script>
gtag('event', 'view_item', {
'item_id': '@product.DocumentID',
'item_name': '@product.DocumentName',
'item_category': '@product.GetValue("ProductCategory")',
'content_type': 'product_page'
});
</script>
User Engagement Events
Scroll Depth Tracking
<script>
var scrollDepthTracked = {
25: false,
50: false,
75: false,
100: false
};
window.addEventListener('scroll', function() {
var scrollPercent = (window.scrollY / (document.documentElement.scrollHeight - window.innerHeight)) * 100;
Object.keys(scrollDepthTracked).forEach(function(depth) {
if (scrollPercent >= depth && !scrollDepthTracked[depth]) {
gtag('event', 'scroll', {
'percent_scrolled': depth,
'page_location': window.location.pathname
});
scrollDepthTracked[depth] = true;
}
});
});
</script>
Time on Page
<script>
var startTime = new Date().getTime();
window.addEventListener('beforeunload', function() {
var timeSpent = Math.round((new Date().getTime() - startTime) / 1000);
// Only track if user spent more than 10 seconds
if (timeSpent > 10) {
gtag('event', 'timing_complete', {
'name': 'page_engagement',
'value': timeSpent,
'event_category': 'engagement',
'event_label': window.location.pathname
});
}
});
</script>
Kentico-Specific Context Events
Tracking Page Builder Interactions
<script>
// Track widget interactions on Page Builder pages
document.addEventListener('click', function(e) {
var widget = e.target.closest('[data-widget-type]');
if (widget) {
gtag('event', 'widget_interaction', {
'widget_type': widget.getAttribute('data-widget-type'),
'widget_zone': widget.closest('[data-zone-id]')?.getAttribute('data-zone-id'),
'interaction_type': e.target.tagName.toLowerCase()
});
}
});
</script>
Personalization Variant Tracking
@using CMS.OnlineMarketing
@{
var variant = ViewBag.PersonalizationVariant;
}
@if (variant != null)
{
<script>
gtag('event', 'personalization_view', {
'variant_name': '@variant.VariantDisplayName',
'variant_enabled': '@variant.VariantEnabled',
'page_type': '@DocumentContext.CurrentDocument.ClassName'
});
</script>
}
Debugging Events
Enable Debug Mode
<script>
// Enable GA4 debug mode
gtag('config', 'G-XXXXXXXXXX', {
'debug_mode': true
});
// Log all events to console
window.dataLayer = window.dataLayer || [];
var originalPush = window.dataLayer.push;
window.dataLayer.push = function() {
console.log('GA4 Event:', arguments);
return originalPush.apply(window.dataLayer, arguments);
};
</script>
Testing Events
- GA4 DebugView: Navigate to GA4 → Configure → DebugView
- Browser Console: Check for event logs
- Network Tab: Filter by "collect" to see event requests
- GA4 Realtime: View events in real-time reports
Best Practices
- Use Consistent Naming: Follow GA4 recommended event names when possible
- Add Relevant Parameters: Include contextual data (page type, user role, etc.)
- Don't Over-Track: Track meaningful interactions, not every click
- Test in Staging: Verify events before production deployment
- Document Events: Maintain a measurement plan spreadsheet
- Use Event Parameters Wisely: Keep parameter names consistent across events
- Consider User Privacy: Don't send PII in event parameters
Common Issues
Events Not Firing
- Check console for JavaScript errors
- Verify gtag is loaded before event code
- Ensure event syntax is correct
- Check ad blockers aren't interfering
Events Missing in Reports
- Allow 24-48 hours for events to appear in standard reports
- Use DebugView for immediate validation
- Verify event name matches GA4 conventions
Duplicate Events
- Check for multiple event listeners on same element
- Verify tracking code doesn't appear multiple times
- Review event delegation setup