Kentico GA4 Event Tracking: Setup Guide | OpsBlu Docs

Kentico GA4 Event Tracking: Setup Guide

Implement custom event tracking for Google Analytics 4 on Kentico Xperience websites

Learn how to implement custom event tracking for Google Analytics 4 on Kentico Xperience websites, including form submissions, downloads, clicks, and custom interactions.

Prerequisites

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

<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>
<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

  1. GA4 DebugView: Navigate to GA4 → Configure → DebugView
  2. Browser Console: Check for event logs
  3. Network Tab: Filter by "collect" to see event requests
  4. GA4 Realtime: View events in real-time reports

Best Practices

  1. Use Consistent Naming: Follow GA4 recommended event names when possible
  2. Add Relevant Parameters: Include contextual data (page type, user role, etc.)
  3. Don't Over-Track: Track meaningful interactions, not every click
  4. Test in Staging: Verify events before production deployment
  5. Document Events: Maintain a measurement plan spreadsheet
  6. Use Event Parameters Wisely: Keep parameter names consistent across events
  7. 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

Next Steps

Additional Resources