Delayed Page Load Tracking with DTM

This article is dedicated to my father, Gerhard Exner, who passed away much too early 6 years ago. It would have been his 74th birthday today.

I want to pick up where I left off last week and describe how to build “delayed page load tracking”.

I can think of a couple of situations where this would be useful or even vital:

  • Search results pages that asynchronously load the results
  • Product Detail Pages that lazy-load some attributes (availability is a favourite)
  • Single-page apps, “fully AJAX’d” sites that do everything via DOM manipulation

They all have one thing in common: normal Page Load Rules (PLRs) won’t work, because when they fire, not all information has been loaded. The ideal would be a PLR that fired “a little bit later”, or “on demand”, but since we don’t have that, we’ll build something.

We are going to use an Event-based Rule (EBR). We also have to tell the Adobe Analytics Tool to not track on page load, otherwise we’ll get inflated numbers and unnecessarily high bills.

I have already described how to delay tracking using DTM, but the solution I show you today is better: it uses DTM rules, which means your friendly marketer can use the DTM UI to make changes or add new stuff that she needs.

Clean slate

We don’t need delayed loading on all pages, so I suggest we introduce a Data Element “Delayed Page Type” that is set to either “normal” or “delayed”. I would default this to “normal”.

I would set the Data Element by reading from an attribute in the data layer, maybe something like

Note that this needs to be set in the page as early as possible! I would have it in the <head> of the page!

Ok, we’re all set, let’s build this.

First of all, we add code that disables the standard tracking for the Adobe Analytics Tool. This is pretty simple: just go into the “Customize Page Code” editor and put return false; if the “Delayed Page Type” Data Element is “delayed”.

Checking the Data Element means tracking will work perfectly normally on “normal” pages. On the “delayed” pages, though, we now have no tracking.

Event-based Rules

For your tracking, we will use Event-based Rules (EBRs), which we will trigger with CustomEvents. The mechanism can be built once, then re-used for every single rule you build, no matter what page. The “Conditions” section within the rule still allows you to specify on which page to fire the individual rule.

So, in the Conditions section, specify “Event Type” as “custom” and type a name into the box on the right.

Tick the “[x] Apply event handler directly to element” box and type “#content” into the “Element Tag or Selector” box, or the ID over any other <div> that you want to use for the event.

The Conditions should look like this:

Conditions on the Event-based Rule
If the rule is specific to a page or template, you can add a condition, just like you’d do in a PLR.

In the “Adobe Analytics” section, make sure you specify “s.t() – increments a pageview” under “Tracking”. This is what makes this rule track a page view!

Analytics Section in Event-based Rule
You can now configure this rule just like you would a normal PLR. Specify a Page Name (using a Data Element) and all other “variables” and events that make sense for you.

That’s your tracking covered.

But the EBR doesn’t fire. It currently waits for a CustomEvent that never comes!


It doesn’t matter what kind of framework you use, whether your events are facilitated using jQuery’s trigger() method or anything else, we can always build a handler for yours that then trigger a CustomEvent for DTM.

Thing is: we have to do that anyway.



We must wait for 2 Events
Well, we need to wait for two things:

  • The event from the page, telling us everything we need is ready (which could arrive at any time before, during, or after DTM has processed the PLRs), and
  • the built-in DTM PLR logic to be done.

The latter is not available out of the box, but it is easy to build.

Back in the code editor in the Adobe Analytics Tool’s “Customize Page Code” section, we add a couple more lines of code. All we want the tool to do is a) not track (we have done that up in the “clean slate” chapter), and b) trigger an event after that. After it hasn’t tracked.

Analytics Tool: Customize Page Code
Here’s the code if you want to copy it:

var doWeTrack = true;

