This guide covers implementing GA4 on Adobe Experience Manager (AEM) for both AEM as a Cloud Service and AEM 6.5.
Method Comparison
| Method | Difficulty | Flexibility | Recommended For |
|---|---|---|---|
| Cloud Service Config | Easy | Low | Quick setup |
| HTL Component | Medium | Medium | Standard sites |
| Client Library | Medium | High | Most implementations |
| Google Tag Manager | Medium | Highest | Complex tracking needs |
Method 1: Cloud Service Configuration
AEM as a Cloud Service
Create Configuration
Navigate to Tools > Cloud Services > Google Analytics
Click Create and configure:
- Title: GA4 Configuration
- Measurement ID: G-XXXXXXXXXX
Apply to Site
In Site Console, select your site root and click Properties > Cloud Services
Add the GA4 configuration.
AEM 6.5
Navigate to Cloud Services
Go to Tools > Deployment > Cloud Services
Configure Google Analytics
Find Google Analytics and click Configure Now
Enter your Measurement ID.
Apply to Pages
Edit page properties and add the cloud service configuration.
Method 2: HTL Component Integration
Create Tracking Component
/apps/mysite/components/analytics/ga4/ga4.html
<sly data-sly-use.ga4="${'com.mysite.core.models.GA4Model'}"
data-sly-test="${ga4.enabled}">
<!-- Google Analytics 4 -->
<script async src="https://www.googletagmanager.com/gtag/js?id=${ga4.measurementId}"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '${ga4.measurementId}', {
'page_title': '${ga4.pageTitle}',
'page_location': '${ga4.pageUrl}',
'debug_mode': ${ga4.debugMode}
});
</script>
</sly>
Create Sling Model
GA4Model.java
package com.mysite.core.models;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.injectorspecific.OSGiService;
import org.apache.sling.models.annotations.injectorspecific.ScriptVariable;
import com.day.cq.wcm.api.Page;
@Model(adaptables = SlingHttpServletRequest.class)
public class GA4Model {
@OSGiService
private GA4ConfigService configService;
@ScriptVariable
private Page currentPage;
public boolean isEnabled() {
return configService.isEnabled();
}
public String getMeasurementId() {
return configService.getMeasurementId();
}
public String getPageTitle() {
return currentPage.getTitle();
}
public String getPageUrl() {
return currentPage.getPath() + ".html";
}
public boolean isDebugMode() {
return configService.isDebugMode();
}
}
Create OSGi Configuration Service
GA4ConfigService.java
package com.mysite.core.services;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.metatype.annotations.AttributeDefinition;
import org.osgi.service.metatype.annotations.Designate;
import org.osgi.service.metatype.annotations.ObjectClassDefinition;
@Component(service = GA4ConfigService.class)
@Designate(ocd = GA4ConfigService.Config.class)
public class GA4ConfigService {
@ObjectClassDefinition(name = "GA4 Configuration")
public @interface Config {
@AttributeDefinition(name = "Enabled")
boolean enabled() default true;
@AttributeDefinition(name = "Measurement ID")
String measurementId() default "";
@AttributeDefinition(name = "Debug Mode")
boolean debugMode() default false;
}
private Config config;
@Activate
protected void activate(Config config) {
this.config = config;
}
public boolean isEnabled() {
return config.enabled();
}
public String getMeasurementId() {
return config.measurementId();
}
public boolean isDebugMode() {
return config.debugMode();
}
}
Include in Page Template
customheaderlibs.html
<sly data-sly-include="/apps/mysite/components/analytics/ga4/ga4.html"/>
Method 3: Client Library Approach
Create Client Library
/apps/mysite/clientlibs/analytics/.content.xml
<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:jcr="http://www.jcp.org/jcr/1.0"
jcr:primaryType="cq:ClientLibraryFolder"
categories="[mysite.analytics]"
dependencies="[mysite.base]"/>
/apps/mysite/clientlibs/analytics/js.txt
#base=js
ga4-init.js
ga4-events.js
/apps/mysite/clientlibs/analytics/js/ga4-init.js
(function() {
'use strict';
var GA4_ID = window.mySiteConfig?.ga4MeasurementId || '';
if (!GA4_ID) {
console.warn('GA4 Measurement ID not configured');
return;
}
// Load gtag.js
var script = document.createElement('script');
script.async = true;
script.src = 'https://www.googletagmanager.com/gtag/js?id=' + GA4_ID;
document.head.appendChild(script);
// Initialize
window.dataLayer = window.dataLayer || [];
function gtag() { dataLayer.push(arguments); }
window.gtag = gtag;
gtag('js', new Date());
gtag('config', GA4_ID, {
send_page_view: false
});
// Track initial page view
gtag('event', 'page_view', {
page_title: document.title,
page_location: window.location.href
});
})();
Include Client Library
In your page component:
<sly data-sly-use.clientlib="/libs/granite/sightly/templates/clientlib.html">
<sly data-sly-call="${clientlib.js @ categories='mysite.analytics'}"/>
</sly>
Verification
Check Author vs Publish
Tracking should only fire on Publish:
// In Sling Model
@ScriptVariable
private SlingHttpServletRequest request;
public boolean isPublish() {
return WCMMode.fromRequest(request) == WCMMode.DISABLED;
}
<sly data-sly-test="${ga4.enabled && ga4.publish}">
<!-- GA4 code here -->
</sly>
Debug Mode
Enable GA4 DebugView:
gtag('config', 'G-XXXXXXXXXX', {
'debug_mode': true
});
Dispatcher Caching
Ensure Dispatcher doesn't cache tracking parameters:
dispatcher.any
/rules {
/0001 { /type "deny" /glob "*" }
/0002 { /type "allow" /glob "*.html" }
# Don't cache pages with tracking params
/0003 { /type "deny" /glob "*?*_ga=*" }
}