— Adobe, AEP, Experience Platform, Data Collection, Web SDK, Target, at.js, alloy.js — 5 min read
This guide assumes familiarity with:
This guide requires access to the following Adobe solutions:
Experience Data Model (XDM) schemas are the building blocks, principles, and best practices for composing schemas in Adobe Experience Platform.
Platform Web SDK uses your schema to standardize your web event data, send it to the Platform Edge Network, and ultimately forward the data to any Experience Cloud applications configured in the datastream. This step is critical as it defines a standard data model required for ingesting customer experience data into Experience Platform and enables downstream services and applications built on these standards.
| :point_right: Checklist |
|---|
For Target, add the recommended pre-defined field groups for web data collection:
When defining your Schema, your data will be passed in the xdm object of payload and visible to all configured Adobe solution.
Alternatively, you can pass data to Target only via data object.
If Target custom parameters are moved to the XDM Schema, the path in the Audience builder will differ and needs to be updated for Audiences and Profile Scripts.
The XDM is serialized using dot notation (for example, web.webPageDetails.name).
| :point_right: Checklist |
|---|
The Adobe Experience Platform Identity Service sets a common visitor ID across all Adobe solutions in order to power Experience Cloud capabilities such as audience-sharing between solutions. You can also send your own customer IDs to the Service to enable cross-device targeting and integrations with other systems, such as your Customer Relationship Management (CRM) system.
| :point_right: Checklist |
|---|
targetMigrationEnabled and idMigrationEnabled parameters to true in the configuration. This helps where some pages still coninue to use Visitor.js and at.js.Code below sends identity data to AEP:
1alloy("sendEvent", {2 xdm: {3 "identityMap": {4 "ID_NAMESPACE": [5 {6 "id": "1234",7 "authenticatedState": "ambiguous",8 "primary": true9 }10 ]11 }12 }13});Target must be enabled in the datastream configuration before any Target activities can be delivered by the Platform Web SDK.
| :point_right: Checklist |
|---|
There are three supported ways to use Adobe Experience Platform Web SDK:
For technical clarification purposes, this guide references the CDN approach even though loading Web SDK via the Data Collection UI would be the most simple and recommended way.
Web SDK's alloy.js can be loaded synchronously or asynchronously.
| :point_right: Checklist |
|---|
1<script>2 !function(n,o){o.forEach(function(o){n[o]||((n.__alloyNS=n.__alloyNS||3 []).push(o),n[o]=function(){var u=arguments;return new Promise(4 function(i,l){n[o].q.push([i,l,u])})},n[o].q=[])})}5 (window,["alloy"]);6</script>7<script src="https://cdn1.adoberesources.net/alloy/2.6.4/alloy.min.js"></script>8<script>9alloy("configure", {10 "edgeConfigId": "ebebf826-a01f-4458-8cec-ef61de241c93",11 "orgId":"ADB3LETTERSANDNUMBERS@AdobeOrg",12 "prehidingStyle": "body { opacity: 0 !important }"13});14</script>It is recommended to set prehidingStyle to body { opacity: 0 !important }. This ensures that the whole page is pre-hidden during personalization call.
| :point_right: Checklist |
|---|
1<script>2 !function(n,o){o.forEach(function(o){n[o]||((n.__alloyNS=n.__alloyNS||3 []).push(o),n[o]=function(){var u=arguments;return new Promise(4 function(i,l){n[o].q.push([i,l,u])})},n[o].q=[])})}5 (window,["alloy"]);6 //Pre-hiding snippet7 !function(e,a,n,t){8 if (a) return;9 var i=e.head;if(i){10 var o=e.createElement("style");11 o.id="alloy-prehiding",o.innerText=n,i.appendChild(o),12 setTimeout(function(){o.parentNode&&o.parentNode.removeChild(o)},t)}}13 (document, document.location.href.indexOf("adobe_authoring_enabled") !== -1, "body { opacity: 0 !important }", 3000);14</script>15<script src="https://cdn1.adoberesources.net/alloy/2.6.4/alloy.min.js" async></script>16<script>17alloy("configure", {18 "edgeConfigId": "ebebf826-a01f-4458-8cec-ef61de241c93",19 "orgId":"ADB3LETTERSANDNUMBERS@AdobeOrg"20});21</script>renderDecisions to EventsTo enable Visual Experience Composer (VEC) experiences delivery, add renderDecisions: true to Web SDK event calls.
This also delivers custom code experiences set up via VEC.
There is no need to include decisionScopes: __view__ as it is automatically added by the Web SDK.
This __view__ scope is a signal to fetch all the page-load activities from Target and prefetch all views.
| :point_right: Checklist |
|---|
renderDecisions option for VEC-based Activities. 1alloy("sendEvent", {2 renderDecisions: true,3 xmd: {},4 data: {}5});decisionScopes to EventsSetting decisionScopes with a list of custom scopes (a.k.a. mboxes or Target Locations in older terms) retrieves qualifying Target Activities created with the form-based composer.
| :point_right: Checklist |
|---|
decisionScopes option for the Form-based Activities.1alloy("sendEvent", {2 decisionScopes: ["scope1", "scope2"],3 xmd: {},4 data: {}5});Note that custom scope experiences will need to be rendered manually using code. See more information below.
It is advised to move custom parameters to XDM Schema. This also changes how Audiences work because the path to a custom parameter from XDM becomes different (eg., web.webPageDetails.name).
However, free form data is also supported inside data.__adobe.target object:
| :point_right: Checklist |
|---|
xdm or data objects as needed. 1alloy("sendEvent", {2 renderDecisions: true,3 xdm: {},4 data: {5 __adobe: {6 target: {7 "profile.gender": "female",8 "entity.id": 1234,9 "user.categoryId": "outerwear,jackets",10 "foo": "bar"11 }12 }13 }14 });For response tokens, subscribe to alloy.js sendEvent promise, iterate through propositions
and extract the details from items -> meta.
| :point_right: Checklist |
|---|
1alloy("sendEvent", {2 renderDecisions: true,3 xdm: {},4 data: {}5 }).then(function(result) {6 handleResponseTokens(result); //custom method that processes response tokens, see definition example below7 });Recommnedations entity data is sent as custom parameters in data object.
| :point_right: Checklist |
|---|
entity.id and any other required entity. data to data.__adobe.target object.1alloy("sendEvent", {2 renderDecisions: true,3 data: {4 __adobe: {5 target: {6 "entity.id": "123",7 "entity.genre": "Drama"8 }9 }10 }11 });On the first sendEvent() call, all XDM Views that should be rendered to the end-user will be fetched and cached. Subsequent sendEvent() calls with XDM Views passed in will be read from the cache and rendered without a server call.
| :point_right: Checklist |
|---|
1alloy("sendEvent", {2 "renderDecisions": true, //<--3 "xdm": {4 "web": {5 "webPageDetails": {6 "viewName":"home" //<--7 }8 }9 }10 });There may be a case where we want to prefetch content and display on demand
1alloy("sendEvent", {2 renderDecisions: false,3}).then(function(result) {4 return alloy("applyPropositions", {5 propositions: retrievedPropositions,6 metadata: getMetadata(scopes)7 }).then(function(applyPropositionsResult) {8 alloy("sendEvent", { // Send the display notifications9 xdm: {10 eventType: "decisioning.propositionDisplay",11 _experience: {12 decisioning: {13 propositions: applyPropositionsResult.propositions14 }15 }16 }17 });18 handleResponseTokens(applyPropositionsResult);19 });20});21
22function getMetadata(scopes){23 let metadata = {};24 scopes.forEach(function(scope){25 metadata[scope] = {26 selector: "head",27 actionType: "appendHtml"28 }29 });30 return metadata;31}If decisions are not set to render, they are pre-fetched and we would need to apply content manually and send a display notification event.
| :point_right: Checklist |
|---|
1function getContentForPropositions(data, props){2 let content = [];3 if (data.propositions) {4 data.propositions.forEach(proposition => {5 proposition.items.forEach(item => {6 if (item.schema === "https://ns.adobe.com/personalization/html-content-item") {7 content.push({8 scope: "",9 });10 }11 });12 });13
14 }15 return content;16}17
18alloy("sendEvent", {19 xdm: {...},20 decisionScopes: ["__view__"]21 }).then(function(result) {22 getContentForPropositions(result, ["myScope1"]);23 if (result.propositions) {24 result.propositions.forEach(proposition => {25 proposition.items.forEach(item => {26 if (item.schema === HTML_SCHEMA) {27 // manually apply offer28 document.getElementById("form-based-offer-container").innerHTML =29 item.data.content;30 const executedPropositions = [31 {32 id: proposition.id,33 scope: proposition.scope,34 scopeDetails: proposition.scopeDetails35 }36 ];37 // manually send the display notification event, so that Target/Analytics impressions are increased38 alloy("sendEvent",{39 "xdm": {40 "eventType": "decisioning.propositionDisplay",41 "_experience": {42 "decisioning": {43 "propositions": executedPropositions44 }45 }46 }47 });48 }49 });50 });51 }52 });You can prefetch scopes, send notifications, track events and user actions by calling the sendEvent command, populating the _experience.decisioning.propositions XDM fieldgroup, and setting the eventType to one of 2 values:
decisioning.propositionDisplay: Signals the rendering of the Target Activity.decisioning.propositionInteract: Signals a user interaction with the Activity, like a mouse click.Track a decisioning.propositionDisplay event after rendering an Activity
1alloy("sendEvent", {2 xdm: {},3 decisionScopes: ['discount']4}).then(function(result) {5 var propositions = result.propositions;6 var discountProposition;7 if (propositions) {8 // Find the discount proposition, if it exists.9 for (var i = 0; i < propositions.length; i++) {10 var proposition = propositions[i];11 if (proposition.scope === "discount") {12 discountProposition = proposition;13 break;14 }15 }16 }17 if (discountProposition) {18 // Find the item from proposition that should be rendered.19 // Rather than assuming there a single item that has HTML20 // content, find the first item whose schema indicates21 // it contains HTML content.22 for (var j = 0; j < discountProposition.items.length; j++) {23 var discountPropositionItem = discountProposition.items[i];24 if (discountPropositionItem.schema === "https://ns.adobe.com/personalization/html-content-item") {25 var discountHtml = discountPropositionItem.data.content;26 // Render the content27 var dailySpecialElement = document.getElementById("daily-special");28 dailySpecialElement.innerHTML = discountHtml;29
30 // For this example, we assume there is only a single place to update in the HTML.31 break;32 }33 }34 // Send a "decisioning.propositionDisplay" event signaling that the proposition has been rendered.35 alloy("sendEvent", {36 xdm: {37 eventType: "decisioning.propositionDisplay",38 _experience: {39 decisioning: {40 propositions: [41 {42 id: discountProposition.id,43 scope: discountProposition.scope,44 scopeDetails: discountProposition.scopeDetails45 }46 ]47 }48 }49 }50 });51 }52});Track a decisioning.propositionInteract event after a click metric occurs
1alloy("sendEvent", {2 xdm: { ...},3 decisionScopes: ["hero-banner"]4}).then(function (result) {5 var propositions = result.propositions;6
7 if (propositions) {8 // Find the discount proposition, if it exists.9 for (var i = 0; i < propositions.length; i++) {10 var proposition = propositions[i];11 for (var j = 0; j < proposition.items.length; j++) {12 var item = proposition.items[j];13
14 if (item.schema === "https://ns.adobe.com/personalization/measurement") {15 // add metric to the DOM element16 const button = document.getElementById("form-based-click-metric");17
18 button.addEventListener("click", event => {19 const executedPropositions = [20 {21 id: proposition.id,22 scope: proposition.scope,23 scopeDetails: proposition.scopeDetails24 }25 ];26 // send the click track event27 alloy("sendEvent", {28 "xdm": {29 "eventType": "decisioning.propositionInteract",30 "_experience": {31 "decisioning": {32 "propositions": executedPropositions33 }34 }35 }36 });37 });38 }39 }40 }41 }42});1alloy("sendEvent", {2 "documentUnloading": true, // sends as a beacon3 "xdm": {4 "eventType": "commerce.purchases",5 "commerce": {6 "order": {7 "purchaseID": "a8g784hjq1mnp3",8 "purchaseOrderNumber": "VAU3123",9 "currencyCode": "USD",10 "priceTotal": 999.9811 }12 }13 }14});mbox3rdpartyId to Target| :point_right: Checklist |
|---|
1alloy("sendEvent", {2 xdm: {3 "identityMap": {4 "ID_NAMESPACE": [5 {6 "id": "1234",7 "authenticatedState": "authenticated"8 }9 ]10 }11 }12 });The Adobe Experience Platform Web SDK supports two types of Analytics logging for Analytics for Target (A4T) use cases:
| :point_right: Checklist |
|---|
If you want to add, remove, or modify fields from the event globally, you can configure an onBeforeEventSend callback. This callback is called every time an event is sent. This callback is passed in an event object with an xdm field. Modify content.xdm to change the data that is sent with the event.
1alloy("configure", {2 "edgeConfigId": "ebebf826-a01f-4458-8cec-ef61de241c93",3 "orgId": "ADB3LETTERSANDNUMBERS@AdobeOrg",4 "onBeforeEventSend": function(content) {5 // ignores events from bots6 if (MyBotDetector.isABot()) {7 return false;8 }9 // Change existing values10 content.xdm.web.webPageDetails.URL = xdm.web.webPageDetails.URL.toLowerCase();11 // Remove existing values12 delete content.xdm.web.webReferrer.URL;13 // Or add new values14 content.xdm._adb3lettersandnumbers.mycustomkey = "value";15 //sets a query parameter in XDM16 const queryString = window.location.search;17 const urlParams = new URLSearchParams(queryString);18 content.xdm.marketing.trackingCode = urlParams.get('cid')19 }20});Remove at.js JavaScript library if previously used and any references to at.js API functions.
| :point_right: Checklist |
|---|
at.js codeadobe.targe.getOffers and/or adobe.targe.getOffer if anyadobe.target.trackEvent if anywindow.targetGlobalSettings if anywindow.targetPageParams and/or window.targetPageParamsAll if any