Complete implementation reference for Adobe Analytics via Launch with AppMeasurement.
| Setting | Value | Docs |
|---|---|---|
| Report Suite ID | Production + Staging (use environment-specific) | Report suites |
| Tracking Server | Your tracking server | s.trackingServer |
| Character Set | UTF-8 | s.charSet |
| Currency Code | USD | s.currencyCode |
| Setting | Value | Why | Docs |
|---|---|---|---|
| Enable Activity Map | ON | Auto-tracks every click with link text, region, and page | Activity Map |
| Track download links | ON | Auto-tracks file downloads | s.trackDownloadLinks |
| Track outbound links | ON | Auto-tracks exit links | s.trackExternalLinks |
| Download extensions | doc,docx,eps,jpg,png,pdf,pptx,svg,xls,xlsx,zip | File types to track as downloads | s.linkDownloadFileTypes |
Activity Map is built into AppMeasurement. When enabled, it automatically fires an s.tl() server call on every click — it's not just a visual heatmap overlay. Every click sends real data to your report suite.
Each click fires s.tl() with these context data variables:
// These go out automatically with every click — no rules needed
c.a.activitymap.page = "Home Page" // s.pageName value
c.a.activitymap.link = "Sign Up Now" // link text / aria-label
c.a.activitymap.region = "hero-section" // parent container ID
c.a.activitymap.pageIDType = "1" // page ID type
You can see these in the Adobe Debugger network tab on every click.
| Setting | Where | Must be | Docs |
|---|---|---|---|
| Enable ClickMap / Activity Map | AA Extension > Link Tracking | ON | Getting started |
| Enable Link Tracking | AA Extension > Link Tracking | ON | trackExternalLinks |
| Track download links | AA Extension > Link Tracking | ON | trackDownloadLinks |
| Track outbound links | AA Extension > Link Tracking | ON | trackExternalLinks |
s.trackInlineStats is NOT a checkbox in the Launch extension UI. It's a code-level setting. Two ways to set it depending on your access level:
AA Extension > Configure Tracker Using Custom Code:
// Enable Activity Map click data collection
s.trackInlineStats = true;
// Tell AppMeasurement your domain so it knows internal vs exit links
s.linkInternalFilters = location.hostname;
When the AA extension settings cannot be edited directly, create a dedicated config rule instead:
| Component | Configuration | Docs |
|---|---|---|
| Rule name | Global Config | Rules |
| Event | Core > Library Loaded (Page Top) | Core events |
| Order | 1 (lowest fires first — must run before all other rules) | Rule ordering |
| Condition | None | |
| Action | Adobe Analytics > Custom Code |
// Global config — runs at Library Loaded, order 1 (before all other rules)
// Alternative when extension custom code editor is not accessible
s.trackInlineStats = true;
s.linkInternalFilters = location.hostname;
1 guarantees it runs before your page view rule (which should use order 50 or higher). This ensures Activity Map and link tracking config are set before the first page view beacon or click event.
| Element type | Auto-tracked? | Notes |
|---|---|---|
<a href="..."> | Yes | All anchor links with href |
<a> with onclick | Yes | JavaScript-driven links |
<button> | Sometimes | Only if AppMeasurement recognizes it as interactive. Not guaranteed. |
<div onclick="..."> | No | Not a standard link element |
[role="button"] | No | ARIA roles not recognized by Activity Map |
<input type="submit"> | Sometimes | Depends on form structure |
s.tl() rule (Rule 4 below) is required. Activity Map + Rule 4 together cover everything.
s.trackInlineStats is false — the main toggle. Must be true.s.pageName isn't set — Activity Map needs a page name to associate the click. If your page view rule isn't firing first, Activity Map has no context.Once enabled, these dimensions are available in Analysis Workspace:
| Dimension | What it shows |
|---|---|
| Activity Map Link | The text/label of the clicked element |
| Activity Map Region | The container ID where the click occurred |
| Activity Map Page | The page where the click happened |
| Activity Map Link By Region | Link + Region combined |
You can also use the Activity Map browser overlay (separate browser extension from Adobe) to see a visual heatmap of clicks directly on the live page.
| Element | Activity Map handles it? | Rule 4 handles it? |
|---|---|---|
| Standard <a> links | Yes | Yes |
| Buttons | Maybe | Yes |
| role="button" divs | No | Yes |
| Form submits | No | Yes |
| onclick handlers | No | Yes |
| Icon-only elements | No label | Yes (reads aria-label) |
Every data element has ONE job. No data layer needed — everything comes from the URL, DOM, or cookies that already exist on the page.
Name format: lowercase.dot.notation
Data elements in Tags reference → | Core extension data element types →
| Name | Type | Source | Maps to | Docs |
|---|---|---|---|---|
page.name | Custom Code | document.title.replace(' | SiteName', '').trim() | s.pageName | pageName |
page.section | Custom Code | location.pathname.split('/').filter(Boolean)[0] || 'home' | s.channel | channel |
page.type | Custom Code | See Page Type Detection | s.pageType / s.prop1 | pageType |
page.heading | Custom Code | var h = document.querySelector('h1'); return h ? h.textContent.trim().substring(0, 255) : document.title.trim().substring(0, 255); | s.prop2 | |
page.url | Core > URL | Full URL | URL type | |
page.path | Core > URL | Path only | ||
page.queryString | Core > URL | Query string | ||
page.domain | Core > URL | Hostname | s.server | server |
| Name | Type | Source | Maps to | Docs |
|---|---|---|---|---|
user.authState | Custom Code | See User State Detection | s.eVar1 | eVars |
| Name | Type | Source | Maps to | Docs |
|---|---|---|---|---|
campaign.trackingCode | Query String Parameter | cid or utm_campaign | s.campaign | campaign |
| Name | Type | Source | Maps to | Docs |
|---|---|---|---|---|
search.term | Custom Code | See Search Term Detection | s.eVar5 / s.prop5 | eVars |
| Name | Type | Source | Maps to | Docs |
|---|---|---|---|---|
env.name | Custom Code | location.hostname.includes('staging') ? 'staging' : 'production' |
%value% references a data element that doesn't exist and isn't in this list, kill it.
Custom Code data element: page.type
Infers page type from URL patterns and DOM elements. No data layer needed.
var path = location.pathname.toLowerCase();
if (path === '/' || path === '/index') return 'home';
if (path.includes('/product')) return 'product';
if (path.includes('/cart') || path.includes('/basket')) return 'cart';
if (path.includes('/checkout')) return 'checkout';
if (path.includes('/confirm') || path.includes('/thank')) return 'confirmation';
if (path.includes('/search')) return 'search';
if (path.includes('/login') || path.includes('/signin')) return 'login';
if (path.includes('/account') || path.includes('/profile')) return 'account';
if (path.includes('/blog') || path.includes('/article') || path.includes('/news')) return 'content';
if (path.includes('/faq') || path.includes('/help') || path.includes('/support')) return 'support';
if (path.includes('/contact')) return 'contact';
if (path.includes('/category') || path.includes('/collection')) return 'category';
if (document.querySelector('.error-page, .page-404, [data-error]')) return 'error';
return 'general';
includes() checks to match. The logic above covers the most common patterns.
Custom Code data element: user.authState
Looks for common DOM patterns indicating logged-in state.
// Check DOM for logged-in indicators
if (document.querySelector(
'.logged-in, .authenticated, [data-logged-in], ' +
'.user-menu, .account-nav, .my-account'
)) return 'authenticated';
// Check meta tags
var meta = document.querySelector('meta[name="user-status"]');
if (meta) return meta.content;
// Check cookies
if (document.cookie.includes('loggedIn=') ||
document.cookie.includes('session=') ||
document.cookie.includes('auth='))
return 'authenticated';
return 'anonymous';
Custom Code data element: search.term
var params = new URLSearchParams(location.search);
return params.get('q')
|| params.get('query')
|| params.get('search')
|| params.get('s')
|| params.get('keyword')
|| (document.querySelector(
'input[type="search"], input[name="q"], .search-input'
) || {}).value
|| '';
The most resilient implementation uses a layered approach: read from the data layer when available, fall back to DOM scraping when it's not. This eliminates the dependency on a perfectly populated data layer while still using it when present.
// Hybrid data element: always returns a value
// Priority: data layer → DOM → sensible default
var dl = window.digitalData || {};
var page = dl.page || {};
// Use data layer value if it exists and is non-empty
if (page.name && page.name.trim()) return page.name.trim();
// Fall back to DOM scraping
return document.title.replace(' | SiteName', '').trim();
var dl = window.digitalData || {};
var page = dl.page || {};
return (page.name && page.name.trim())
|| (page.pageName && page.pageName.trim())
|| document.title.replace(/\s*[\|–—]\s*[^|–—]+$/, '').trim();
var dl = window.digitalData || {};
var page = dl.page || {};
return (page.section && page.section.trim())
|| (page.siteSection && page.siteSection.trim())
|| (page.category && page.category.trim())
|| location.pathname.split('/').filter(Boolean)[0] || 'home';
var dl = window.digitalData || {};
var page = dl.page || {};
if (page.type && page.type.trim()) return page.type.trim();
if (page.pageType && page.pageType.trim()) return page.pageType.trim();
// DOM/URL fallback
var path = location.pathname.toLowerCase();
if (path === '/') return 'home';
if (path.includes('/product')) return 'product';
if (path.includes('/cart')) return 'cart';
if (path.includes('/checkout')) return 'checkout';
if (path.includes('/search')) return 'search';
if (path.includes('/account')) return 'account';
if (path.includes('/blog') || path.includes('/article')) return 'content';
return 'general';
var dl = window.digitalData || {};
var user = dl.user || {};
if (user.authState) return user.authState;
if (user.loginStatus) return user.loginStatus;
if (user.authenticated !== undefined) return user.authenticated ? 'authenticated' : 'anonymous';
// DOM fallback
if (document.querySelector('.logged-in, .authenticated, .user-menu, .my-account'))
return 'authenticated';
return 'anonymous';
var dl = window.digitalData || {};
if (dl.search && dl.search.term) return dl.search.term;
if (dl.page && dl.page.searchTerm) return dl.page.searchTerm;
// URL/DOM fallback
var params = new URLSearchParams(location.search);
return params.get('q') || params.get('query') || params.get('search')
|| params.get('s') || params.get('keyword')
|| (document.querySelector('input[type="search"], input[name="q"]') || {}).value
|| '';
window.digitalData. Replace with the actual data layer variable name used on the site — common alternatives include window.dataLayer, window.utag_data, window.pageData, or any custom object. The fallback logic remains the same regardless of the variable name.
| Scenario | Approach |
|---|---|
| Data layer exists and is fully populated | Use data layer values directly — DOM fallbacks will never fire |
| Data layer exists but is partially populated | Hybrid — use data layer where available, DOM fills the gaps |
| Data layer does not exist or is empty | Hybrid gracefully degrades to full DOM scraping |
| Data layer is planned but not yet deployed | Start with DOM scraping now, add data layer checks later — no reconfiguration needed |
| Component | Configuration | Docs |
|---|---|---|
| Event | Core > Library Loaded (Page Top) | Core events |
| Condition | None | |
| Action 1 | Adobe Analytics > Set Variables | Rules |
| ||
| Action 2 | Adobe Analytics > Send Beacon — s.t() page view | s.t() |
| Component | Configuration | Docs |
|---|---|---|
| Event | Core > Library Loaded | Core events |
| Condition | Core > Value Comparison: %page.type% equals error | Conditions |
| Action 1 | Adobe Analytics > Set Variables | |
| ||
| Action 2 | Adobe Analytics > Send Beacon — s.t() | s.t() |
| Component | Configuration | Docs |
|---|---|---|
| Event | Core > Library Loaded | Core events |
| Condition | Core > Value Comparison: %search.term% is not empty | Conditions |
| Action 1 | Adobe Analytics > Set Variables | |
| ||
| Action 2 | Adobe Analytics > Send Beacon — s.t() | s.t() |
One rule covers every clickable element. Reads whatever data exists on the element automatically.
| Component | Configuration | Docs |
|---|---|---|
| Event | Core > Click | Core events |
Elements matching CSS: a, button, [role="button"], input[type="submit"], [onclick] | ||
| Condition | None | |
| Action 1 | Adobe Analytics > Custom Code (fires s.tl() inside) | s.tl() · linkTrackVars · linkTrackEvents |
var el = this;
// Walk up to find the actual clickable element (if we hit a child span/icon)
while (el && !el.matches('a, button, [role="button"], input[type="submit"]')) {
el = el.parentElement;
}
if (!el) el = this;
// Grab the best label available
var label = el.getAttribute('aria-label')
|| el.getAttribute('title')
|| el.getAttribute('data-track-label')
|| el.getAttribute('data-analytics')
|| el.getAttribute('data-name')
|| el.textContent.trim().substring(0, 100)
|| el.getAttribute('alt')
|| el.value
|| 'unlabeled';
// Grab the region/section of the page
var region = '';
var parent = el.closest(
'[id], [role="navigation"], [role="banner"], [role="main"], ' +
'[role="contentinfo"], nav, header, footer, aside, main, [data-region]'
);
if (parent) {
region = parent.getAttribute('data-region')
|| parent.getAttribute('id')
|| parent.getAttribute('role')
|| parent.tagName.toLowerCase();
}
// Grab the destination URL
var href = el.getAttribute('href') || '';
// Set the variables
s.eVar10 = label;
s.prop10 = label;
s.eVar11 = region || 'unknown';
s.prop11 = region || 'unknown';
s.eVar12 = href;
s.linkTrackVars = 'eVar10,prop10,eVar11,prop11,eVar12,events';
s.linkTrackEvents = 'event10';
s.events = 'event10';
// Fire the beacon right here — NOT as a separate Send Beacon action.
// s.tl() must be in the same code block so 'label' is available as the link name.
s.tl(this, 'o', label);
s.tl() call MUST be inside the Custom Code action — not as a separate "Send Beacon" action. Launch's Send Beacon action wants the link name as a static field or data element, and it can't read the label variable from the preceding custom code. Firing s.tl() directly in the code block solves this.
| Component | Configuration | Docs |
|---|---|---|
| Event | Core > Form Submission (or Custom Event for SPA) | Core events |
Elements matching CSS: form | ||
| Condition | None | |
| Action 1 | Adobe Analytics > Custom Code (fires s.tl() inside) | s.tl() |
// Scrape the form name from whatever exists
var formName = this.getAttribute('name')
|| this.getAttribute('id')
|| this.getAttribute('action')
|| (this.querySelector('[type="submit"]') || {}).textContent?.trim()
|| 'unknown form';
s.eVar13 = formName;
s.prop13 = formName;
s.linkTrackVars = 'eVar13,prop13,events';
s.linkTrackEvents = 'event11';
s.events = 'event11';
Then: Adobe Analytics > Send Beacon — s.tl(this, 'o', formName)
If developers eventually add data attributes to the HTML, you can target specific elements without changing Launch:
<!-- Any clickable element -->
<a href="/products" data-track-click data-track-label="Nav: Products">Products</a>
<button data-track-click data-track-label="Hero: Learn More">Learn More</button>
<!-- Any form -->
<form data-track-form="Contact Form" action="/submit">...</form>
The smart click rule (Rule 4) already checks for data-track-label and data-analytics attributes in its priority chain. So when devs add them, the tracking automatically gets more specific — no Launch changes needed.
When targeting a specific element beyond the universal click tracker, use this priority for selectors:
| Priority | Selector Pattern | Example | Reliability |
|---|---|---|---|
| 1 | ID | #sign-up-btn | Best |
| 2 | Data attribute | [data-action="subscribe"] | Best |
| 3 | Aria label | [aria-label="Close dialog"] | Good |
| 4 | Unique class + tag | button.cta-primary | OK |
| 5 | Container + tag | #hero-section button | OK |
| 6 | Structural path | nav > ul > li:nth-child(3) > a | Fragile |
.flex, .p-4, .w-full, .text-sm are not semantic — they exist on hundreds of elements and change frequently. Target structural or semantic classes only.
%garbage% or doesn't existLaunch data elements can be tested directly in the browser console without publishing changes. These methods return the resolved value of any %data_element% in real time.
// Returns the current resolved value of a data element
_satellite.getVar('page.name');
_satellite.getVar('page.type');
_satellite.getVar('user.authState');
_satellite.getVar('campaign.trackingCode');
_satellite.getVar('search.term');
// List every data element name and its current value
var names = Object.keys(_satellite._container.dataElements || {});
names.forEach(function(name) {
console.log(name + ': ' + _satellite.getVar(name));
});
// Run the same code a custom code data element would run
// Useful for debugging fallback chains
// Example: test the hybrid page.name logic
(function() {
var dl = window.digitalData || {};
var page = dl.page || {};
var result = (page.name && page.name.trim())
|| document.title.replace(/\s*[\|–—]\s*[^|–—]+$/, '').trim();
console.log('page.name would resolve to:', result);
console.log('Source:', (page.name && page.name.trim()) ? 'data layer' : 'DOM fallback');
})();
// See what the data layer contains (or if it exists at all)
console.log('digitalData:', window.digitalData);
console.log('dataLayer:', window.dataLayer);
console.log('utag_data:', window.utag_data);
// Deep inspect a specific path
console.table(window.digitalData && window.digitalData.page);
// After the page loads and the page view rule fires,
// inspect what AppMeasurement actually sent
console.log('pageName:', s.pageName);
console.log('channel:', s.channel);
console.log('campaign:', s.campaign);
console.log('eVar1:', s.eVar1);
console.log('events:', s.events);
// Or dump all populated props and eVars
for (var i = 1; i <= 75; i++) {
if (s['prop' + i]) console.log('prop' + i + ': ' + s['prop' + i]);
}
for (var i = 1; i <= 250; i++) {
if (s['eVar' + i]) console.log('eVar' + i + ': ' + s['eVar' + i]);
}
// Enables verbose console output for all Launch rule evaluations,
// data element resolutions, and beacon calls
_satellite.setDebug(true);
// Disable when done
_satellite.setDebug(false);
// Simulate what the universal click tracker would capture
// for a specific element — without actually firing a beacon
var el = document.querySelector('#sign-up-btn'); // target element
var label = el.getAttribute('aria-label')
|| el.getAttribute('title')
|| el.textContent.trim().substring(0, 100)
|| 'unlabeled';
var parent = el.closest('[id], nav, header, footer, main, aside');
var region = parent
? (parent.getAttribute('id') || parent.tagName.toLowerCase())
: 'unknown';
console.log('Label (eVar10):', label);
console.log('Region (eVar11):', region);
console.log('Href (eVar12):', el.getAttribute('href') || 'none');
Using the AA Debugger or Adobe Experience Platform Debugger extension:
| Check | Expected |
|---|---|
| Page load fires | s.t() with pageName and channel populated |
| Click on any link/button | Activity Map data in network request (a.activitymap.*) |
| Click on tracked element | s.tl() with eVar10 (label) and eVar11 (region) populated |
| Form submit | s.tl() with eVar13 (form name) populated |
| Search page | eVar5 populated with search term |
| Error page | s.pageType = "errorPage", event20 fires |
| Console | No undefined warnings for %value% references |
| What | How | Developer needed? |
|---|---|---|
| Page name | document.title | No |
| Page type | URL pattern matching | No |
| Site section | First path segment | No |
| User state | DOM class / cookie sniffing | No |
| Search term | URL params or input value | No |
| All clicks | Activity Map | No |
| Smart clicks | Universal click rule (DOM scraping) | No |
| Form submits | Form name/id/action scraping | No |
| Errors | DOM class detection | No |
| Campaign | Query string params | No |
| Downloads | Built-in link tracking | No |
| Exit links | Built-in link tracking | No |
| Time on page | Built into AppMeasurement | No |
| Scroll depth | Core > Scroll event | No |
| Variable | Purpose | Set by | Docs |
|---|---|---|---|
s.pageName | Page name | Rule 1 | pageName |
s.channel | Site section | Rule 1 | channel |
s.pageType | Page type | Rule 1 / Rule 2 | pageType |
s.campaign | Campaign tracking code | Rule 1 | campaign |
s.eVar1 | User auth state | Rule 1 | eVars |
s.prop1 | Page type (hit-level) | Rule 1 | Props |
s.eVar5 / s.prop5 | Internal search term | Rule 3 | eVars |
s.eVar10 / s.prop10 | Click label | Rule 4 | eVars |
s.eVar11 / s.prop11 | Click region | Rule 4 | |
s.eVar12 | Click destination URL | Rule 4 | |
s.eVar13 / s.prop13 | Form name | Rule 5 | |
event5 | Internal search | Rule 3 | Events |
event10 | Click event | Rule 4 | |
event11 | Form submit | Rule 5 | |
event20 | Error page | Rule 2 |
| Call | When | Type | Docs |
|---|---|---|---|
s.t() | Page view, error page, search page | Page view beacon | s.t() method |
s.tl(this, 'o', name) | Click tracking, form submit | Custom link beacon | s.tl() method |