// lastly, suppress tracking on "delayed" pages
var delayed = _satellite.getVar("Delayed Page type");
if (delayed == "delayed") {
    // send an event that PLR-based tracking is done
    setTimeout(function() {
    }, 25);
    doWeTrack = false;

return doWeTrack;

In lines 4 & 5, we check whether we should do normal or delayed tracking on this page. Lines 6 to 10 are for pages with delayed tracking.

On lines 7 to 9, we tell the browser to wait 25 milliseconds, then trigger the “plr-done” event (I made that name up, in case you wonder). Those 25 milliseconds should be enough for DTM to have finished its own PLR logic, so by the time the “plr-done” event fires, PLRs should indeed be done.


Now the only thing missing is something that brings it all together — the event handler / translator.

I use a PLR to load the handler, that way I can control where and when it gets loaded (only on pages that use delayed tracking, for example).

The PLR is set to “Top of Page” because I want the handler / translator to register as early as possible, just in case the data is ready early in the process.

The code goes into a “Sequential Javascript” block in the “Javascript / Third Party Tags” section of the rule. I guess a “Non-Sequential” block would work, too, but I want my handlers to register as early as possible…

Event handler / Translator
// clean slate
window.jeTriggers = {site: false, dtm: false};

// event handler for the asynchronous content
$(document).on("async-ready", function() { = true;
    if (window.jeTriggers.dtm) {
        var ne = new CustomEvent("myCustomDelayedPageLoadEvent", {"bubbles": true});

// event handler for the DTM page load rule
$(document).on("plr-done", function() {
    window.jeTriggers.dtm = true;
    if ( {
        var ne = new CustomEvent("myCustomDelayedPageLoadEvent", {"bubbles": true});

Line 2 creates a variable that allows us to see whether one or both of the handlers have been called. Remember: we only want the CustomEvent to fire once both have been called, not earlier.

On lines 5 & 14, we register handlers for the two events we’re waiting for. Lines 4 ff. handle the “external” event that our backend fires, while lines 14 ff. handle the event sent after DTM has done all PLRs.

The mechanism for the handlers is the same:

  1. set “my part” of the variable to true
  2. check whether the “other part” is true as well
  3. if so: fire the CustomEvent

Makes sense?


If you want to see all of this in action, with some added _satellite.notify() calls for visual debugging, head over to a test page.

On that page, you have to simulate the external event, of course, but everything else is exactly as described here.

Example Page in Action
Btw: I recommend you also build a test page first. The great thing about having things in rules is that you can copy the rules when you need them!

Having a blue print ready is good practice! If not best!


This is not the most straight-forward solution ever, I must say. As such, there are caveats and things you have to keep in mind.

On disabling tracking at Tool level

We are killing the standard tracking at Tool level! You can leave all your PLRs as they are, no tracking will happen.

The slate is not totally clean, though.

Although the Analytics Tool does not fire actual tracking requests, your PLRs are still being executed, meaning they’ll set “variables” quite happily.

I sort of like that, but I’m also aware that it adds complexity to the setup, so I do not plan on making use of it explicitly (e.g. by having a “general” PLR that sets lots of “variables” and a specific delayed EBR that only adds to that. Totally possible, and might be a perfect match for your site).

One thing to keep in mind: all your custom Javascript code will also still be executed! The PLRs will work as usual! Careful!

If you think that could be an issue, you could always put a condition into your PLRs, based on the Data Element being “normal”. That would prevent the PLRs from firing on “delayed” pages.


The mechanism I described ensures you can use one single tracking call on pages that load important things asynchronously.

There is probably a sweet spot for this, meaning you would not want to use it on a page that loads stuff 5 seconds later. My guess is that the sweet spot is anything from 0 to 1 seconds average delay. Go beyond 1 second and it might make more sense to use two tracking calls…

It might make sense to add a time-out to the solution.

The time-out would fire the rule after a second, no matter what, therefore making sure the page load would be tracked at some point, though the tracked data would be incomplete.

I guess your friendly marketer might have an opinion on this one, and my hunch would be she’d be in favour of a time out. Counting the loading of a page is a pretty crucial thing, more so than completeness of data.

So knowing myself, I’d say there might be a version 2 of this article at some point, including a time out… or if anyone else wants to step in, go right ahead!

Multiple Tracking Calls

Note: the Analytics Tool knows how to merge the effects of PLRs into a single tracking call. It does not do that for EBRs, so if more than one of your EBRs fire, you’ll have more than one tracking call on the page!

The usual best practice approach suggests to use “one PLR per topic”. For this pattern — tracking with EBRs — you have to instead use a “one EBR per page” approach.

CustomEvents & the DOM

I have tried to fire the CustomEvent simply on the document or the body element of the DOM, but I have not been able to make that work so far.

If you have managed to do something like that, something that doesn’t depend on specific elements in the DOM, please let me know!

For now, we’ll just fire the events on an element called “content”.


I have run into a gotcha with this solution: we register our event handler / translator as early as possible using a “Top of Page” PLR. We have to do that, in case the external event comes early.

But I have seen at least one case where jQuery was loaded multiple times on the page.

Unfortunately, when that happens, all event handlers that you have registered before are forgotten.

We have to register our event handler after the last loading of jQuery.

That can be a challenge, and the best solution is, of course, to only load jQuery once. And do it early.


By default, a CustomEvent does not bubble!

(Am I the only one who thinks that reads funny?)

You can easily set the Event so it does bubble, and depending on your DOM, that might be a good idea.

As I wrote, I wasn’t able to make my EBR work unless I attached the handler directly to a <div> and specified the ID, but who knows, you are probably a better coder than I am.

So, to specify the event should bubble, just create it like this:

var ne = new CustomEvent("myCustomDelayedPageLoadEvent", {"bubbles": true});

Now how do I get this clown fish out of my head?

Ah, yes: “just keep swimming, just keep swimming”

12 thoughts on “Delayed Page Load Tracking with DTM

  1. I love the spirit and intent of this post! Often it’s a challenge to scrape the page and get the data into Analytics before Analytics actual executes the s.t() call.

    I modified this a bit for my use case but the general framework was ideal as you laid out Jan.


  2. I have an idea on how to implement the event firing from the body tag, assuming that you only have one body tag on the page:

    var ne = new CustomEvent(“myEvent”, {“bubbles”: true});

    It does work!


  3. I was wondering if anybody tested that method on compatibility with different browsers since CustomEvent method was not widely supported by legacy versions, especially by IE?
    It would be great if you could share your experience in comments.


  4. In Chrome, On the confirmation page, I am getting incomplete image request

    While in Firefox, I am getting complete image request.

    I would like to tell you that I am using setInterval() function on confirmation page as well as on the previous page i.e payment page as well.

    May i know what could be the potential reason behind it ? and how come delay function results in incomplete tracking ??


    1. Hi Rajdeep,

      Good question.

      It might make sense to use Charles or Fiddler to see what actually happens. Could be that Chrome reports the wrong thing?

      Delaying the call itself should not lead to incomplete requests.



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.