Install Google Analytics 4 on Adobe Experience Manager | OpsBlu Docs

Install Google Analytics 4 on Adobe Experience Manager

Step-by-step GA4 installation and configuration guide for Adobe Experience Manager (AEM).

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

  1. Create Configuration

    Navigate to Tools > Cloud Services > Google Analytics

    Click Create and configure:

    • Title: GA4 Configuration
    • Measurement ID: G-XXXXXXXXXX
  2. Apply to Site

    In Site Console, select your site root and click Properties > Cloud Services

    Add the GA4 configuration.

AEM 6.5

  1. Navigate to Cloud Services

    Go to Tools > Deployment > Cloud Services

  2. Configure Google Analytics

    Find Google Analytics and click Configure Now

    Enter your Measurement ID.

  3. 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=*" }
}

Next Steps