Experimentation Agency Message Testing Blog Search Start 7-day trial for $1 Upskilling Playbooks Find a job Resources Help

Adobe Analytics Implementation: Hacks You Don’t Know About

Adobe Analytics Implementation: Hacks You Don’t Know About

A few years ago, our developers rolled out Angular on a few key web pages—without consulting the web analytics team. The pageviews on some of the pages suddenly dropped to almost nothing.

I bought two books on Angular to try to find a solution. Meanwhile, my manager stumbled on an alternative syntax for tracking pageviews in Adobe Analytics. As we found out, a similar alternative syntax also exists to track clicks (and anything else).

My first reaction was that both syntaxes were equivalent, but I could not have been more wrong. It also turned out that Adobe has barely documented the features you’re about to uncover.

1. Tracking pageviews

The classic syntax (Adobe’s recommendation)The overrides syntax (undocumented)
  pageName:"homepage",   channel:"home",

In both examples above, you pass the same information to Adobe. Looking at the tracking request URL in the browser dev tools Network tab, you can’t even tell which of the two syntaxes the web page uses.

The advantages of the overrides syntax emerge when you need to tag a single-page application (SPA). An SPA tends to load a page only once, then updates it partially to build the next page it needs to display. This produces the illusion of a new page but loads faster because fewer things change—there’s no need to reload the top navigation, the footer, etc.

Consider the following sequence:

  1. A visitor lands on an SPA, and the first page loads fully:


  1. The same visitor sees a second page:

s.pageName="products page 1";

With Adobe Analytics, the problem is that your second pageview request contains s.prop1 again. That’s because the second page didn’t load as a new page—only an updated version. As a result, the s.prop1 is still there until a new page loads, or you reset it back to nothing:

s.pageName="products page 1";
s.prop1=""; //resetting s.prop1 back to nothing

I don’t know about you, but remembering to clean all the props, eVars, events, contextData, product strings, etc., takes a lot of discipline. Someone is likely to forget one (or a few of them) in their tagging guides.

You could use s.clearVars before tracking your pageviews to reset most of your data points to nothing, but not contextData values. The s.clearVars has been around since at least 2016, but many companies still use ancient versions of the Adobe Analytics libraries.

Using the overrides syntax, there’s a more efficient, surgical way to clean these data points than s.clearVars that’s also supported by old Adobe Analytics libraries. Let’s revisit the second-page sequence but use the overrides syntax:

  1. A visitor lands on an SPA, and the first page loads fully:

  pageName : "home",
  channel  : "home",
  prop1    : "home"

  1. The same visitor sees a second page:

  pageName : "products page 1",
  channel  : "products"

Now, if you looked at the tracking requests for both pageviews, you would see that prop1 is in the first pageview request but not the second one. You no longer need to remember what to reset; you don’t need s.clearVars either.

The data points you declare inside the JSON that you pass as a parameter for your s.t() call have a scope that’s strictly limited to that s.t() call. With this overrides syntax, you declare private data points. Using the classic syntax, you declare public data points.

In the context of an SPA, Adobe Analytics remembers your public data points and forgets your private ones.

2. Tracking clicks and other page element interactions

As mentioned in the introduction, the overrides syntax exists for tracking clicks, too. You can also use the overrides syntax for any page element interaction, not just clicks.

