≡ Menu

Google Tag Manager for Single Page Applications

Single Page Applications are becoming more and more common as web developers adopt asynchronous technologies such as AJAX, Node.JS and Knockout. One of the challenges of adding a web analytics solution such as Google Analytics to a Single Page Application, is the browser location (site address) does not change as a user performs actions on the site. With Google Analytics, you can emulate a pageview by explicitly calling the _trackPageview function passing in a virtual page path (e.g., /home).

Now let’s say you want to implement Google Tag Manager to take advantage of the flexibility of being able to fire Google Analytics as well as some AdWords conversion tracking code as actions take place on your Single Page Application. Because the browser’s location bar doesn’t change, the traditional Google Analytics setup in Google Tag Manager won’t cut it. You need to leverage the event macro to tell Google Tag Manager that a ‘pageview’ has occurred. Let’s get to it.

In your Google Tag Manager container, add a new rule called ‘virtual pageview’ and set the event macro {{event}} equals ‘pageview’.  Don’t forget to click Save.

Now we’ll create a new Google Tag Manager macro that watches for a virtual url to appear in the data layer.  Note: If you aren’t sure what the data layer is or how to implement one, take a look at Justin Cutroni’s Data Layer post as well as the Google Tag Manager data layer help page.

  1. Enter a descriptive name for the macro such as ‘virtual url’
  2. Set the Macro Type to ‘Data Layer Variable’
  3. Set the Data Layer Variable Name to ‘virtualUrl’ (Note: this is the variable name you’ll need to specify in the Data Layer for the URL you want to track in Google Analytics)
  4. Click Save

It’s time to create our Google Analytics tag to fire when a new virtual pageview occurs.

  1. Enter a descriptive name for the Tag
  2. Choose a Tag Type of ‘Google Analytics’
  3. Enter your GA Profile ID
  4. Set the Track Type to ‘Page View’
  5. Expand Basic Configuration
  6. Check the Virtual Page Path box
  7. Click the +{{macro}} button
  8. Select the virtual url macro you created earlier
  9. Add a firing rule for the tag and choose the ‘virtual pageview’ rule created earlier
  10. Click Save


Now to enable the new tag along with the macro and firing rule, you’ll need to create a version of the tag container and publish it.  Now it’s time to populate the data layer with a virtual url when a user changes the view on our single page application.  This might look something like:

  dataLayer = [];
function loadProductDetails(productIdentifier) {
// code to load product details and display to user
<!-- Google Tag Manager -->
<noscript><iframe src="//www.googletagmanager.com/ns.html?id=GTM-3VLTP"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
<!-- End Google Tag Manager -->
<a onclick="loadProductDetails('nexus7');">Nexus 7</a>

Now when a user clicks to view the the product details for a Nexus 7, the loadProductDetails function fires and pushes a new item into the data layer.  Google Tag Manager is watching the data layer and fires the ‘virtual pageview’ rule which triggers the Google Analytics Virtual Pageview tag passing in a virtual URL of ‘/product/nexus7’.  (‘nexus7’ is the productIdentifer parameter passed into the loadProductDetails function.)

Verify it’s working by viewing the Real-time Analytics reports for your GA profile.  You should see the ‘/product/nexus7’ pageview listed under  the ‘Top Active Pages’.

Have questions or comments?  Get in touch with me on Google+.

Comments on this entry are closed.

  • http://alexanderfilatov.se Alexander

    Hi Dan,

    Great article! There is so little information about GTM since it’s so young still, so quality writing is always appreciated.

    I’m wondering how would you handle GA event tracking on a one-page site with loads of AJAX?
    Let’s say I push to dataLayer an event with category, action, label and value. Those values are now in the dataLayer. Next I want to track an event that only requires category and action, so I push just that into dataLayer, but GTM already has label and value from the previous push so it will use those, wouldn’t it?


    • Dan

      Hi Alexander,

      Thanks for stopping by. As for GA event tracking via GTM, that’s a good question. I would wrap the dataLayer.push for events into a JavaScript function that checks for undefined values for the label and value. If you do this, then you can just exclude those parameters when you call the event function. It might look something like this:

      function someEvent(eventCategory, eventAction, eventLabel, eventValue) {
      if (typeof(eventLabel)===’undefined’) {
      eventLabel = ”;
      if (typeof(eventValue)===’undefined’) {
      eventValue = ”;

      ‘event’: ‘event’,
      ‘eventCategory’: eventCategory,
      ‘eventAction’: eventAction,
      ‘eventLabel’: eventLabel,
      ‘eventValue’: eventValue

      then calling this function with a signature such as:

      someEvent(‘some event category’, ‘some event action’);

      I realize this isn’t the same as omitting the values entirely, but it’s better than the alternative and won’t negatively impact Google Analytics reports.

  • http://www.vipsingles.com Matt

    Hi Dan,

    Great article! We have a site that used AJAX for a multi-part lead form. I was able to use your setup to get our Analytics working perfectly. I’m hoping you can help with the Adwords Conversion setup as well. Once they submit the 2nd part of the form, we consider that a conversion. On our other sites it just goes to a “thank you” page where the tag fires for the conversion.

    So considering the AJAX setup referenced in your article, how would I set it up for an Adwords Conversion?

    Thanks for your help!

    • Dan

      Hi Matt,

      Glad I could help. I would suggest pushing an event to the data layer when the user submits the 2nd part of the form. Since your form is submitted via AJAX, I would add the dataLayer push to the success portion of the callback. Assuming you are using jQuery, it might look something like this:

      type: “POST”,
      url: //url of your service listening to form post,
      data: //your form data would go here,
      success: trackConversion, //this is where you would call a function to push an event to the data layer – see below.
      dataType: //json, etc.

      function trackConversion(data)

      In the Tag Manager UI, you would then create a new tag with a type of ‘AdWords Conversion Tracking’ and populate the ID, label and value fields. Finally, to fire this conversion code, you would add a rule that triggers when {{event}} equals registrationSuccess. Publish and you’re good to go!

  • http://VIPSingles.com Matt

    Dan this worked great, thanks. PS – I highly recommend anyone using tag manager download the Google Tag Assistant for the Chrome browser for testing purposes.

  • Natalia Kazachenko

    Great article!

    After testing it with Universal Analytics I have a couple of additions though:

    1) Virtuial page address should end with .html, otherwise it won’t work (it will track the actual page user’s on)

    2) The field in GTM tag for Virtual Page Path is named Document Path if you use Universal Analytics

  • http://www.primecut.gr Giorgos

    Hi Dan,

    This is really a nice tutorial, easy to understand and apply it to my needs. I tried many things around but your sample seems to be working for me without trying hard.


  • Hans

    Dan, I’m working on integrating GTM into my SPA (powered by Backbone + chaplinjs).

    I do have url being updated (chaplin does this via backboneHistory). I setup a regular page view tag to fire whenever the url change. More details here https://stackoverflow.com/questions/25003389/google-tag-manager-with-chaplinjs

    I couldn’t get it to work after the initial page load. I would image that GTM would have listened to change in history and be able to push out my page view event.


  • Favorfly


    do you maybe have an updated version of this article, for GTM v2? Their terminology is giving me enormous headaches; rules are now triggers, macros are… You get the picture.

    Many thanks!

  • Igor G

    Thanks for the great article, but there is a problem.

    Whenever you hit ‘Nexus 7’ button, Google Tag Manager would add all the tags mapped to ‘virtual page view’ event to the DOM every time. In real world single page app, with multiple ‘pages’, multiple tags per page, the DOM would get polluted and huge pretty quickly. That would lead to memory leaks, slowdowns, and other problems.

    How do you deal with this?