Outbound Link Tracking With Google Universal Analytics

If you use Google Analytics on your website, you will be aware of the new tags that they have been rolling out.

Old Tags – ga.js

 var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-xxxxxx-x']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();

New Tags (Universal Analytics) – analytics.js


  (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
  (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
  m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
  })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
  ga('create', 'UA-xxxxxx-x', 'mydomain.com');
  ga('send', 'pageview');

No doubt over time they will gradually encourage users to adopt the new code, and, eventually cease supporting the older ga.js.

Unfortunately the new code breaks any custom event tracking you have set up. I use event tracking for outbound clicks. It’s handy to look through Analytics and see what users are clicking on, and from what pages.

Here’s how to do this when using Universal Analytics (using vanilla Javascript – no jQuery needed).

Tracking Outbound Links


//Track outbounds
<script>
(function trackOutbounds() {
	
	var hitCallbackHandler = function(url,win) {
	    if (win) {
		    window.open(url, win);
	    } else {
        	window.location.href = url;
        }
    };
    
    var addEvent = function(el, eventName, handler) {
    
		if (el.addEventListener) {
			el.addEventListener(eventName, handler);
			} else {
			el.attachEvent('on' + eventName, function(){
				handler.call(el);
		    });
		}
	}
	
	if (document.getElementsByTagName) {
		var el = document.getElementsByTagName('a');
		var getDomain = document.domain.split('.').reverse()[1] + '.' + document.domain.split('.').reverse()[0];
		
		// Look thru each a element
		for (var i=0; i < el.length;i++) {
		
			// Extract it's href attribute
			var href = (typeof(el[i].getAttribute('href')) == 'string' ) ? el[i].getAttribute('href') : '';
			
			// Query the href for the top level domain (xxxxx.com)
			var myDomain = href.match(getDomain);
			
			// If link is outbound and is not to this domain	
			if ((href.match(/^(https?:|\/\/)/i)  && !myDomain) || href.match(/^mailto\:/i)) {
			
				// Add an event to click
				addEvent(el[i],'click', function(e) {
					var url = this.getAttribute('href'), win = (typeof(this.getAttribute('target')) == 'string') ? this.getAttribute('target') : '';
							
							console.log ("add event", url);
					// Log even to Analytics, once done, go to the link
					ga('send', 'event', 'outbound', 'click', url,
						{'hitCallback': hitCallbackHandler(url,win)},
						{'nonInteraction': 1}
					);
					
					e.preventDefault();
				});
			}
		}
	}
})();
</script>

How it Works

  1. Use getElementsByTagName to get all a tags in the document (it’s faster than QuerySelectorAll and it supported by more browsers).
  2. Loop thru each of these links, and extract out the href attribute.
  3. If the href is external, and does not go to our own domain then add a click event listener.
  4. If the href is a mailto – also log this.
  5. When clicked we log an event to Analytics, and only perform the browser link when this is done (this is what the hitCallbackHandler is for).

Event Tracking Format

According to the Google guidelines, the format for an event track is this.

ga('send', 'event', 'category', 'action');
ga('send', 'event', 'category', 'action', 'label');
ga('send', 'event', 'category', 'action', 'label', value);  // value is a number.

In my example we are setting the category to “outbound”, the action to “click”, and the label to the url of the link. We don’t need to use a value.

You’ll notice I also add a variable {'nonInteraction': 1}  – this tells Analytics not to count the pageview as a bounce. If a user has clicked on something within a page, then I don’t want this to impact bounce rate as it means the user  has engaged with the page.

Reviewing Your Links

In Analytics navigate to Behavior / Events / Top Events and you can then drill down into your clicked links. You can also go Behavior / Events / Page to show your pages that had the most events, and drill down to see what users were clicking on.

eventtrack

YOU MUST place the script at the end of the page (just before the </body> tag).

To Do:

  • At the moment the script will not track social button links that have your URL embedded in them. I need to fix this.

Further:

See Todd Motto’s Flare – an event tracking script for Universal Analytics. It doesn’t track outbound links but allows for any kind of tracking by using data attributes on the link.

Advertisement
Hi, I'm James, and for the last decade I've made a living by making my own blogs and websites.
Updated: September 15, 2016

