Magnolia CMS is a Java-based enterprise digital experience platform that supports both traditional server-rendered (FreeMarker templates) and headless (REST API) delivery. Analytics integration depends on which delivery model you use, and requires understanding Magnolia's dual-instance architecture (author vs public instances).
Integration Architecture
Magnolia provides four integration paths:
- FreeMarker Templates -- Edit page templates in your Light Module or Maven module. Templates at
/templates/pages/control page layout and can inject tracking scripts. Light Modules allow file-based development without Maven builds. - Light Modules -- File-based modules in
/<magnolia-home>/modules/. Create or edit templates, dialogs, and configurations by placing files in the correct directory structure. No compilation required. - Headless/REST API -- Magnolia's Delivery API serves content as JSON. Frontend frameworks consume this API and handle analytics integration independently.
- Decoration -- Magnolia's decoration pattern extends existing templates without modifying them. Add tracking scripts by decorating the base page template.
Available Integrations
Analytics Platforms
- FreeMarker page template injection
- GTM via Light Module (recommended)
- Headless: frontend framework integration
Tag Management
- FreeMarker template head/body injection
- Light Module-based configuration
- Headless: frontend app shell
Marketing Pixels
- Via GTM container (recommended)
- FreeMarker template injection
FreeMarker Template Integration
Add GTM to your page template's FreeMarker file in a Light Module:
[#-- modules/yourmodule/templates/pages/base.ftl --]
<!DOCTYPE html>
<html lang="${cmsfn.language()!"en"}">
<head>
<!-- Google Tag Manager -->
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;
j.src='https://www.googletagmanager.com/gtm.js?id='+i+dl;
f.parentNode.insertBefore(j,f);})(window,document,'script','dataLayer','GTM-XXXX');</script>
[#assign content = cmsfn.contentByPath(state.handle)!]
<script>
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
'pageTemplate': '${state.handle!"unknown"}',
'pageTitle': '${content.title!""|js_string}',
'nodeType': '${content["jcr:primaryType"]!"mgnl:page"}',
'workspace': '${cmsfn.workspace()!"website"}',
'language': '${cmsfn.language()!"en"}',
'depth': '${cmsfn.ancestors(content)?size}'
});
</script>
[@cms.page /]
</head>
<body>
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-XXXX"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
[@cms.area name="main" /]
</body>
</html>
Light Module Configuration
Create a Light Module directory structure for analytics configuration:
modules/analytics/
├── templates/
│ └── components/
│ └── gtm-container.ftl
├── dialogs/
│ └── components/
│ └── gtm-container.yaml
└── decorations/
└── pages/
└── base.yaml
The dialog YAML allows editors to configure the GTM container ID via the Magnolia admin:
# modules/analytics/dialogs/components/gtm-container.yaml
form:
tabs:
- name: tabMain
fields:
- name: gtmId
class: info.magnolia.ui.form.field.definition.TextFieldDefinition
label: GTM Container ID
description: "Enter your GTM container ID (e.g., GTM-XXXX)"
Platform Limitations
Author vs Public instance. Magnolia runs two instances: the author instance (for content editing) and the public instance (for live content). Tracking scripts should only fire on the public instance. Use cmsfn.isEditMode() to conditionally exclude tracking during authoring:
[#if !cmsfn.isEditMode()]
<!-- GTM code here -->
[/#if]
JCR repository caching. Magnolia uses a JCR content repository with caching layers. Data layer values rendered in FreeMarker templates are cached along with the page HTML. User-specific data must be populated client-side.
Personalization overlap. Magnolia's built-in Personalization module tracks visitor segments and behavior. Running both Magnolia Personalization and GA4 creates parallel data collection. Coordinate segment definitions between the two systems.
Headless complexity. When using Magnolia as a headless CMS (Delivery API), the frontend framework handles all analytics. The FreeMarker template integration described above does not apply. You must implement tracking in your React/Next.js/Vue frontend separately.
Maven module vs Light Module. Complex analytics integrations (server-side event hooks, custom servlets for Measurement Protocol) require Maven modules compiled into the Magnolia WAR. Light Modules are file-based and limited to templates, dialogs, and configuration.
Performance Considerations
- JCR query performance. Accessing content properties in FreeMarker (
cmsfn.contentByPath()) triggers JCR repository queries. On pages with many components, each component's data layer contribution adds a query. Pre-compute data layer values at the page level rather than per-component. - Public instance caching. Magnolia's page cache on the public instance serves pre-rendered HTML. Tracking scripts in cached pages load identically for all users, which is correct behavior. Ensure dynamic data layer values use client-side population.
- Multi-site. Magnolia supports multi-site configurations. Each site can have different templates and configurations. Use site-specific GTM container IDs via dialog-configurable components.
Recommended Integration Priority
- Add GTM to base page template -- Conditional on
!cmsfn.isEditMode()to exclude author instance - Build content-aware data layer -- Map Magnolia content types, workspace hierarchy, and language to data layer
- Configure GA4 via GTM -- Content groups from Magnolia page templates, custom dimensions from JCR properties
- Add Meta Pixel via GTM -- Standard engagement tracking
Next Steps
For general integration concepts, see the integrations overview.