SPAs, DTM, and clearVars

For some reason, I see a lot of SPAs right now, some of them actually embedded into otherwise harmless and perfectly likable web sites. I guess they’re not going to go away anytime soon. This article is part of my “don’t fight it” exercise.

It is a follow-up to the article on tracking single-page applications. I want to add two important things:

  1. a brief discussion on EBRs and hashes, and
  2. a workaround for the fact that the s object is only instantiated once and we need to be careful with s.t() calls and what data they send.

EBRs & Hashes

Back in the days, SPAs had this ugly habit of breaking the browser navigation (back button would exit the SPA rather than going back “one page”), until someone decided that the hash part of the URL could be used to simulate pages.

So these days, most SPAs do change the URL when they render a new “page”.

In an SPA that displays books, I could move from the overview (on to one of the books (e.g., or maybe to a list of some books (on

This is great news for your friendly marketer, and for you, too!

What it means is that we no longer rely on you — the developer — to call DCRs at every turn. Instead, we can use EBRs along with the “pushState or hashchange” event type in DTM.

DTM event types – hashchange
In the Analytics part of that rule, we select “s.t(); – increments a pageview”, and we specifiy a page name.

EBR – s.t and pageName
The “Page Name” Data Element can potentially be really easy, and just read window.location.hash, but that would be cheating, wouldn’t it?!

That EBR completely covers normal tracking, which is why I usually call it “Quasi-normal Page Load”.

Everybody happy. Except…

The clearVars Conundrum

When we are dealing with an SPA, conceptually, there will be one single Page Load Rule, which runs when the SPA is loaded. That happens when someone lands on it for the first time, or when they reload. The normal flow within the SPA does not reload any pages, though.

As a result, we get a tracking object (“s object”) that never dies. Or rather, unlike the usual “page scope”, where a loading page creates the s object, and the next page creates a new one, in an SPA, the lifetime of the s object is unnaturally long. It is immortal, so to speak.

Now that is an issue.


A long time back, I used to write on this blog about “variables”, and I always put that in quotes, because you really shouldn’t see something like s.eVar47 as a variable if you’re a developer. I used to call it a container.

But then I spent a day with my kids in an amusement park last month, and now I have a better analogy:

We went on a roller coaster. There was a (nicely short) queue, which at the end split up into individual queues with little barriers. When a roller coaster train arrived, the barriers would let two people pass and sit on the train, then close again. The train would then head off.

The s object is like those individual queues at the end with the little barriers. You put people (values) into each little queue (eVarXYZ, propXY, …) as needed, then the train arrives, takes them and leaves (s.t() call).

The big difference is that s.t() copies the people, so they are still at the head of the queue once the train has left.


That means that the next train will grab the same people, plus those that have arrived at formerly empty little queues in the meantime.

Think about custom tracking on some visitor action. You likely want to track some event and maybe an eVar, but I’m pretty sure you’re not interested in all the other stuff that currently sits in the s object.

And that’s ok, that’s why we have s.linkTrackVars and s.linkTrackEvents. Works as designed, I’d say.

But for an s.t() call, which sends the lot, we usually except the s object to be shiny and new, which in an SPA, it isn’t. Nobody originally thought we would reuse the object, but that’s what SPAs do.

The answer is a little-known utility method, that is part of the AppMeasurement core code: s.clearVars().

The s.clearVars() method deletes the values for most of the “variables”, or it takes all those people standing by the mini-gates and tells them to leave. Nicely.

We can use it with an SPA like so:

  1. visitor moves to “new page”
  2. call s.clearVars()
  3. track the “new page”

Or like so, pre-emptively, so to speak:

  • track “new page”
  • call s.clearVars()

Whichever way, only by calling s.clearVars() can we be sure our tracking doesn’t have “old values”.

2017-06-29 update: my colleague JB Creusat points out that contextData is not emptied, so you still need to do that manually!


The first time I implemented this, I had to do it the first way, i.e. call s.clearVars() before tracking, which raised an interesting question: how can I make sure those two things happen in that exact order?

The answer to that involves some Javascript and a DCR, of course.

In my SPA, I had an EBR that listened to “pushstate or hashchange”, then tracked a bunch of “variables” and some events.

When I realised that all “variables” and events from the previous “page” were also on the tracking call, I did the following:

  1. created a new DCR
  2. moved all tracking from the “Analytics” section of the EBR over to the DCR (“moved” as in: created the same things in the DCR, then deleted them in the EBR)
  3. created a 3rd-party Javascript script in the EBR
  4. in that script, called s.clearVars(), then the DCR via _satellite.track('')

As a result, the EBR (which is still being called) doesn’t actually track anything. But it cleans all the “variables”, and then calls a DCR which does. A bit like a manager who takes a request, then passes it on to a worker.

Easy peasy, isn’t it?

15 thoughts on “SPAs, DTM, and clearVars

  1. Yes, this is a huge problem for lots of folks- thanks for the insight! I tend to run s.clearVars in the Custom Code Condition block in the EBR, which is guaranteed to run before any of the rule logic- something like “s.clearVars(); return true” as a condition. The only potential problem is that that won’t work if your s object isn’t globally scoped.


    1. I came across yet another way today:
      Make EBRs for triggers, which then call DCRs. One of those DCRs does nothing but call s.clearVars().

      I guess that helps separate logic and implementation detail.


  2. Hi, This is indeed a headache and i would classified as a flaw of DTM. I Don’t know why they don’t allow an option to tell the rule to base itself to what it will see in the template. I mean you are using a TMS so why is this missing or making users life more difficult?

    Anyway, on my side i like using the EBR for the hash change pages and either use a condition (which is not possible in DCR) OR i wrote a function (included on my global page load rule) which i call in each EBR or DCR custom js (i.e. in the analytics editor) and tell it to clear all vars that is NOT in that specific rule.
    I have also noticed that any custom js you include in any type of rules do not have access to the s object unless you use the JS editor in the analytics section of the rule.

    Let me know if you want me to share the function.


  3. Hi Jan, I came up with a very simple solution for implementing clearVars which simply requires overriding s.t() to trigger clearVars after every call to it.

    This is so much easier than trying to clear variables in numerous scenarios all over the site, and leaves no question that clearVars() will be called 100% of the time, immediately after every page view call.

    You’ll want to define this globally such as in global Custom Code in DTM, so that it only runs once:

    // override s.t() to trigger clearVars after every call to it
    var t_orig = s.t;
    s.t = function(){
    t_orig.apply(this, arguments);
    try {
    } catch(e){}

    With this solution, you never need to worry about accidentally clearing variables set by custom scripts or event-based rules.

    A second scenario is when you have multiple calls in an application, and you’d like to clearVars() between one event and another to avoid sending the same variables in the next

    For that, use the following code in your custom script just before you invoke the, or in the top of the custom conditions block in an event-based DTM rule:

    // clear pre-existing variables here, before anything new gets set
    var s = _satellite.getToolsByType(‘sc’)[0].getS(); // this line is optional and for use in DTM only; not necessary if you are sure your “s” object is already set to the correct reference.


      1. Update: I learned that Adobe has introduced callback methods for just this purpose as of AM 1.8.0: s.registerPreTrackCallback and s.registerPostTrackCallback.

        It’s as simple as:
        s.registerPreTrackCallback(function() {


  4. Hi Jan,

    Regarding your EBR’s and Hashes section, if someone refreshed the page then how pushstate/hashchange method work as there is no change in state/URL. can you please suggest some alternative


    1. Hi Kuldip,

      If they hit refresh in the browser, the whole app would be reloaded, right?

      That would trigger a PLR, so your job is to build that PLR so it can handle a reload and track correctly (i.e. take into account the current position in the SPA).

      Does that make sense?


      1. Actually we are working on single page application which doesn’t reload/refreshed so there is no page load rule. we need to create EBR’s for page load and event.


      2. In my comment, I mean to say the URL is not changing when you reload so pushstate/hashchange won’t work for us. and also there is no page load concept in SPA.


      3. Well, when you hit reload, the whole app is reloaded, including the HTML. You can absolutely track that as a page view. It’s the same as when you initially load the SPA, no?

        I’m not an SPA expert, but I always thought that the browser has to load at least some HTML, that this HTML includes the tag which loads Launch, and that Launch sees the first load as a Page View. Is that not so?


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.