đźš§ This is an early access technology and is still heavily in development. Reach out to us over Slack before using it.
The AEM Marketing Technology plugin helps you quickly set up a complete MarTech stack 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
This plugin optimizes the integration of Adobe's marketing technology stack by moving away from a monolithic Adobe Launch script. Instead, it "decomposes" the key components—Adobe Experience Platform WebSDK and Adobe Client Data Layer (ACDL)—and loads them at the optimal time during the page load lifecycle.
This is achieved through a phased approach that aligns with modern web performance best practices:
- Eager Phase: Handles critical tasks like personalization during the initial page load to prevent content flicker, ensuring a smooth user experience.
- Lazy Phase: Loads analytics and the data layer after the main content has rendered, capturing metrics without delaying the Largest Contentful Paint (LCP).
- Delayed Phase: Executes non-essential scripts, like third-party tags via Launch, after the page is fully interactive.
By instrumenting the core Adobe libraries directly but in a controlled sequence, this plugin minimizes performance impact while retaining the full power of the Adobe Experience Cloud.
The AEM MarTech plugin is essentially a wrapper around the Adobe Experience Platform WebSDK and the Adobe Client Data Layer that seamlessly integrates your website with:
- 🎯 Adobe Target or Adobe Journey Optimizer: to personalize your pages
- 📊 Adobe Analytics: to track customer journey data
- đźš© Adobe Experience Platform Tags (a.k.a. Launch): to track your custom events
Its key differentiators are:
- 🌍 Experience Platform enabled: The library fully integrates with our main Adobe Experience Platform and all the services of our ecosystem.
- 🚀 Extremely fast: The library is optimized to reduce load delay, TBT, and CLS, and has a minimal impact on your Core Web Vitals.
- 👤 Privacy-first: The library does not track end-users by default and can be easily integrated with your preferred consent management system.
- 🔬 Speculative prerender aware: The library supports speculative prerendering and won't fire Analytics events (and artificially inflate your page views) until the page is actually viewed.
You need access to:
- Adobe Experience Platform (no full license needed, just basic permissions for data collection)
- Adobe Analytics
- Adobe Target or Adobe Journey Optimizer
Before instrumenting your project, you must configure your Adobe Experience Platform Tags (Launch) container correctly for use with this plugin.
- DO NOT include the following extensions in your Launch container:
Adobe Experience Platform Web SDK
Adobe Analytics
Adobe Target
This plugin handles the initialization of these components directly to optimize performance. Including them in Launch will lead to conflicts and potential data duplication.
- DO ensure you have the
Adobe Client Data Layer
extension configured.
pending
to comply with privacy regulations. Overriding this behavior to grant consent by default (e.g., setting it to in
) without explicit user agreement may have significant legal implications under regulations like GDPR and CCPA. We strongly advise consulting with your legal team before altering the default consent handling.
We also recommend using a proper consent management system.
We have a comprehensive tutorial on Experience League, or you can just follow the steps below.
Add the plugin to your AEM project by running:
git subtree add --squash --prefix plugins/martech [email protected]:adobe-rnd/aem-martech.git main
If you later want to pull the latest changes and update your local copy of the plugin:
git subtree pull --squash --prefix plugins/martech [email protected]:adobe-rnd/aem-martech.git main
If the subtree pull
command fails, you can delete the plugins/martech
folder and re-add it using the git subtree add
command.
If you use a linter, make sure to ignore minified files in your .eslintignore
:
*.min.js
To connect and configure the plugin, you'll need to edit your project's head.html
and scripts.js
.
Add the following lines at the end of your head.html
to speed up page load:
<link rel="preload" as="script" crossorigin="anonymous" href="/plugins/martech/src/index.js"/>
<link rel="preload" as="script" crossorigin="anonymous" href="/plugins/martech/src/alloy.min.js"/>
<link rel="preconnect" href="https://edge.adobedc.net"/>
<!-- Change to adobedc.demdex.net if you enable third-party cookies -->
Import the necessary methods at the top of your scripts.js
file:
import {
initMartech,
updateUserConsent,
martechEager,
martechLazy,
martechDelayed,
} from '../plugins/martech/src/index.js';
Call initMartech
at the top of the loadEager
method in your scripts.js
. This function takes two arguments: the WebSDK configuration and the library-specific configuration.
/**
* Loads everything needed to get to LCP.
*/
async function loadEager(doc) {
// Hook in your consent check to determine if personalization can run.
const isConsentGiven = true; /* your consent logic here */
const martechLoadedPromise = initMartech(
// 1. WebSDK Configuration
// Docs: https://experienceleague.adobe.com/en/docs/experience-platform/web-sdk/commands/configure/overview#configure-js
{
datastreamId: /* your datastream id here */,
orgId: /* your IMS org id here */,
// The `debugEnabled` flag is automatically set to true on localhost and .page URLs.
// The `defaultConsent` is automatically set to "pending".
onBeforeEventSend: (payload) => {
// This callback allows you to modify the payload before it's sent.
// Return false to prevent the event from being sent.
},
edgeConfigOverrides: {
// Optional datastream overrides for different environments.
},
},
// 2. Library Configuration
{
personalization: !!getMetadata('target') && isConsentGiven,
launchUrls: [/* your Launch script URLs here */],
// See the API Reference for all available options.
},
);
// ... rest of loadEager
}
Adjust your loadEager
method to wait for the MarTech promise to resolve before rendering the main content. This prevents content flicker from personalized content.
// ... inside loadEager
if (main) {
decorateMain(main);
document.body.classList.add('appear');
await Promise.all([
martechLoadedPromise.then(martechEager),
loadSection(main.querySelector('.section'), waitForFirstImage),
]);
}
Add a reference to martechLazy
just after the loadFooter(…);
call in your loadLazy
method:
async function loadLazy(doc) {
// ...
loadFooter(doc.querySelector('footer'));
await martechLazy();
// ...
}
Add a reference to martechDelayed
in your loadDelayed
method:
function loadDelayed() {
window.setTimeout(() => {
martechDelayed();
import('./delayed.js');
}, 3000);
}
The plugin exports several functions to interact with the marketing stack.
Initializes the library. This should be called once in loadEager
.
webSDKConfig
{Object}
: Configuration for the Adobe Experience Platform WebSDK. RequiresdatastreamId
andorgId
.martechConfig
{Object}
: Optional configuration for this library.analytics
{Boolean}
: Enable analytics. Default:true
.alloyInstanceName
{String}
: Global name for the alloy instance. Default:'alloy'
.dataLayer
{Boolean}
: Enable Adobe Client Data Layer (ACDL). Default:true
.dataLayerInstanceName
{String}
: Global name for the ACDL instance. Default:'adobeDataLayer'
.includeDataLayerState
{Boolean}
: Include the full data layer state on every event. Default:true
.launchUrls
{String[]}
: Array of Launch script URLs to load.personalization
{Boolean}
: Enable personalization. Default:true
.performanceOptimized
{Boolean}
: Use aggressive performance optimizations. Default:true
.personalizationTimeout
{Number}
: Timeout in ms for personalization. Default:1000
.shouldProcessEvent
{Function}
: A function that receives a data layer event payload and returnsfalse
to prevent it from being sent.
Sets user consent based on the IAB TCF 2.0 standard.
consent
{Object}
: An object detailing user consent choices (collect
,marketing
,personalize
,share
).
Pushes a generic payload to the Adobe Client Data Layer.
payload
{Object}
: The data object to push.
A helper for pushing a standardized event to the data layer.
event
{String}
: The name of the event.xdm
{Object}
: The XDM data object.data
{Object}
: Additional data mappings.configOverrides
{Object}
: Optional Edge configuration overrides.
A proxy for the alloy('sendEvent', ...)
command to send a raw event.
payload
{Object}
: The full event payload for the WebSDK.
A helper for sending an analytics event directly.
xdmData
{Object}
: The XDM data object.dataMapping
{Object}
: Data mappings for the event.configOverrides
{Object}
: Optional Edge configuration overrides.
Initializes RUM (Real User Monitoring) tracking.
sampleRUM
{Object}
: The RUM sampling object.options
{Object}
: Optional configuration.
- Returns
{Boolean}
:true
if personalization is configured and enabled.
Connect your consent management system (CMS) to track user consent. Call updateUserConsent
when your CMS sends a consent event.
Example for the AEM Consent Banner Block:
function consentEventHandler(ev) {
const collect = ev.detail.categories.includes('CC_ANALYTICS');
const marketing = ev.detail.categories.includes('CC_MARKETING');
const personalize = ev.detail.categories.includes('CC_TARGETING');
const share = ev.detail.categories.includes('CC_SHARING');
updateUserConsent({ collect, marketing, personalize, share });
}
window.addEventListener('consent', consentEventHandler);
window.addEventListener('consent-updated', consentEventHandler);
Example for OneTrust:
function consentEventHandler(ev) {
const groups = ev.detail;
const collect = groups.includes('C0002'); // Performance Cookies
const personalize = groups.includes('C0003'); // Functional Cookies
const share = groups.includes('C0008'); // Targeted Advertising
updateUserConsent({ collect, personalize, share });
}
window.addEventListener('consent.onetrust', consentEventHandler);
For Single Page Applications or pages with dynamic content, you may need to manage personalization manually to avoid concurrency issues.
- Follow the SPA approach and define views in Adobe Target.
- Import the helper methods in your components:
import { isPersonalizationEnabled, getPersonalizationForView, applyPersonalization, } from '../plugins/martech/src/index.js';
- Fetch the personalization for the view when it renders:
if (isPersonalizationEnabled()) { await getPersonalizationForView('my-view-name'); }
- Apply the personalization every time there is a significant DOM update:
applyPersonalization('my-view-name');
A default Launch implementation can negatively impact Core Web Vitals. Our approach optimizes for performance by loading components intelligently.
Deferring the entire script introduces content flickering for personalization use cases and can lead to missed analytics events from users who bounce early.
git subtree
is used to vendor the plugin's code directly into your project. This approach avoids the need for a package manager like npm
and the complexities of git submodule
, providing a simple way to pull in updates while keeping the code self-contained within your repository.
This library uses the same official Adobe Experience Platform WebSDK and Adobe Client Data Layer as Launch. We are building on documented Adobe APIs, such as top and bottom of page events, to ensure compatibility.
Since some logic is moved from the Launch UI into your project's code, not all features can be managed from the Launch UI. We recommend a baseline of the Core, ACDL, and AA via AEP Web SDK extensions in your Launch container.
This plugin includes the following core libraries:
- Adobe Experience Platform WebSDK:
v2.28.0
(alloy.min.js
) - Adobe Client Data Layer:
v2.0.2
(acdl.min.js
)
This project manages the on-page, self-hosted implementation of the Adobe Experience Platform Web SDK (alloy.js
). When used with the AEP Web SDK extension in Adobe Launch, you must enable a specific setting to avoid conflicts and ensure optimal performance.
In the AEP Web SDK extension settings within Adobe Launch, enable the "Use existing alloy.js instance" option.
When this is checked:
- The extension will not bundle a second copy of
alloy.js
into the Launch library. - The extension will not re-configure the SDK. It will use the instance that has already been configured on the page by this project.
- All configuration settings within the Launch UI (e.g., Datastream ID, Org ID, Default Consent) will be ignored at runtime.
All SDK configuration must be handled within the scripts managed by this project.
All configuration is set within the alloy("configure", { ... })
command. For a complete and official reference of all available options, please see the Adobe Experience League documentation, which is the source of truth.
- Primary Documentation: Configuring the Web SDK
The table below summarizes the most common settings that are now managed here instead of in the Launch UI, with direct links to their respective documentation pages.
Option | Description | Example Value |
---|---|---|
datastreamId |
The ID of the datastream to send data to. | 'YOUR_DATASTREAM_ID' |
orgId |
Your Experience Cloud Organization ID. | 'YOUR_ORG_ID@AdobeOrg' |
edgeDomain |
The first-party domain (CNAME) for interacting with Adobe services. | 'edge.your-domain.com' |
defaultConsent |
The default consent level (in , out , or pending ). |
'pending' |
idMigrationEnabled |
Enables migration of visitor IDs from legacy Adobe libraries. | true |
targetMigrationEnabled |
Ensures visitor profile is maintained when migrating from at.js . |
true |
onBeforeEventSend |
A callback function to modify event data before it's sent. | (options) => { /* logic */ } |
Here is an example of how you might configure the SDK in your script:
alloy("configure", {
// --- Required ---
datastreamId: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
orgId: "XXXXXXXXXXXXXXX@AdobeOrg",
// --- Recommended ---
edgeDomain: "edge.your-site.com",
defaultConsent: "pending",
// --- Optional: Migration & Callbacks ---
idMigrationEnabled: true,
targetMigrationEnabled: true,
onBeforeEventSend: function(options) {
// This callback allows for last-minute modification
// of the XDM payload before it is sent.
// For example, adding a custom context:
options.xdm.customContext = "some value";
}
});