Launch + AppMeasurement Clean Implementation Guide

Complete implementation reference for Adobe Analytics via Launch with AppMeasurement.

Contents

  1. Extension Configuration
  2. Activity Map Deep Dive
  3. Data Elements (DOM Scraping)
  4. Page Type Detection
  5. User State Detection
  6. Search Term Detection
  7. Hybrid Data Layer + DOM Fallback Pattern
  8. Rules
  9. Smart Click Tracking (No Data Attributes)
  10. Form Tracking
  11. Data Attribute Pattern (If You Get Dev Support Later)
  12. CSS Selector Strategy
  13. Console Testing Methods
  14. Cleanup Checklist
  15. Validation
  16. Full Coverage Matrix
  17. API Reference

1. Extension Configuration

Adobe Analytics Extension

SettingValueDocs
Report Suite IDProduction + Staging (use environment-specific)Report suites
Tracking ServerYour tracking servers.trackingServer
Character SetUTF-8s.charSet
Currency CodeUSDs.currencyCode

Full extension reference →

Link Tracking Settings

SettingValueWhyDocs
Enable Activity MapONAuto-tracks every click with link text, region, and pageActivity Map
Track download linksONAuto-tracks file downloadss.trackDownloadLinks
Track outbound linksONAuto-tracks exit linkss.trackExternalLinks
Download extensionsdoc,docx,eps,jpg,png,pdf,pptx,svg,xls,xlsx,zipFile types to track as downloadss.linkDownloadFileTypes
Activity Map gives you click tracking for free. It auto-captures every link click with the link text, the region (parent container ID), and the page. Shows click heatmaps in Adobe Analytics. No rules, no data elements needed. See Activity Map Deep Dive below.

1b. Activity Map Deep Dive

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.

What Activity Map sends on every click

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.

Extension settings that must be ON

SettingWhereMust beDocs
Enable ClickMap / Activity MapAA Extension > Link TrackingONGetting started
Enable Link TrackingAA Extension > Link TrackingONtrackExternalLinks
Track download linksAA Extension > Link TrackingONtrackDownloadLinks
Track outbound linksAA Extension > Link TrackingONtrackExternalLinks

Code settings (no checkbox in the UI)

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:

Option A: Extension Custom Code (requires extension configuration access)

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;
This is the preferred method. The extension custom code runs once when the library loads, before any rules fire. This guarantees Activity Map is active before the first click.

Option B: Rule-based config (when extension configuration access is unavailable)

When the AA extension settings cannot be edited directly, create a dedicated config rule instead:

ComponentConfigurationDocs
Rule nameGlobal ConfigRules
EventCore > Library Loaded (Page Top)Core events
Order1 (lowest fires first — must run before all other rules)Rule ordering
ConditionNone
ActionAdobe 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;
Why Library Loaded (Page Top) with Order 1? This is the earliest rule event — fires as soon as the Launch library loads, before DOM Ready and Window Loaded. Setting the order to 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.
Rule ordering reference: Lower numbers fire first. Event-based rules (click, form submit) fire on their trigger regardless of order — order only matters for rules sharing the same event type.

What Activity Map tracks vs doesn't

Element typeAuto-tracked?Notes
<a href="...">YesAll anchor links with href
<a> with onclickYesJavaScript-driven links
<button>SometimesOnly if AppMeasurement recognizes it as interactive. Not guaranteed.
<div onclick="...">NoNot a standard link element
[role="button"]NoARIA roles not recognized by Activity Map
<input type="submit">SometimesDepends on form structure
Activity Map only reliably tracks <a> tags. For buttons, divs with onclick, role="button", and other non-link interactive elements, the custom s.tl() rule (Rule 4 below) is required. Activity Map + Rule 4 together cover everything.

Why clicks might not appear

  1. s.trackInlineStats is false — the main toggle. Must be true.
  2. Link tracking globally disabled — check "Enable Link Tracking" in extension settings.
  3. The element isn't an <a> tag — Activity Map only auto-fires on elements AppMeasurement identifies as links. Use Rule 4 for everything else.
  4. 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.
  5. The link text is empty — icon-only links with no text content, no aria-label, and no title won't produce a useful Activity Map entry.

Activity Map in Adobe Analytics reporting

Once enabled, these dimensions are available in Analysis Workspace:

DimensionWhat it shows
Activity Map LinkThe text/label of the clicked element
Activity Map RegionThe container ID where the click occurred
Activity Map PageThe page where the click happened
Activity Map Link By RegionLink + 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.