The classic syntax (Adobe’s recommendation)The overrides syntax (undocumented)
  “click”, function(){
    s.tl(true, “o”, “my button was clicked”);
  “click”, function(){
    s.tl(true, “o”, “my button was clicked”,{prop2:”test”});

The s.tl() method lets you track these page element interactions. It can take a fourth parameter, which is a JSON override object similar to the one for tracking pageviews.

The three-layer onion model

Any data point you declare inside the s-code (ancient) or the AppMeasurement Library (the new name for several years now) acts as a constant for all websites using that specific version of the Adobe Analytics library.

Details about your tracking servers, currency code, and character set are typical examples of such constants. This is a documented feature of Adobe Analytics implementations.

You can use the classic syntax after your Adobe Analytics library has loaded, making those data points public data points. You can check their values in the browser dev tools JavaScript console.

Typical examples are the page name, site section, sub-section, etc. There’s also an overrides syntax that lets you create private data points:

  1. In the Adobe Analytics library file: site-wide constants;
  2. In the page code public page-specific properties;
  3. Inside the s.t() and the s.tl() calls: private tracking request specific properties.

For a non-SPA website, you’ve probably used only Layers 1 and 2 together. You can replace your page code properties with the overrides syntax (i.e. use Layers 1 and 3 instead). But with an SPA website, you should use all three layers, repurposing Layer 2 to declare only template-wide constants.

If you’re a fan of Functionalism, you may have categorized all web pages into page types. I would consider declaring the page type using a prop at Layer 2 with pathing enabled. (Functionalism is a topic for another blog post. Read the blog posts by June Dershewitz, and the original whitepaper by Gary Angel, Joel Hadary, and Paul Legutko.)

In SPA parlance, the developers refer to the content that updates a page template to create the illusion of new pages as fragments. When you go from page to page, the SPA loads fragments to update the page you were on before you clicked on that link. The page name probably changed as a result of that click, too.

You want to track that page name on Layer 3—inside the overrides JSON that you pass as an input parameter to the s.t() call because the page name is specific to the pageview tracking request.

And there you have it: Adobe Analytics on all three layers. Adobe has never documented how these layers work together.

A fourth layer for the onion model?

I have explained how you can declare data points as constants for your whole website, or a collection of websites when you declare them at a layer inside your Adobe Analytics library code.

The concepts of constants and default values are closely related. You might declare a data point on Layer 1, then redeclare the value for that data point on Layer 2 or 3. When you do, a given data point will be set to different values as the screen renders.

Generally speaking, the last value you pass to a data point will be the value you will see in your reports. The overrides syntax overrides all previous values the data point contained (should you redeclare its value in the JSON overrides object).

But this is not always true.

The Adobe Analytics library contains a function called s_doPlugins(). You need to set the s.usePlugins function to true, or the s_doPlugins() function will never execute.

At this point, you should be asking what difference it makes to declare a data point inside the library file versus inside or outside the function. It turns out that inside makes the data point read-only, and redeclaring it in Layers 2 or 3 makes no difference—the original value inside the s_doPlugins() function is the value you will see in the reports.

This happens because s.t() checks for the s.usePlugins flag, and if it’s set to true, it executes the s_doPlugins function that redeclares your data points. You could call this the fourth layer of the onion model:

  1. The Adobe Analytics library loads and declares the data points declared outside the s_doPlugins() function for the first time.
  2. The page code redeclares some of them.
  3. The s.t() function redeclares some of them in the JSON overrides object.
  4. The s.t() function executes and checks the s.usePlugins flag:
    • If it’s set to false, the reports show the values of the data points set inside the JSON overrides object in the s.t() function call.
    • If it’s set to true, the reports show the values declared inside the s_doPlugins() function.

Using s.t() for all your tracking needs

If you hadn’t guessed by now, I find strange ways to spend my free time. I once decided to deobfuscate the H.26 s_code.js Adobe Analytics library. I found the code for the s.t() and the s.tl() functions that handle pageviews tracking and page element interactions tracking, respectively.

The s.tl() function calls the s.t() function, which ultimately calls a deeper Adobe Analytics function called s.mr(). (I assume that mr stands for “make request” since that function builds the pixel URL for your s.t() and s.tl() calls.)

If you’re looking for April Fools ideas to confuse your junior Adobe Analytics developer, you can make s.t() calls using the overrides syntax that behave just like s.tl() calls. Here’s how:

  "click", function(){
      lnk : true,
      linkTrackVars : "channel,prop1,prop2,prop3",
      linkTrackEvents : "None",
      linkName : "Button was clicked",
      linkType : "o",
      pageName : dataLayer.pageName,
      channel : dataLayer.channel,
      prop1 : dataLayer.prop1,
      prop2 : dataLayer.prop2,
      prop3 : dataLayer.prop3


Adobe introduced callback support in AppMeasurement 1.8.0 in 2017. These are the s.registerPreTrackCallback() and s.registerPostTrackCallback() functions. They’re documented, and they let you execute code in response to a tracking request just before it fires, or right after it fired.

If you’re working with an older version of the Adobe Analytics library and need these callbacks, all is not lost, but what follows is not supported by Adobe—it requires modifying the code in the “do not modify” part of the library (which could void your support).

I’ve mentioned above that s.mr() functions generate the tracking request and fire the HTTP request. The function contains this line of code:


  • im is an <img> tag element created on the fly. This object has a src property that is the URL of that <img> tag.
  • The rs is a string containing the tracking pixel URL. If you commented out that seemingly innocent command, the tracking requests stop firing altogether.

Setting the src property of an <img> tag triggers an HTTP GET request. You could call a custom JavaScript function before and/or after this line of code to support callbacks with older versions of the Adobe Analytics library code.

For even greater code decoupling, consider firing two distinct custom Javascript events—one before and one after the tracking request. Each of these custom JavaScript events can take a custom JSON object, which could be the same JSON you used with the overrides syntax (or a subset or superset of it).

document.body.dispatchEvent(new CustomEvent(
  detail:{},//optional JSON, can be your overrides JSON object
document.body.dispatchEvent(new CustomEvent(
  detail:{},//optional JSON, can be your overrides JSON object

Two things to remember (unless you want to generate infinite server calls and get fired):

  1. Never call s.registerPreTrackCallback() or s.registerPostTrackCallback() inside the s_doPlugins() function.
  2. Never call s.t() or s.tl() inside the s.registerPreTrackCallback(), s.registerPostTrackCallback(), or s.mr() functions.

Please note that more recent versions of Adobe Analytics support HTTP POST requests. Old versions of Internet Explorer only support URLs of up to 2,083 characters. This leads to truncation, and truncation leads to loss of data.

Since 2014, Adobe Analytics has supported switching from HTTP GET (i.e. using <img> tags to send the data to the Adobe servers) to HTTP POST (i.e. submitting a hidden form). This article does not cover creating callbacks for HTTP POST, but I’m sure that determined minds will find the HTTP POST equivalent of im.src=rs.


I believe that Adobe originally had a single method to track pageviews and page element interactions. Then, later versions of Adobe Analytics introduced a separate function for tracking these interactions, and so the s.tl() function was born—and the s.t() was restricted to tracking pageviews.

I also believe that the overrides syntax was the original way of tracking; the page code came later on. Omniture, then Adobe, has had to ensure that upgrading the s_code.js file and, later, the AppMeasurement library code didn’t force clients to redo all their tagging. 

The latest and future versions of the Adobe Analytics library will continue to contain code that supports ancient implementations. I like the overrides syntax. I find it elegant, and it has worked very well for us, especially on our SPAs.

Related Posts

Current article:

Adobe Analytics Implementation: Hacks You Don’t Know About

What’s on my mind

Hi, I'm Peep Laja—founder of CXL. I'm a former champion of optimization and experimentation turned business builder.

I do a lot of thinking, reading, and writing around business, strategy, and optimization. I send a weekly newsletter with what's on my mind on this stuff.