Google Tag Manager: The History Listener

There’s a new listener in town! It’s a few days now since the Google Tag Manager team unleashed the History Listener, and the time has come for me to tell you what this baby can do.

The History Listener is designed to be used on websites where content is loaded dynamically. Typically, these websites make heavy use of AJAX (Asynchronous JavaScript and XML), which is designed for loading content in the background and serving it dynamically without having to reload the page.

AJAX basics
Because I don’t want to go too deep into how a dynamic website works, this tutorial should be pretty simple. The key is to understand what events trigger the History Listener, and how you can track your page views in a website without static URLs bound to static content.


A site which loads content dynamically is usually incompatible with certain default browser events. For example, if you have a site which dynamically loads its content without any change in the URL of the page (nothing happens in the address bar), what do you think happens when a visitor navigates away from your site and then clicks the browser Back button? I can give you a hint, they won’t see whatever dynamic state the page was in just before they left.

This is, of course, a problem for search engines as well, since they want to index pages with unique URL addresses, which each return a static, unique piece of content. This is why URL fragments (AKA anchors AKA hashes) were introduced to solve the problems with browser history. If you loaded content clicking an “About Us” link, your URL would have looked like

This fixed the problem with browser history, since now you could detect changes in the URL fragment and serve appropriate content again, even if the user used the Back button. However, this wasn’t good enough for search engines or for Google Analytics, which have a hard time dealing with URL fragments.

Enter the hashbang (coolest name to be ever given to anything anywhere). The hashbang is a hash followed by an exclamation mark: #!. It’s an old invention, having existed in UNIX systems to facilitate script loading. Google adopted it as a schema to signify an AJAX site with crawlable content. When search crawlers came across the hashbang sequence, they translated it to a proper URL query parameter (_escaped_fragment=the-original-fragment). A query parameter meant that the page could be crawled and indexed for search engine users to find.

However, the hashbang isn’t the easiest solution to implement, it looks absolutely horrible, and it’s just not how you should treat your URLs.

The final chapter of the story, and the most relevant one for the History Listener, is brought about by the HTML5 standard. HTML5 introduced the window.history API, which can be used to manually manipulate browser history when loading content dynamically. You can now manually push a state into the browser history, and when someone navigates with the browser’s Back button to your page, you can look for stored states and load them in order, just like the default behavior of a static website.

The History Listener listens for changes in browser history. If it detects such a change event, it will tell you what happened, whether there’s a state object stored in history, and if there was a URL fragment change involved. You can use this information to send virtual page views, prevent certain tags from firing again, serve dynamic tags, and basically anything that you’d normally do when a page is loaded.

Set up the History Listener

You set up the History Listener like you would any other listener.

  1. Create a new tag
  2. Name it wisely
  3. Set Tag Type to Event Listener > History Listener
  4. Set firing rule to All pages ({{url}} matches regex .*)

The History Listener
And that’s it. This will set your listener up on all pages to wait for the triggers that activate it.

You’ll also notice that a bunch of new auto-event variables have been provided. You can use these to access properties of the history event change after the History Listener has fired. Read more about these in my Macro Guide.

The triggers

The History Listener will activate every time one of the following occurs:

  • Call to the window.history.pushState() method
  • Call to the window.history.replaceState() method
  • A popstate event is detected
  • The URL fragment (hash) changes

window.history.pushState() is used to add a state into the browser history stack. pushState() takes three arguments: a state object, a title, and a URL.

The state object is where you store details about the content, which you can use later to serve the same content to the returning visitor. The title is just a way to name the history entry, though it can be used with popstate to set the page title. The URL is what will appear in the address bar of your browser after the pushState() csll is completed. This is especially useful, because your site visitors can share this “virtual” URL, which can be then used to serve the correct content to visitors arriving via this link.

window.history.replaceState() is pretty much the same as pushState(), except that it replaces the current history state with the new state.

popstate is what occurs every time the active history entry changes. For example, the browser’s Back and Forward buttons trigger popstate. If the history entry which is activated upon popstate was created by pushState() or replaceState(), the popstate event will have the stored state object as a property. You can use this object to serve the same content to people navigating through browser history to your AJAX site.