Activity Map + custom s.tl() rule = full coverage

ElementActivity Map handles it?Rule 4 handles it?
Standard <a> linksYesYes
ButtonsMaybeYes
role="button" divsNoYes
Form submitsNoYes
onclick handlersNoYes
Icon-only elementsNo labelYes (reads aria-label)
Use both. Activity Map provides baseline coverage. Rule 4 catches everything Activity Map misses. They complement each other with no conflict when mapped to separate eVars.

2. Data Elements (DOM Scraping)

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 →

Page-Level

NameTypeSourceMaps toDocs
page.nameCustom Codedocument.title.replace(' | SiteName', '').trim()s.pageNamepageName
page.sectionCustom Codelocation.pathname.split('/').filter(Boolean)[0] || 'home's.channelchannel
page.typeCustom CodeSee Page Type Detections.pageType / s.prop1pageType
page.headingCustom Codevar h = document.querySelector('h1'); return h ? h.textContent.trim().substring(0, 255) : document.title.trim().substring(0, 255);s.prop2
page.urlCore > URLFull URLURL type
page.pathCore > URLPath only
page.queryStringCore > URLQuery string
page.domainCore > URLHostnames.serverserver

User-Level

NameTypeSourceMaps toDocs
user.authStateCustom CodeSee User State Detections.eVar1eVars

Campaign

NameTypeSourceMaps toDocs
campaign.trackingCodeQuery String Parametercid or utm_campaigns.campaigncampaign

Search

NameTypeSourceMaps toDocs
search.termCustom CodeSee Search Term Detections.eVar5 / s.prop5eVars

Environment

NameTypeSourceMaps toDocs
env.nameCustom Codelocation.hostname.includes('staging') ? 'staging' : 'production'
Delete everything else. If a %value% references a data element that doesn't exist and isn't in this list, kill it.

3. Page Type Detection

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';
Customize the URL patterns to match the site structure. Look at the actual URL structure and adjust the includes() checks to match. The logic above covers the most common patterns.

4. User State Detection

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
  || '';

5b. Hybrid Data Layer + DOM Fallback Pattern

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.

Why this pattern matters. Data layers are often incomplete, inconsistently populated across page types, or deployed in phases. A hybrid approach ensures tracking works on day one regardless of data layer readiness, and automatically upgrades to structured data as it becomes available.

The Pattern

// 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();

Applied to Each Data Element

page.name (hybrid)

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();

page.section (hybrid)

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';

page.type (hybrid)

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';

user.authState (hybrid)

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';

search.term (hybrid)

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
  || '';
Adapting the data layer object name. These examples reference 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.

When to use which approach

ScenarioApproach
Data layer exists and is fully populatedUse data layer values directly — DOM fallbacks will never fire
Data layer exists but is partially populatedHybrid — use data layer where available, DOM fills the gaps
Data layer does not exist or is emptyHybrid gracefully degrades to full DOM scraping
Data layer is planned but not yet deployedStart with DOM scraping now, add data layer checks later — no reconfiguration needed

6. Rules

Rule 1: Page View — All Pages

ComponentConfigurationDocs
EventCore > Library Loaded (Page Top)Core events
ConditionNone
Action 1Adobe Analytics > Set VariablesRules
s.pageName  = %page.name%
s.channel   = %page.section%
s.pageType  = %page.type%
s.campaign  = %campaign.trackingCode%
s.eVar1     = %user.authState%
s.prop1     = %page.type%
Action 2Adobe Analytics > Send Beacon — s.t() page views.t()

Rule 2: Error Page

ComponentConfigurationDocs
EventCore > Library LoadedCore events
ConditionCore > Value Comparison: %page.type% equals errorConditions
Action 1Adobe Analytics > Set Variables
s.pageType = "errorPage"
s.events   = "event20"
Action 2Adobe Analytics > Send Beacon — s.t()s.t()

Rule 3: Internal Search

ComponentConfigurationDocs
EventCore > Library LoadedCore events
ConditionCore > Value Comparison: %search.term% is not emptyConditions
Action 1Adobe Analytics > Set Variables
s.eVar5  = %search.term%
s.prop5  = %search.term%
s.events = "event5"
Action 2Adobe Analytics > Send Beacon — s.t()s.t()

7. Smart Click Tracking (No Data Attributes Needed)

One rule covers every clickable element. Reads whatever data exists on the element automatically.

Rule 4: Universal Click Tracker

