🚧 This is an early access technology and is still heavily in development. Reach out to us over Slack before using it.
he AEM Marketing Technology plugin helps you quickly set up a MarTech stack based on Google Analytics (GA) & Google Tag Manager (GTM) for your AEM project. It is currently available to customers in collaboration with AEM Engineering via co-innovation VIP Projects. To implement your use cases, please reach out to the AEM Engineering team in the Slack channel dedicated to your project.
- AEM Edge Delivery Services Marketing Technology - GA/GTM
This plugin optimizes the integration of Google's Analytics and Tag Manager tools by moving away from one (or more) large GTM containers. Rather than intializing the loading of several Google libraries & containers via a single GTM request, each is loaded at the ideal time during the AEM Edge Delivery Services render lifecycle.
This is accomplished using a phase-based solution, aligning with the Edge Delivery Services lifecycle phases:
- Eager: Handles initializing Google Analytics, including fetching the GA4 script.
- Lazy: Loads the first set of the GTM containers that process events, after the main Core Web Vitals (CWV) elements (such as Largest Contentful Paint, LCP) have rendered.
- Delayed: Loads any other GTM containers that process non-critical events, such as third-party tags.
By processing the libraries & containers in this sequence, this plugin minimizes the impact to performance, while supporting critical-path event handling.
The AEM MarTech plugin is essentially a wrapper around the GA4 and GTM Libraries, and that can seamlessley integrate your website with:
- 📊 Google Analytics: to track customer journey data
- 🏷️ Google Tag Manager: to track your custom events
It's key differentiators are:
- 🚀 extremely fast: the library is optimized to reduce load delay, TBT and CLS, and has minimal impact on your Core Web Vitals
- 👤 privacy-first: the library does not track end users by default, and can easily be integrated with your preferred consent management system to open up more advanced use cases
You need to have access to:
- Google Analytics
- Google Tag Manager
You need to have preconfigured:
- A data stream in Google Analtyics
- A Google Tag Manager Workspace
denied. Setting user consent to granted overrides this behavior to grant consent by default (i.e. without explicit end user agreement). Customers should consult with their own legal counsel to understand their privacy obligations and the appropriate use and configuration of this library. We also recommend using a consent management system.
Add the plugin to your AEM project by running:
git subtree add --squash --prefix plugins/gtm-martech [email protected]:adobe-rnd/aem-gtm-martech.git mainIf you later want to pull the latest changes and update your local copy of the plugin
git subtree pull --squash --prefix plugins/gtm-martech [email protected]:adobe-rnd/aem-gtm-martech.git mainIf you prefer using https links you'd replace [email protected]:adobe-rnd/aem-gtm-martech.git in the above commands by https://github.com/adobe-rnd/aem-gtm-martech.git.
If the subtree pull command is failing with an error like:
fatal: can't squash-merge: 'plugins/martech' was never added
you can just delete the folder and re-add the plugin via the git subtree add command above.
If you use some ELint at the project level (or equivalent), make sure to update ignore plugin files in your .eslintignore:
plugins/gtm-martech/*
To properly connect and configure the plugin for your project, you'll need to edit both the head.html and scripts.js in your AEM project and add the following:
Add the following lines at the end of your head.html, to speed up the page load:
<script nonce="aem" src="/scripts/gtm-martech.js" type="module"></script>
<link rel="preload" as="script" crossorigin="anonymous" href="/plugins/gtm-martech/src/index.js"/>
<link rel="preconnect" href="https://www.googletagmanager.com"/>Add a script to encompass the intialization of the plugin. This will can be used to import in any context necessary to peform the different operations.
This is a template file for use, suggested file name is "gtm-martech.js".
// eslint-disable-next-line import/no-relative-packages
import GtmMartech from '../plugins/gtm-martech/src/index.js';
// For DA Preview support.
const disabled = window.location.search.includes('martech=off');
const martech = new GtmMartech({
analytics: !disabled,
tags: [/* One or more GA4 Measumrent Ids */],
containers: {
lazy: [/* Zero or more GTM Container Ids to load during Lazy Phase */],
delayed: [/* Zero or more GTM Container Ids to load during Delayed Phase */],
},
pageMetadata: { /* Metadata to pass on during the intializaton of the GA4 tag */ },
consent: !disabled,
consentCallback: /* Function that handles consent processing, if consent is enabled, this must be specified */,
decorateCallback: /* Function to call on each found or loaded Section/Block */,
});
export default martechImport the plugin at the top of your scripts.js file:
import gtmMartech from './gtm-martech.js';Update the loadEager function in your scripts.js file, to call plugin's eager phase:
async function loadEager(doc) {
…
if (main) {
decorateMain(main);
doc.body.classList.add('appear');
await Promise.all([
gtmMartech.eager(),
loadSection(main.querySelector('.section'), waitForFirstImage),
]);
}
…
}Note that the eager() call is asynchronous, therefore can be added before or after the LCP section. We do recommend awaiting it, as future updates (such as personalization support) may require it.
Update the loadLazy function, to call plugin's lazy phase.
async function loadLazy(doc) {
…
await loadSections(main);
await gtmMartech.lazy();
…
}Note that the lazy() function must be awaited independently for correct handling of the decorateCallback. Do not perform other processing (e.g. section loading or dynamic block insertions) simultaneously using Promise.all(), otherwise correct decoration may not occur.
Update the loadDelayed function to call the plugin's delayed phase, after a timeout. If there are no delayed GTM Containers, this step is not necessary.
function loadDelayed() {
…
window.setTimeout(gtmMartech.delayed, 1000);
window.setTimeout(() => import('./delayed.js'), 3000);
…
}If consent is enabled, implement a function to check consent. If the Consent Managment Provider (CMP) does not automatically update Google's consent store, resolve to a state based on user selections. The data structure must conform to the expected Google consent types
async function checkConsent() {
return new Promise((resolve) => {
// Perform the Consent popup check here.
// Not using a CMP, therefore we must resolve to the desired Consent State.
resolve({
ad_storage: /* granted or denied */,
ad_user_data: /* granted or denied */,
ad_personalization: /* granted or denied */,
analytics_storage: /* granted or denied */,
functionality_storage: /* granted or denied */,
personalization_storage: /* granted or denied */,
security_storage: /* granted or denied */,
});
});
}If desired, implement a decorateCallback to add event processing to Sections or Blocks. This function makes a best attempt at finding all Sections & Blocks that are loaded. Each will be passed to the specified function. If some elements are not processed, we recommend you manually monitor and decorate missed elements.
function decorateEvents(el) {
if (el.classList.contains('block')) {
// Check type of block and add DataLayer pushes as desired.
} else if (el.classList.contains('section')) {
// Do something on each section to push to DataLayer
}
}This plugin exports several functions to manage the marketing libraries:
Initializes the plugin. This should be called in the scripts.js outside any lifecycle phase.
martechConfig{Object}: Configuration for this plugin.analytics{Boolean}: Enable analytics. Default:true.dataLayerInstanceName{String}: Global name for the GTM Data Layer instance. Default:'gtmDataLayer'.tags{String[]}: Array of GA4 Measurement Ids to load.containers{Object|String[]|String}: Configuration for GTM Containers, or an Array of GTM Container Ids to load during the lazy phase, or a single GTM Container Id to load during the lazy phase.lazy{String[]}: Array of GTM Container Ids to load in the lazy phase.delayed{String[]}: Array of GTM Container Ids to load in the delayed phase.
pageMetadata{Object}: A set of key-value pairs to pass to the GA4 tag initializer.consent{Boolean}: Enable consent. Default:trueconsentCallback{Function|undefined}: A function that will perform consent validation. Returns a promise that resolves to an object, which will be passed to the GA for update.decorateCallback{Function}: A function that can decorate HTML elements for events. Passed all sections & blocks found.
Performs the eager phase operations for the plugin.
Performs the lazy phase operations for the plugin.
Performs the delayed phase operations for the plugin.
Pushes a generic payload to the Adobe Client Data Layer.
payload{Object}: The data object to push.
Updates the consent according to the []gtag.js implementation](https://developers.google.com/tag-platform/security/guides/consent?consentmode=advanced#implementation_example)
consent{Object}: An object detailing user consent choices.
On initialization, this Plugin will define the window.gtag function according to the GA Documentation.
An example of this plugin in use can be found on the AEM GTM Martech demo site.
All GA4 scripts are imported to the page during the Eager Phase. During this process a page_view event occurs, which includes any custom metadata provided to during plugin initialization. This page_view event will be dispatched as soon as the Google library decides it should be sent, regardless of when any other phase's GTM libraries are loaded.