Note! There are some differences as to how and when browsers send the popstate. Chrome and Safari, for example, fire a popstate with every page load (since that’s when the active history entry changes), but Firefox doesn’t. Firefox requires a history entry that was created by a pushState() or replaceState() call.

URL fragment changes are remnants of the hashbang era, but fragments are still widely used on websites. For example, if you have internal anchor links, you’ll trigger a fragment change event every time the visitor jumps to an anchor on your page. In AJAX sites, you can listen for fragment change events, and use these to send a page view or fire a tag of your choosing.

{{event}} equals gtm.historyChange

When the History Listener is running, and any of the events described in the previous chapter occur, a gtm.historyChange is pushed into the data layer. Most of the properties in this event can be accessed by the auto-event macros, but it’s good to know what the data layer entry looks like.

Here’s what your dataLayer object looks like after a pushState() event. Here, the state object I push is very simple. It contains just a single key-value pair, page: “About Us”.
Data Layer after pushState()
Here’s what you dataLayer looks like after a replaceState() event. The parameters are the same as in pushState(). Note that I fired replaceState() just after pushState(), so you’ll see the previous state object stored in the gtm.oldHistoryState property!
Data Layer after replaceState()
Now, if I navigate away from the page and return with the browser Back button, a popstate event occurs with my state object neatly stored within:
Data Layer after popstate
As you can see, the old state and the new state are the same, because I navigated away from the Contact page and arrived on the same page after clicking Back in my browser. When popstate occurs, I can look into the history state object and send the correct page view for the dynamic content. In this case, I can see that the visitor returned to the Contact page.

Finally, here’s me navigating from one URL fragment to another:
Data Layer after fragment change
As you can see, I first went to the bottom of the page via URL fragment #bottom. This triggers a popstate event, with #bottom as gtm.newUrlFragment. After that, I clicked “Back to top”, which transported me to URL #backtotop, and this is reflected in the screenshot above.

Example of use

I’ll show a quick example, expanding the idea of navigating to the “Contact” page, where the page details are stored in a state object, created using pushState().

The premise is that when your visitor clicks a link to the “Contact” page, the contents are loaded dynamically. To account for history changes, the pushState() method is called with something like:

This pushes a new entry into your browser history stack, with a single property within the state object. This property is the one we’ll want to access in a popstate instance, i.e. when someone returns to the contact page via a history event (browser Back, for example).

  1. Make sure your History Listener is running
  2. Create a new Data Layer Variable macro to identify what history event took place:
  3. History Change Source macro

  4. Create a new Data Layer Variable macro to get the page property of the new history state:
  5. New macro for Page property

  6. Create a new rule for your page view tag:
  7. Rule for the History Change

And that’s it. You can attach this sequence to your page view tag, for example, ensuring that the page view is sent every time someone navigates back to your Contact content via browser history (see below for how to make sure the page view properties are in order).

The firing rule is important. You’re looking for a gtm.historyChange event. This is pushed into the data layer when the History Listener is triggered. After that, you’ll want to make sure that the event was popstate (the macro in (2)), and that the page property of the new history state has value “Contact”.

Now when you send a page view, the URL will be correctly contact.html (since you provide that in the pushState() call). However, your document title might be off, unless you explicitly set it in your AJAX logic. A nice way to send the page title is to use the Document Path setting in your page view tag, and populate it with the value of a Lookup Table macro, which in turn evaluates the page property again. Allow me to demonstrate:
Page View tag with document title
That’s your page view tag. In the Document Title field, you’re retrieving the value from a Lookup Table macro, which I’ve named {{ AJAX – Page Title }}.
Lookup Table from AJAX Page Title
This sets the page title depending on the page property, pushed into the data layer when the history listener is fired. You could set a default value to the Lookup Table macro, but on the other hand, if it doesn’t return a value, the tag will just send whatever is found in the document.title property.


Phew! This turned into a monster of a tutorial. But that’s to be expected. Working with AJAX and dynamic content is not easy, as there are so many things to factor in. Not only are there usability and browser compatibility issues involved, you’ll also need to take search engines into account, and you’ll need to understand how history events work.