97 Comments

  1. oops sorry, I meant… it does not pickup outbound to email where href=mailto:… ,any change in code to pickup on email clicks? Thanks

  2. I really just need to track one outbound link, but in WP that link is in the menu, in buttons, and hard coded. Is there any way for it to just track a single URL?

    • Unless you can access the source, and add an attribute to the link, then there’s no way of only tracking this particular link.

  3. i could make this work with the full version of the code but not the minified version.

    • Thanks I removed the minified version. Not necessary to have it as there could be a multitude of ways different people deal with their javascript.

  4. I’m getting this error in the console: “Uncaught SyntaxError: Unexpected token ILLEGAL”.

    Could someone help with this, please?

  5. I am having a similar experience to Merx.

    • What’s the URL?

  6. Hi there,

    I have the universal analytics added in the header and I have added your script in the footer just before the tag. It doesn’t seem to do anything. There are no outgoing events in the GA/Top Events.
    Not sure what I am doing wrong? Thanks

  7. Any idea on how to change the var isThisDomain, so that it DOES log an external event if a user clicks to go to a subdomain of the site?

  8. And how to dothe same for Inboud Links ? I changed all outbounts links by plugin to inbounds links.

    • The blastam one requires jQuery – so that’s a big difference right there. The axllent one is similar. Whatever works for you is the best one!

  9. Hi,
    The code worked fine for one day , then I start getting syntax error messages: Uncaught SyntaxError: Unexpected token ILLEGAL
    I test the code with lint, that says:
    var addEvent = function(el, eventName, handler) {
    13
    14 if (el.addEventListener) {
    15 el.addEventListener(eventName, handler);
    16 } else {
    17 el.attachEvent(‘on’ + eventName, function(){
    18 handler.call(el);
    19 });
    20 }
    21 }
    22
    23 if (document.getElementsByTagName) {
    =^
    lint warning: missing semicolon

    I use Joomla that sometimes change all kinds of charecters while saving. but I dont understand where is the problem.
    Any ideas will be nice,

    • Lint is fussy about semicolons. But this is just a warning, and it wouldn’t cause an error in the browser – so the problem must be somewhere else. Maybe some invalid character has got in there somewhere?

  10. Your script doesn’t handle external links with no protocol (e.g. //www.example.com). If you change the match to href.match(/^(https?:|//)/i) it will handle this.

    • Excellent idea. Will add.

  11. Great Code-Snippet. I will try it this evening.

    But where do the script knows to which google analytics account it should submit the event?

    There’s no entry like: UA-xxxxxx-x?

    • The code assumes that you’ve already included the standard universal analytics code snippet from Google.

  12. Thank you so much for this code. Implemented the non-minified version on a Drupal 7.35 site at http://www.toyrider.com which is a review site for the best kids ride-on toys.

    It worked perfectly first time.

    I have been looking for a simple way to re-instate event reporting for outbound links in GA and I am deeply grateful!

  13. Hi James – I’ve implemented this code on a Drupal (7) site; it certainly works for mailto: links (in real time and Behaviour>Events), but it doesn’t seem to pick up external links.. which is kind of what I was hoping for.

    I’ve saved the code in the page.tpl file, right at the bottom – given that it’s picking up mailto:’s I would have thought that has confirmed the code itself is working. Do you know of anything else which I need to check/change to get this working?
    S

    • That’s odd. The fact that the mailto works means that you’ve got the code working.

      Basically it looks for links starting with http: (i.e. an absolute link), and then checks that the URL is NOT the same domain as the page you’re on.

      Works fine on one of my sites… so not sure?

  14. Hey James,

    Where do i put the code snippet if i am running Genesis framework for WordPress?

    Would appreciate the help, I have been trying everything to track outbound links and am hoping I can stop looking after this article.

    Thanks!

    • You can put the script into your child theme directory, and then enqueue it in the functions.php file.

  15. And how this events import to Adwords as conversions?

    • This is something you need to setup from within the Google Analytics dashboard (it’s beyond the scope of this article).

      • I know this does not describe the article because I am looking for help, like I defined target Analytics but I do not see either conversion or Adwords Analytics based on this goal

  16. Please, what will happen if there is bounce rate on my site, is there any problem for my visitors to click on links to direct them to other website?

    • No problems, this is not considered a bounce.

  17. Hi,
    This is all great info. I have been using the link tracking code for some time but noticed recently that when I use the tracking code in text view on WordPress (self-hosted site), then click to the visual editor, AND THEN BACK again, the tracking code disappears! I had this happen to me once after I’d entered in the tracking code on something like 30+ links. Frustrated would be an understatement.

    Has anyone else had this experience and is there a workaround to keep WordPress from screwing with my custom coding?

    Thanks in advance
    Bill

    • Hi Bill. I feel your pain and many times have watched WordPress destroy code.

      Ideally the link tracking code should be somewhere in the sitewide Javascript. But if you are embedding code into posts, there are two plugins that may help:

      One is the Raw HTML plugin (stops WordPress adding it’s jank into your code).
      The other is Always Edit in HTML – stops you from clicking to the Visual view

  18. Hi

    Is the script working also with links which have “target=_blank° included.

    If yes, it would be the right solution for my needs. Thank you in advance.

    regards Didi

    • YEs it works with different windows in target.

  19. Any chance of adding “tel” clicks too?

    • I’ll add it to the list of things to do.

  20. Hi, thanks, works great. I am not sure why you are matching domain only but in case somebody else needs to track subdomains (trigger events for links to other subdomain and main domain) change line:
    var getDomain = document.domain.split(‘.’).reverse()[1] + ‘.’ + document.domain.split(‘.’).reverse()[0];
    to:
    var getDomain = document.domain;

  21. I just tried this on my squarespace site how long should it take before I start seeing results?

    • Analytics normally is about 2-3 hours behind. Although you can test this on Realtime analytics, and click on Events. Then when you click the outbound link on your site, you should see the event flash up on realtime.

    • I’m trying to get this setup for my squarespace page and not having any luck. I’m using the code injector to paste into the footer. How did you implement it?

  22. This is great. I’ve got it up and working on my site. I’m not a programmer, is there any issues with putting this script into an external js file and linking to it so I don’t have to paste all this code on the page itself?

    • No you can put it wherever, as long as it is called at the end of the document.

  23. Thank you for this nice solution. Works brilliantly and I really liked your addition on bounce count.

    Please please include the “end of the page obligation” in the text for others. I was here because of target=”_blank” problem and just replaced the Google’s suggested code which was in the header, and it didn’t work and I just skipped to another solution which would not work either…etc. Luckily a few hours later I saw your comment and it was on top of others.

    Great contribution, thanks again.

  24. Hi James,
    This is exactly what I have been looking for! However, when I put it on my footer php page in wordpress, the code shows up on the webpage 🙁 any tips? I’ve also tried putting it on my plugin “Insert Headers and Footers” with the same result.

    • Hi Lauren. The code does need to be inside <script> tags. I’ve modified the code above to show this.

      • Waaaoow it works now! Thank you so much for your reply and helping a newb like me out 🙂

  25. can we use this script for track outbound links for particular domain?

    • Not quite sure what you mean. It will track outbounds to ALL domains where the href does not equal the domain that the page was served from.

      If you wanted to only track links to a particular domain, you would need to modify the script to only check for that particular domain.

  26. Hi,

    I tried implementing the script in order to be able to track mailto: links as events using Universal Analytics.
    However, the script seems not to be working as I can´t see any events being sent when testclicking mailto: links.
    What could be the issue? The script is correctly installed just before the closing body tag.
    Any help is greatly appreciated!

    With kind regards,
    Mike

    • Mike, the good news is I just checked on the site where I have this running, and it definitely tracked the mailto: (I could see it in the realtime events).

      The bad news is I may not have had the latest version pasted into this post, so I would encourage you to copy it into your site, and see what happens.

      • Hi Mike,

        Did you change the script after my last question? Would you mind sharing your latest version of the script please?

        With kind regards,

        • Yes I did. The script listed is all good to go.

  27. Awesome! Worked like a charm

  28. Looks like typo:

    win = (typeof(this.getAttribute(‘target’) == ‘string’)) ? this.getAttribute(‘target’) : ”;

    If a link has no ‘target’ this will always return null. Closing bracket is in wrong place.

    Should be:
    win = (typeof(this.getAttribute(‘target’)) == ‘string’) ? this.getAttribute(‘target’) : ”;

    • Thanks. Fixed.

  29. Using WordPress – I placed the tracking script at the bottom of the footer.php before the tag. Does this tracking work in Real-Time mode or just in Behavior/Events/Top Events? Thank you for making this as simple as one can make it.

    • Hi John, just tested it now. Events definitely appear in real-time.

    • Justin, that code is very different on two major fronts:

      1) It needs jQuery.
      2) It’s for the old Analytics tags.

      The code I have above does not need jQuery, and is for the newer universal analytics tags.

  30. Hi James,
    any update on getting this to work in IE8? I’ve placed it alongside our new tracking on an intranet which has majority IE8 users.

    • I’ve got some new code up on a site, needs more testing.

  31. Do you test with GTM (google tag manager)?

    • No, I just have the Analytics tags directly in my page. Have you used tag manager?

  32. @Dennis Because of “Step 2: Add the onclick attribute to your outbound links”, I suppose 😉

    • Ah, got cha. If you do not want to edit all your existing links, you can use the script above. What happens if you want to dynamically preface the label with text to tell me the page clicked on (or area of the page)? You cannot get that granular here – unless you wanted to add a faux tag to the link of, for example: linktrackinfo=”whatever-i-want” then we can append the label for more info. I will try out this code and see how it goes! Hopefully an IE8 update will be posted soon too. thx, great stuff! Dennis

      • Looks like I am getting an error on page load in Firefox latest version:

        TypeError: href is null
        On this line: var myDomain = href.match(getDomain);

        Any thoughts? same issue on the minified one. And I looked over to see if any copy paste issues, all good there.

        Thanks!

        Dennis

        • Fixed this. There was a problem if you had a tags with no href (such as anchor tags on your page with only a name attribute).

          This works now.

      • Dennis, the page the link was clicked on is available in Google Analytics. When you drill down into events, you can make it show Pages. This is one of the most useful metrics.

        • Looks like the error is gone – awesome. I will test out with a few links and see if they populate correctly. What I meant about the page is say you have a link to the same site in multiple locations – I used to code each link with (for this example) “link1-pagename”, “link2-pagename”, etc to see what was clicked. As I said above, you could probably add a unique tag to each href link and look up the value of that tag & append to give your solution more granular results. But, this is definitely a more advance “nice to have”. Thanks again! Dennis

          • Aaah right, within the same page. IT is possible to do this with Google Analytics itself (but it ONLY measures internal clicks).

            You have to add extra code to your Analytics call, and it doesn’t all kinds of crazy stuff to add unique numbers to each internal link.

            I’ve never used it myself.

        • Sorry for the extra reply. I think i found a bug. If you have target=_blank, your code stops the link from opening in a new window/tab. Can this be fixed? Thanks, Dennis

          • Thanks you are providing some great testing. This script was adapted from one that I had been using (but only worked on the old analytics tags).

            Clearly it had some shortcomings but we’re making it better and better! It should now handle windows opening in any target window.

            There’s various things still to do yet.

          • Yup, we are! The issue is that e.preventDefault(). Hmmmm.

          • Thanks for the help. I’m going to get back to this at some point and get the code up on github.

  33. Two things: (1) This code will throw an error in IE8 and below, since `.addEventListener` is not supported. (2) Also in IE8 and below, the referrer is not passed when navigating via `location.href`. This may affect your traffic source reporting since the subsequent pageviews may appear as direct traffic.

    • Oh and `.preventDefault()` is also not supported in IE8 🙂

    • Ahh the delicious irony – I just posted a new article on how IE8 isn’t dead yet!!!

      http://exisweb.net/ie8-will-not-die-quietly

      … and shot myself in the foot.

      In my situation I’ve made a choice not to include IE8 stats on outbound clicking BUT I do need to fix the code to make it test for Event listener first.

      Thanks for the heads up. Appreciate all the JS help I can get 🙂

    • Hi Dennis, as Uwe said – the sample google code means you have to manually edit every single link on your site.

      With this kind of script I’ve listed above, it covers every link on the site without having to edit content.

    • Thanks for the tip. Any advice re: my fumbling Javascript skills is most welcome.

  34. Hi James, can you please tell me what would be the best practice to use your code with WordPress? I wouldn’t want to loose the code after a WordPress update for example. Thank you.

    • Leon, again it depends in how you implement Javascript on your particular site. If you have any existing JAvascript files as part of your theme you can paste this code at the end. OR you can create a new Javascript file in your theme folder.

      You can then either use the wordpress register-script calls or call it directly from your footer.php

      <script src="<?php echo get_stylesheet_directory_uri() ?>/js/app.5.js"></script>

  35. oops sorry, I meant… it does not pickup outbound to email where href=mailto:… ,any change in code to pickup on email clicks? Thanks

    • Good point. It’s not something I wanted to measure but wouldn’t be hard to include.

      I’ll update this post sometime soon.

    • I’ve changed the code to include this.

  36. Hi James. I placed the exact code you provided after the GA script, but my GA report is not picking up any links/URLs. Do I need to modify anything in the code or the URLs I’m tracking? Thanks for your help!

    • Doh. Sorry I should have made clear. The script MUST sit at the end of the document. It has to be there so that the DOM is ready and it can go thru each link. Try putting it at the end – and with a few hours (depending on your traffic) you should see results coming through on Analytics.

  37. Do I assume that this function code is placed at the end of the GA Universal tracking code within the same script tags?

    • It depends how you structure your Javascript. I combine and minify all my own scripts into a single file that is called at the end of the document. The GA script still sits inline at the head of the document.

      You could paste the function in directly after the GA call, and it should still work.

      Correction: The code MUST be at the end of the document (otherwise the DOM has not yet been constructed).

  38. Should the outbound link tracking javascript be included in the tags of the regular analytics tracking code in the head, or do I have to put it somewhere else? Thanks for your help!

    • Hi Jeremy,

      The code should be placed at the end of your page (before the </body> tag).

Add a Comment