1 · Why link tracking exists
The page view answers "what page is the user on?" It does not answer "what did the user do on that page?" If a product page is rich and interactive — scroll, hover, expand, click the size selector, click the colour selector, finally add to cart — the page view captures none of that interaction. Link tracking is how you record it.
Mechanically, a link hit is the same wire format as a page view, sent by s.tl()
instead of s.t(). The collection server processes it differently: a link hit does
not increment the Page Views metric, does not change pageName in reports, and
appears in the Custom Links / Exit Links / File Downloads reports — depending on type.
2 · The three link types
Adobe defines exactly three. You pick one each time you call s.tl(); that choice
determines which report the click lands in.
| Code | Type | Wire | Use for |
|---|---|---|---|
'o' | Custom / Other | pe=lnk_o | Generic interactions — button clicks, accordion expansions, video plays. |
'd' | Download | pe=lnk_d | File downloads — PDFs, ZIPs, exports. |
'e' | Exit | pe=lnk_e | Clicks to external domains. |
These map to three distinct reports in AA: Custom Links, File Downloads,
and Exit Links. The same click cannot appear in two of them — you pick one type per
tl call.
3 · The s.tl() signature
s.tl(thisLinkOrTrue, linkType, linkName, variableOverrides, callbackFn);
The arguments, in order:
- thisLinkOrTrue — either the actual DOM link element being clicked (so AA can
auto-extract
hrefif no name is given), or the booleantruewhen you don't have a DOM element (programmatic events). Almost always passtruein modern code. - linkType —
'o','d', or'e'. - linkName — the human-readable string that will appear in the report. Choose deliberately — this is the name analysts will see for years.
- variableOverrides — optional object to set variables only for this hit
(rare; usually you set them on
sdirectly). - callbackFn — optional function that fires when the hit has been sent. Useful for delayed navigation (see patterns below).
// A "Add to Cart" button click
s.linkTrackVars = "events,eVar4,prop4";
s.linkTrackEvents = "scAdd";
s.events = "scAdd";
s.eVar4 = "ASTRO-3000";
s.prop4 = "telescopes";
s.tl(true, "o", "Add to Cart · PDP");
// A PDF download
s.linkTrackVars = "eVar30";
s.eVar30 = "spec-sheet-astro3000";
s.tl(true, "d", "PDF · Astro 3000 Spec Sheet");
// Click to a partner domain
s.linkTrackVars = "eVar31";
s.eVar31 = "amazon-partner-link";
s.tl(true, "e", "Exit · amazon.com/astro3000");
4 · linkTrackVars and linkTrackEvents — the gate
This is the moment every new implementer trips over. By default, s.tl() sends
no custom variables, even if they are set on s. It explicitly opts in via
two whitelist strings:
s.linkTrackVars = "comma,separated,variable,names";
s.linkTrackEvents = "comma,separated,event,names";
If linkTrackVars doesn't list eVar4, then setting s.eVar4
before s.tl() has no effect — the value never reaches the server. If
linkTrackEvents doesn't list scAdd, the event doesn't increment even
if you set s.events = "scAdd".
"Why doesn't my eVar/event show up on this link click?" Ninety percent of the time:
linkTrackVars doesn't include it. The other ten percent: a previous tl() set
linkTrackVars and you didn't reset it.
Why does this gate exist? Two reasons. First, link hits are often fired from rich content where
the page-level s object has accumulated all sorts of state — sending it all by accident
would pollute reports. Second, opt-in forces the implementer to be explicit about what this
click is for, which produces cleaner data.
4.1 — Special values
Two reserved values for linkTrackVars:
"None"— send no custom variables. Use this for a click where you only care about the link name and type.- To include events, you must list the literal string
"events"inlinkTrackVarsand name the specific event(s) inlinkTrackEvents. Both are required.
5 · Auto link tracking
AppMeasurement can attach itself to every link on the page and fire tl() automatically.
Configured via three switches on s:
s.trackDownloadLinks = true; // Fire 'd' on links matching extensions below
s.trackExternalLinks = true; // Fire 'e' on links to other domains
s.trackInlineStats = true; // Enable ClickMap
s.linkDownloadFileTypes = "exe,zip,wav,mp3,mov,mpg,avi,wmv,doc,pdf,xls";
s.linkInternalFilters = "javascript:,mydomain.com"; // What counts as "internal"
s.linkLeaveQueryString = false; // Strip query strings from link names
Enable these in Launch via the Adobe Analytics extension settings, not by hand-editing
s_code. They run on every click anywhere on the page and can quickly inflate hit
volumes if mis-configured. Audit linkInternalFilters carefully — anything not matching
is considered external and counts as an exit link.
6 · Patterns and pitfalls
6.1 — The "page view in disguise" anti-pattern
Tempting: "I'll fire s.t() when the user clicks a button so I see the click in
reports." Wrong. That click now counts as a page view, inflating your Page Views metric and
overwriting pageName for the visit's pathing. Use s.tl() for clicks.
6.2 — The "navigation race" pitfall
Click a link, the browser navigates instantly, the page unloads — and the tl()
request is still in flight. Modern browsers (and AppMeasurement) handle this via the Beacon API
or short-blocking, but it remains brittle on slow networks. For critical clicks where you must
get the hit through:
link.addEventListener("click", function (e) {
e.preventDefault();
s.linkTrackVars = "eVar4,events";
s.linkTrackEvents = "event10";
s.events = "event10";
s.eVar4 = "important-link";
s.tl(true, "o", "Critical CTA", null, function () {
// Callback fires when the hit has been sent
window.location.href = link.href;
});
});
For modern Launch, this is handled by the "Send Beacon" data action — set "Linkage" to wait for the hit. The pattern above is the manual version.
6.3 — Resetting linkTrackVars between hits
If you don't reset, the prior hit's whitelist leaks into the next:
// Bug: second tl() inherits eVar4 + events whitelist
s.linkTrackVars = "eVar4,events";
s.linkTrackEvents = "event10";
s.tl(true, "o", "First click");
// Later — different click, different variables, no reset
s.eVar20 = "newsletter";
s.tl(true, "o", "Newsletter signup"); // eVar20 NOT sent
// Fix: explicit reset
s.linkTrackVars = "eVar20";
s.linkTrackEvents = "";
s.eVar20 = "newsletter";
s.tl(true, "o", "Newsletter signup");
In Launch, this is one reason rules should call a "clear variables" data action at the end — to reset transient state between rule invocations.
6.4 — Naming convention
Link names land in reports forever. A consistent prefix scheme saves an analyst's afternoon:
cta:add-to-cart · header
cta:add-to-cart · sticky-footer
nav:product-category · telescopes
modal:size-selector · open
modal:size-selector · close
video:play · product-tour
video:25% · product-tour
video:50% · product-tour
video:75% · product-tour
video:complete · product-tour
Lowercase, colon-delimited, structured. Use the same structure across teams. A flat list of inconsistent names becomes unmanageable at 200 link events.
7 · Checkpoint
You should now be able to:
- Pick link type
o/d/efor any given click. - Predict the
peparameter on the wire. - Diagnose a "missing eVar on link hit" bug in under thirty seconds.
- Explain why
s.tl()requires whitelisting buts.t()does not. - Use the callback to navigate after a click without losing the hit.
Practice exercises C and D from module 03's sandbox if you have not yet — they directly exercise the whitelist gate.