As always, I highly recommend that you brush up on your JavaScript before you start working with the History Listener. There’s no damage to be done, but you don’t want to screw your tracking by not understanding how state objects work, for example. Also, it’s very likely that you’ll have to return to your AJAX setup to make sure that pushState() calls and replaceState() calls provide all the necessary data for you to use.

Using the History Listener on a dynamic site is so much better than resorting to Link Click Listeners. More often than not, you’ll come across event propagation problems with your listeners, since most of AJAX navigation is designed to prevent the default behavior of links. With the History Listener, you’re only listening for browser history events, so on-page conflicts won’t affect the listener’s basic functionality.

You’ll need to be careful and precise with your rules. Make use of all the properties of the gtm.historyChange event, because you’ll want to do different things upon pushState() and popstate, for example.

And if you have any questions, drop me a mail or leave a comment below!


  1. says

    That is indeed a long (and complex) tutorial Simo. As usual, your pick & shovel work is much appreciated.

    I am just wondering about the applicability to sites built with Flash (yuck!)? Are there another scenarios where these new listeners provide GA value?

  2. Audrey says

    Hi Simo, thanks for this great tutorial!

    I’m wondering if you’ve run into issues with tag firing sequences. I noticed that the History Change tag fires into the data layer before other listeners. For instance, if I have an event that fires on on a button on page A, and the button leads to Page B, for which the pageview is fired using the History Listener, the pageview will fire first, then the event. This somewhat skews data as the event appears to have fired on page B rather than page A. Any idea why that might be or if there is a way to fix this? Thanks!

    • says

      Hi Audrey,

      Is your navigation button wrapped in a link (a href…) tag? If it is, you could use the link click listener for your event, as it has a built in “Wait for tags” function, which ensures that all dependent tags are fired before the link action is completed.

      Otherwise, I would suggest you introduce a short delay (500 ms or so) to your page change method to give time for the click event to be sent before the URL changes.

      It’s an interesting phenomenon, nonetheless. Do you have an onClick event or some other non-GTM handler bound to your button, controlling the page change? Of so, there’s probably a race condition at play here, with the page change method taking place first since it’s “closer” to the click occurrence, before the event has time to bubble up to the GTM click listener, waiting on the document node.

      Hope this made sense :) I wrote about GTM listeners a little time ago, and the text might be relevant in this case as well.

      • Audrey says

        Hi Simo,
        Thanks so much for your answer. Your GTM listeners article is very useful. Unfortunately, those links were not picked up by the link click listener so, like you said, there probably is some condition at play.

        I ended up getting some help to create a custom tag that does a dataLayer.push on mousedown which seems to have fixed my problem. I just wish there was a built in mousedown listener in GTM!

        Thanks again for your precious help!

      • says

        Hi Simo,
        Your blog has proved to be a gold mine of information regarding GTM. It’s been on a very short its of mine while learning how to properly tag sites with GTM.

        Unfortunately I’m having a small problem with my history listener on one of our clients sites. There are 4 filters on the page, when you click on one, the content filters by a certain criteria and the URL changes (isotope functionality). So my history listener fires both a gtm.historyChange AND a gtm.hashChange when one of the filters is clicked, and I get my virtual page view. All is fine.

        However on the last filter, “see all,” it fires the gtm.historyChange and gtm.hashChange twice, rendering two page views. Neither my developers or I can figure it out and I’m hoping you can spare 5 minutes to take a quick look?
        I can send you the URL via email.

        I understand if you don’t have the time to check, but thank you just the same for your contributions to digital analytics!

  3. R says

    What about browsers like IE8 and IE9 – they dont support html history push API. When GTM virtualpageviews are setup with the hitory listener, it doesnt capture the data for these browsers. Any workaround É

  4. R says

    Thanks, tried it but doesn’t work with the GTM listener, thanks for the reply though.

  5. says

    Hi Simo,

    Your blog is excellent and I have been referring to it nearly daily for a month!

    I have a site that (isn’t live yet) uses a filtering method such as this and we’re going to implement anchors in the url to identify changes to the content. Am I able to use this History listener to track virtual pageviews that will capture the value after the anchor? If so how can you share in the simplest for what I’d need to do?

    • says

      Hey Kris,

      Every click on an anchor link should fire the listener, since it’s a change of browser state. If you have a History Listener on, every time an anchor link is clicked it fires and pushes a data layer variable gtm.newUrlFragment into dataLayer. So create a new Data Layer Variable MAcro which refers to this variable.

      Then you can have tags with e.g.

      {{event}} equals gtm.historyChange
      {{url fragment}} equals about-us

      to fire a tag such as remarketing only on the #about-us page.

    • says

      Hi Krister,

      I’m sorry but I don’t quite follow. What select item functionality? What tags, rules and macros do you have in place? I need a bit more information before I can give any assistance.

  6. says

    Hey Simo,

    Absolutely great blog. I have learned a lot from reading your content. Unfortunately I am a terrible time trying to get my history listener to work. I have followed your steps exactly in order to set up the listener. None of my macros are being populated though. I have even turned off all of my other listeners to make sure none of them are interfering but the history tag only fires ones and no what I do with my website none of the fields are populated.

    I am trying to set up link anchor tracking and I have tried every method I can find and NOTHING works. I am at my wits ends with trying to figure out what is going on.

    Here are some of the different methods I have tried:

    • says


      Without an example (URL link) it’s pretty impossible to shoot guesses what might be the problem. It could be in your tags/rules, it could be in the AJAX on your page, it could be an interfering listener which prevents the hashchange from occurring.

  7. says

    Hey There,

    Thanks very much for this write up!

    It seems like with this method, you’ll need to manually set up a separate rule for every single page on your site. This seems like a lot of work!

    I am currently working on a site that changes the URLs but loads the content via AJAX, which poses similar tracking problems posed here (gtm/ga script isn’t auto loaded on state change)

    Is there anywhere that a step could be taken to automate the process further? Such as, after a history listener is in place, automatically fire a virtual pageview on state change that dynamically sets the page url and title?

    • says

      Hey Max,

      That’s exactly what the History Listener is for. Either track pushState() or changes in the hash, and then send a Virtual Pageview. The reason why pushState() is better is because you can add details about the page in the state object, such as title and page path, and pull these into the tag.

      The thing about single-page apps is that the dataLayer persists. So if you’ve set pageTitle in an earlier push, and you neglect to set it with some later page load, GTM will access this earlier value since it’s already in dataLayer. So you’ll have to remember to update all required variables with every push.

  8. Anouka says

    Hi Simo

    Always a pleasure to read and use your tutorials.

    And now, I’m stuck.
    For a client’s AJAX-based website (GA sees the whole thing as 1 URL) I wanted to get better insight into the pageview-volume and -ditribution. So I followed your steps for the popstate-setup (I cannot see any HistoryState-changes, just the changing URL Fragments). I’ve my Pageview-tag set up as such that the ‘History New URL Fragment’ is being sent as the Document path.
    Now, something weird seems to happen: sometimes it works, sometimes it doesn’t :-( When I watch my activity in the GA realtime view, I sometime DO get the URL-fragment to show in the full URL and sometimes it just shows ‘/’ without anything following. Even if this concerns the same content.
    First, I thought it had something to do with that content being loaded after TYPING in the direct URL in the adress bar (including hashtag) in stead of loading it via a click – and that makes a difference as well (since there is no history change taking place) – but it also happens when I’m obtaining the content through clicks in the page. What can be wrong in my setup that the results are so unpredictable?

    I do appologize if my story is unclear and full of wrongfully used terminology; I am not a programmer, merely a consultant trying to use GTM to the fullest (of my capacity).

    Thanks so much and looking forward to seeing you speak in London in two weeks :)

  9. says

    One question: Does

    var page = {page:”Contact”};

    have to be put in the code? I’m trying to change the History – New State – Page Property = a registration page. How does it know that page?

    • says


      Yes, it’s most likely that your developers will have to control the pushState() calls in the AJAX (or other) functions which control what content is shown with each navigation interaction.


Leave a Reply

Your email address will not be published. Required fields are marked *

Please do not write HTML or other formatted code in your comments!