Integrating with Site Analytics
Outline
- Core idea: CMS repositories query CMS content; analytics platforms query analytics data.
- What “integration” means: instrument templates + pass content metadata into tracking tools.
- Where CMS helps: store analytics-related settings on content, and audit those settings programmatically.
-
Where CMS doesn’t help: you can’t pull pageviews, sessions, or conversions from
IContentLoader.
1. Introduction
In digital experience work, content quality matters—but content performance matters too. Analytics helps you understand how people interact with pages, blocks, CTAs, forms, and journeys. For CMS 12 developers, the tricky part is connecting “this content item” to “that performance record.”
One important clarification up front:
Optimizely CMS content repositories (IContentRepository, IContentLoader) are for querying and managing CMS content, not querying raw analytics data.
Analytics data lives in specialized platforms (GA4, ODP, etc.), and integration means connecting systems—not expecting one repository to magically speak the other system’s language.
In practice, integration usually looks like:
- instrumenting templates with tracking scripts
- passing content metadata (IDs, categories, template type, campaign tags) into analytics events
- using analytics outputs to influence delivery (personalization/experimentation)
- auditing analytics configuration stored inside CMS content
2. Understanding analytics data sources
Analytics platforms are built for high-volume event collection, aggregation, segmentation, and reporting. They store metrics like pageviews, sessions, bounce rate/engagement, conversions, and audience cohorts.
Common analytics sources you’ll see alongside CMS 12:
- Google Analytics (GA4): tracks and reports website traffic and events.
- Optimizely Data Platform (ODP): a CDP for unifying customer data and powering real-time audiences and personalization.
- Custom analytics: internal pipelines for product-specific KPIs and attribution models.
Each platform exposes its own APIs (for example, GA4’s data API or ODP’s APIs) for querying analytics datasets. CMS repositories are not a substitute for those APIs.
3. Connecting CMS content to analytics
“Bridging content and analytics” starts with instrumentation: your rendered pages must send events that include enough identifiers to tie an event back to a content item. This is where CMS content can help—by storing analytics-related settings or identifiers that your templates output.
Example: reading a tracking ID property from a page
A common pattern is a custom property on a page type, such as a GA measurement ID override, content group,
campaign tag, or “exclude from tracking” flag. You read the property via IContentLoader and then pass it to your rendering logic.
using EPiServer.Core;
using System;
// Assume StandardPage has a custom property 'GoogleAnalyticsId'
// public class StandardPage : PageData { public virtual string GoogleAnalyticsId { get; set; } }
public class AnalyticsIntegrationService
{
private readonly IContentLoader _contentLoader;
public AnalyticsIntegrationService(IContentLoader contentLoader)
{
_contentLoader = contentLoader;
}
public string GetPageAnalyticsId(ContentReference pageLink)
{
try
{
StandardPage page = _contentLoader.Get<StandardPage>(pageLink);
if (!string.IsNullOrEmpty(page.GoogleAnalyticsId))
{
Console.WriteLine($"Page '{page.PageName}' has Google Analytics ID: {page.GoogleAnalyticsId}");
return page.GoogleAnalyticsId;
}
Console.WriteLine($"Page '{page.PageName}' does not have a specific Google Analytics ID.");
return null;
}
catch (ContentNotFoundException)
{
Console.WriteLine($"Page at {pageLink.ID} not found.");
return null;
}
catch (TypeMismatchException)
{
Console.WriteLine($"Content at {pageLink.ID} is not a StandardPage.");
return null;
}
}
}
Explanation: The repository call retrieves CMS data (your custom property). What you do with that value—embedding it into markup, a script block, a data layer, or a tracking library—is the “integration” part.
Example: sending custom events with AnalyticsInteraction helpers (Google Analytics add-on)
If you’re using Optimizely’s Google Analytics add-on, helper APIs can let you push custom events tied to CMS content. In many implementations, that means placing event definitions on the request context and letting the page’s tracking script render them.
using EPiServer.GoogleAnalytics.Helpers;
using EPiServer.GoogleAnalytics.Helpers.Events;
using Microsoft.AspNetCore.Http;
using System;
public class CustomAnalyticsEventService
{
// Typically called from a controller, view component, or middleware in a web request.
public void TrackCustomContentEvent(
HttpContext httpContext,
ContentReference contentLink,
string eventName,
string eventCategory)
{
if (httpContext == null)
{
Console.WriteLine("HttpContext is null, cannot track event.");
return;
}
httpContext.AddAnalyticsCustom(new AnalyticsEvent
{
EventName = eventName,
EventCategory = eventCategory,
EventAction = "ContentInteraction",
EventLabel = contentLink.ID.ToString()
});
Console.WriteLine($"Custom analytics event '{eventName}' tracked for content ID: {contentLink.ID}");
}
}
Explanation: You’re not “querying analytics” here—you’re emitting analytics events. The content link (or a stable content identifier) becomes your bridge between CMS content and analytics reporting.
4. Using analytics to influence CMS delivery
Analytics isn’t just for dashboards. It can influence what content users see—through personalization and experimentation. In Optimizely ecosystems, that often shows up as segments/audiences driving conditional content.
ODP and real-time audiences
ODP integrates with CMS to enable real-time personalization based on audiences. In CMS terms, editors typically work with audiences/visitor group logic in the UI, while developers ensure the integration is enabled and that templates/components support targeted rendering.
// Conceptual example (pseudo):
// Editors target content using audiences (visitor groups / real-time audiences).
// Developers wire up rendering so different blocks/partials are served based on audience evaluation.
//
// @if (Model.Audience?.IsInSegment("HighValueCustomers") == true)
// {
// @Html.Partial("PersonalizedPromoBlock", Model.HighValuePromo)
// }
// else
// {
// @Html.Partial("DefaultPromoBlock", Model.DefaultPromo)
// }
Explanation: Querying “who is in which audience right now” is typically done via the platform integration (ODP APIs/integration layer), not through CMS repositories. CMS repositories remain focused on content.
5. Querying CMS content related to analytics
Even though CMS repositories can’t fetch pageviews or conversions, they can absolutely help you audit analytics-related settings stored in CMS content. That’s often a real operational need: “Which pages still use the old measurement ID?” or “Which content is excluded from tracking?”
Scenario: finding pages with a specific measurement ID
If your StandardPage has a GoogleAnalyticsId property, you can locate matching content under a subtree.
This is useful for audits, migrations, and keeping instrumentation consistent.
using EPiServer.Core;
using System;
using System.Collections.Generic;
using System.Linq;
public class AnalyticsContentQueryService
{
private readonly IContentLoader _contentLoader;
public AnalyticsContentQueryService(IContentLoader contentLoader)
{
_contentLoader = contentLoader;
}
public IEnumerable<StandardPage> FindPagesWithSpecificAnalyticsId(
ContentReference rootLink,
string targetAnalyticsId)
{
Console.WriteLine($"Finding pages with Google Analytics ID '{targetAnalyticsId}' under ID: {rootLink.ID}");
IEnumerable<StandardPage> allStandardPages = _contentLoader.GetDescendants<StandardPage>(rootLink);
var matchingPages = allStandardPages
.Where(page => !string.IsNullOrWhiteSpace(page.GoogleAnalyticsId)
&& page.GoogleAnalyticsId.Equals(targetAnalyticsId, StringComparison.OrdinalIgnoreCase))
.ToList();
if (matchingPages.Any())
{
Console.WriteLine($"Found {matchingPages.Count} pages:");
foreach (var page in matchingPages)
{
Console.WriteLine($"- '{page.PageName}' (ID: {page.ContentLink.ID})");
}
}
else
{
Console.WriteLine("No pages found with the specified Google Analytics ID.");
}
return matchingPages;
}
}
Explanation: This is a CMS query over CMS data (your custom property). The output helps you manage instrumentation, but it does not provide analytics metrics.
6. Limits of CMS repositories for analytics
It’s worth restating the boundary clearly, because this misunderstanding causes a lot of “why is the repository empty?” moments:
-
No raw analytics data:
IContentRepository/IContentLoadercannot query pageviews, sessions, bounce rate, conversions, or audience membership. - No real-time performance metrics: repositories don’t expose “how is this content performing?” You must use the analytics platform’s APIs for that.
If you need performance numbers, use the platform that stores the numbers (GA4, ODP, etc.). If you need content configuration, use CMS repositories. Different jobs, different toolboxes.
7. Best practices for analytics integration
- Consistent instrumentation: ensure templates consistently output tracking scripts and content metadata.
- Privacy and consent: implement consent handling and comply with relevant regulations.
- Use ODP when appropriate: for segmentation + personalization, a CDP can unlock more than pageview reports.
- Audit regularly: query CMS content for analytics-related configuration drift (IDs, flags, exclusions).
-
Model analytics metadata: prefer explicit properties like
AnalyticsTrackingIdandExcludeFromAnalyticsover “mystery logic” in templates.
8. Conclusion
Integrating Optimizely CMS 12 with analytics platforms is essential for building data-driven experiences. The key is understanding the boundary: CMS repositories manage content, and analytics platforms manage behavior/performance data.
Once that boundary is clear, the integration path becomes straightforward: store analytics metadata in CMS, emit consistent tracking events from templates, use analytics outputs for personalization, and audit your configuration with normal CMS querying patterns.
