Skip to content

The tracker script, line by line

Your visitors load one small file from PureMetrix: about 2 KB compressed. Below is the complete core of that file, readable, so you can check for yourself what runs on your site.

What it does

  • cfg() reads the data-* attributes of the script tag: domain, public key, optional event endpoint (data-api, used by the proxy), and switches for localhost, manual mode and 404 pages.
  • ign(), bot(), loc() stop all sending if you excluded this browser, if the browser is automated, or if the page runs on localhost.
  • body() builds the request: event name, domain, key, page URL, referrer – plus engagement time, scroll depth, revenue or custom properties when they apply.
  • send() posts it with sendBeacon (or fetch as fallback). No cookies are set or read.
  • pageview() and bindSpa() count page loads and URL changes in single-page apps.
  • tick(), flush(), bindEngagement() measure time only while the tab is visible and focused, and send it when the visitor leaves.

The file also contains optional extensions (outbound links, downloads, forms, scroll milestones, hash routes, CSS-class events). Each one stays inactive until you switch it on in the site settings, which adds the matching data-* attribute to your snippet.

Source

This is core.js before minification. The build step only compresses it and bundles the optional extensions.

var K='puremetrix_ignore';
export function scriptEl(){try{return document.currentScript||document.querySelector('script[data-domain][data-key],#puremetrix-tracker')}catch(e){return null}}
function cfg(){var el=scriptEl();if(!el)return null;var d=el.getAttribute('data-domain')||'',k=el.getAttribute('data-key')||'',a=el.getAttribute('data-api')||'',il=el.getAttribute('data-include-localhost'),m=el.getAttribute('data-manual'),f4=el.getAttribute('data-404');
if(!a){var s=el.getAttribute('src')||'';if(s.indexOf('//')>-1){try{a=new URL(s).origin+'/api/event';}catch(e){a=location.origin+'/px/event';}}else a=location.origin+'/px/event';}else if(a[0]==='/')a=location.origin+a;
return{domain:d,key:k,api:a,il:il==='true'||il==='1'||il==='',manual:m==='true'||m==='1'||m==='',is404:f4==='true'||f4==='1'||f4===''};}
function ign(){try{return localStorage.getItem(K)==='1'}catch(e){return false}}
function bot(){try{return!!(navigator.webdriver||window._phantom||window.__nightmare||window.Cypress)}catch(e){return false}}
function dnt(){try{if(navigator.doNotTrack==='1'||navigator.globalPrivacyControl===true)return true}catch(e){}return false}
function loc(){try{var h=location.hostname;return h==='localhost'||h==='127.0.0.1'||h==='[::1]'||location.protocol==='file:'}catch(e){return false}}
function on(){try{return document.visibilityState==='visible'&&document.hasFocus()}catch(e){return document.visibilityState==='visible'}}
export function createTracker(c){var ms=0,since=0,last='',mx=0,smx=0,sms=0,onPv;
function body(n,o){o=o||{};var b={n:n,d:c.domain,k:c.key,u:o.u||location.href,r:document.referrer||''};if(o.props)b.p=o.props;if(o.revenue)b.revenue=o.revenue;if(o.engagement_ms!=null)b.e=o.engagement_ms;if(o.scroll_pct!=null)b.sd=o.scroll_pct;return b}
function send(b){if(ign()||bot()||(!c.il&&loc()))return;var j=JSON.stringify(b);try{if(navigator.sendBeacon){var bl=new Blob([j],{type:'text/plain'});if(navigator.sendBeacon(c.api,bl))return}}catch(e){}try{fetch(c.api,{method:'POST',body:j,keepalive:true}).catch(function(){})}catch(e){}}
function track(n,o){if(n&&!ign())send(body(n,o))}
function tick(){if(since&&on())ms+=Date.now()-since;since=on()?Date.now():0}
function flush(u){tick();var d=ms-sms;if(mx>smx||d>=3e3){var o={u:u||location.href};if(d>0)o.engagement_ms=d;if(mx>smx)o.scroll_pct=mx;track('engagement',o);smx=mx;sms=ms}}
function pageview(u){u=u||location.href;if(u===last)return;flush(last||u);last=u;mx=0;smx=0;sms=0;ms=0;since=on()?Date.now():0;track('pageview',{u:u});if(onPv)try{onPv(u)}catch(e){}}
function nav(){pageview(location.href)}
function vis(){if(on()){if(!since)since=Date.now()}else{tick();flush()}}
return{track:track,pageview:pageview,noteScroll:function(p){if(p>mx)mx=p},flush:flush,onPageview:function(fn){onPv=fn},
bindSpa:function(){var p=history.pushState,r=history.replaceState;history.pushState=function(){var o=p.apply(this,arguments);nav();return o};history.replaceState=function(){var loc=location.pathname+location.search;var o=r.apply(this,arguments);if(location.pathname+location.search!==loc)nav();return o};addEventListener('popstate',nav)},
bindEngagement:function(){since=on()?Date.now():0;document.addEventListener('visibilitychange',vis);addEventListener('pagehide',function(){flush()})}};}
export function boot(f){if(window.__puremetrix_loaded||bot()||dnt())return null;var c=cfg();if(!c||!c.domain||!c.key||(!c.il&&loc()))return null;window.__puremetrix_loaded=1;var t=createTracker(c);var q=window.puremetrix&&window.puremetrix.q?window.puremetrix.q:[];window.puremetrix=function(n,o){t.track(n,o)};window.puremetrix.q=[];for(var i=0;i<q.length;i++)window.puremetrix.apply(null,q[i]);f=f||{};if(f.pageview!==false&&!c.manual)t.pageview();if(c.is404||f['404'])t.track('404',{u:location.href});if(f.spa!==false&&!c.manual)t.bindSpa();if(f.engagement!==false)t.bindEngagement();return t}

Where the file comes from

  • Default: https://collect.puremetrix.org/js/script.js
  • With the proxy: from your own domain, for example /px/js on WordPress.

What happens with the data on our side is described in How we measure.

Related