ComponentConfigurationDocs
EventCore > ClickCore events
Elements matching CSS: a, button, [role="button"], input[type="submit"], [onclick]
ConditionNone
Action 1Adobe 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);
Important: ONE action, not two. The 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.
What this captures for every click:

8. Form Tracking

Rule 5: Form Submit

ComponentConfigurationDocs
EventCore > Form Submission (or Custom Event for SPA)Core events
Elements matching CSS: form
ConditionNone
Action 1Adobe 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 Beacons.tl(this, 'o', formName)


9. Data Attribute Pattern (If You Get Dev Support Later)

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.

10. CSS Selector Strategy

When targeting a specific element beyond the universal click tracker, use this priority for selectors:

PrioritySelector PatternExampleReliability
1ID#sign-up-btnBest
2Data attribute[data-action="subscribe"]Best
3Aria label[aria-label="Close dialog"]Good
4Unique class + tagbutton.cta-primaryOK
5Container + tag#hero-section buttonOK
6Structural pathnav > ul > li:nth-child(3) > aFragile
Avoid Tailwind/utility classes as selectors. Classes like .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.

11. Cleanup Checklist

  1. Delete every data element that references %garbage% or doesn't exist
  2. Delete every rule that references deleted data elements
  3. Configure the AA extension settings (Activity Map ON, link tracking ON)
  4. Create the clean data elements from the table above
  5. Create the 5 rules
  6. Build to staging
  7. Validate with Adobe Debugger (see below)
  8. Build to production

11b. Testing Data Elements in the Browser Console

Launch 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.

Method 1: _satellite.getVar()

// 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');

Method 2: Dump all data elements at once

// 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));
});

Method 3: Test specific custom code logic

// 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');
})();

Method 4: Check the data layer object directly

// 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);

Method 5: Validate the s object after a page view fires

// 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]);
}

Method 6: Enable Launch debug logging

// Enables verbose console output for all Launch rule evaluations,
// data element resolutions, and beacon calls
_satellite.setDebug(true);

// Disable when done
_satellite.setDebug(false);
_satellite.setDebug(true) persists across page loads by setting a cookie. It will remain active until explicitly turned off or the cookie is cleared. Useful for multi-page debugging sessions.

Method 7: Test a click tracking rule manually

// 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');

12. Validation

Using the AA Debugger or Adobe Experience Platform Debugger extension:

CheckExpected
Page load firess.t() with pageName and channel populated
Click on any link/buttonActivity Map data in network request (a.activitymap.*)
Click on tracked elements.tl() with eVar10 (label) and eVar11 (region) populated
Form submits.tl() with eVar13 (form name) populated
Search pageeVar5 populated with search term
Error pages.pageType = "errorPage", event20 fires
ConsoleNo undefined warnings for %value% references

13. Full Coverage Matrix

WhatHowDeveloper needed?
Page namedocument.titleNo
Page typeURL pattern matchingNo
Site sectionFirst path segmentNo
User stateDOM class / cookie sniffingNo
Search termURL params or input valueNo
All clicksActivity MapNo
Smart clicksUniversal click rule (DOM scraping)No
Form submitsForm name/id/action scrapingNo
ErrorsDOM class detectionNo
CampaignQuery string paramsNo
DownloadsBuilt-in link trackingNo
Exit linksBuilt-in link trackingNo
Time on pageBuilt into AppMeasurementNo
Scroll depthCore > Scroll eventNo

14. API Reference

Variable Mapping Summary

VariablePurposeSet byDocs
s.pageNamePage nameRule 1pageName
s.channelSite sectionRule 1channel
s.pageTypePage typeRule 1 / Rule 2pageType
s.campaignCampaign tracking codeRule 1campaign
s.eVar1User auth stateRule 1eVars
s.prop1Page type (hit-level)Rule 1Props
s.eVar5 / s.prop5Internal search termRule 3eVars
s.eVar10 / s.prop10Click labelRule 4eVars
s.eVar11 / s.prop11Click regionRule 4
s.eVar12Click destination URLRule 4
s.eVar13 / s.prop13Form nameRule 5
event5Internal searchRule 3Events
event10Click eventRule 4
event11Form submitRule 5
event20Error pageRule 2

Beacon Types

CallWhenTypeDocs
s.t()Page view, error page, search pagePage view beacons.t() method
s.tl(this, 'o', name)Click tracking, form submitCustom link beacons.tl() method

Complete variable reference →

Zero storage. Zero tracking. This page runs entirely in your browser.