mpEZtrack
helps you collect data from your web application as events (and users) in Mixpanel with no code. All you need is to copy/modify/paste a <script>
tag and deploy it on your website to get up and going. i've also made a short demo which goes through the installation process.
more specifically mpEZTrack
is a javascript bundle that wraps the mixpanel JS SDK, initializes it with best practices, and adds listeners for common events like page view
, page exit
, button click
, link click
, etc...
you can customize the behavior or your implementation by passing options to mpEZTrack.init()
the bundle is hosted in a multi-region Google Cloud Storage bucket; it is free to use.
for more information see the options + recipes to learn about customizing your implementation.
finally, feel free to read my thoughts on why this tool exists, as well as some considerations for testing, user profiles, security, and performance
you will need:
- a (free) mixpanel account
- a new mixpanel project
- a website / web application you can deploy a code snippet on
to install, include the following two <script>
tags before the closing </body>
HTML tag on any page to use mpEZTrack
.
customize the second tag with your mixpanel project token:
<script src="https://mpeztrack.com/v1.0.0/eztrack.min.js" type="text/javascript"></script>
<script>
mpEZTrack.init("YOUR-PROJECT-TOKEN"); //change me π€
</script>
Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β
you MUST change the value of YOUR-PROJECT-TOKEN
in the above snippet to your mixpanel project's token
one deployed on your website, look in your mixpanel project; you are now collecting many useful events:
you can choose to add a second options
object {}
to .init()
to customize your implementation
in the table below, you will find all the options exposed by this module; if you wish to use a default
value for any particular option, it does not need to be explicitly passed:
option | expected type | default | notes |
---|---|---|---|
region |
"US" or "EU" |
"US" |
controls whether data is routed through global servers (default) or EU-only servers; learn more about EU data residency |
persistence |
"localStorage" or "cookie" |
"localStorage" |
controls the persistence mechanism; use "cookie" if you need cross sub-domain tracking. note that the "cookie_domain" option can also be passed if using "cookie" persistence with non-standard subdomains. learn more about persistence options |
----------- | -------- | ---- | -------------------------------------- |
refresh |
integer |
5000 |
the frequency (ms) in which the queue will be flushed (sending events to mixpanel) |
location |
boolean |
true |
resolve the end-user's geo-location (country/region/state/city) |
deviceProps |
boolean |
true |
add device information (extending mixpanel defaults) about the client device |
pageView |
boolean |
true |
tracks all page views as page enter |
pageExit |
boolean |
true |
track all pages exits as page exit including duration and scroll % |
links |
boolean |
true |
tracks all clicks on <a> elements as link click or navigation click |
buttons |
boolean |
true |
tracks all clicks on <button> -like elements as button click |
forms |
boolean |
true |
track all submissions on <form> elements as form submit |
selectors |
boolean |
true |
track all changes to <select> , [type=radio] , [type=checkbox] , and other drop-down elements as user selection |
videos |
boolean |
true |
track <video> elements getting play , pause , and ended events (including watch % and duration ) |
window |
boolean |
true |
track visibility of page (page lost/regained focus , full screen: on/off , resize , print , etc... ) |
profiles |
boolean |
true |
creates user profiles for every unique device (see note) |
spa |
boolean |
true |
for single page applications where page elements are rendered dynamically (see note) |
----------- | -------- | ---- | -------------------------------------- |
inputs |
boolean |
false |
track all <input> text fields as user entered text (see note) |
clicks |
boolean |
false |
track all clicks on other page elements as page click (see note) |
youtube |
boolean |
false |
track interactions with embedded youtube videos (see note) |
error |
boolean |
false |
tracks any javascript errors thrown as page error |
clipboard |
boolean |
false |
tracks interactions with the clipboard, firing cut , copy , paste events (see note) |
tabs |
boolean |
false |
generate a unique tabId for each window (note: uses sessionStorage ) |
firstPage |
boolean |
false |
determine if it is the first page in the user's history (note: uses localStorage ) |
debug |
boolean |
false |
puts the mixpanel SDK in debug mode |
extend |
boolean |
false |
exposes the mixpanel object as a global under the namespace mixpanel.ez |
- track embedded youtube videos; don't track button clicks or profiles
mpEZTrack.init('YOUR-PROJECT-TOKEN', {youtube: true, buttons: false, profiles: false})
- track user input fields; also track page visibility and identify unique browsing "tabs"
mpEZTrack.init('YOUR-PROJECT-TOKEN', {inputs: true, window: true, tabs: true})
- use
mixpanel
debug mode, flush queue every second, exposemixpanel
asmixpanel.ez
mpEZTrack.init('YOUR-PROJECT-TOKEN', {debug: true, refresh: 1000, expose: true})
- don't set device properties or make user profiles
mpEZTrack.init('YOUR-PROJECT-TOKEN', { deviceProps: false, profiles: false})
- use spa mode (react, angular, vue, svelte, ember, etc...) and enable "first page, first visit" tracking
mpEZTrack.init('YOUR-PROJECT-TOKEN', { spa: true, firstPage: true})
- use a (first party) cookie for persistence; override the cookie domain (if non-standard)
mpEZTrack.init('YOUR-PROJECT-TOKEN', { persistence: "cookie", cookie_domain: "subdomain.mainsite.aktunes.biz"})
- the default settings for all options
mpEZTrack.init('YOUR-PROJECT-TOKEN', {debug: false, extend: false, refresh: 5000, location: true, superProps: true, pageView: true, pageExit: true, links: true, buttons: true, forms: true, profiles: true, selectors: true, inputs: false, clicks: false, youtube: false, window: false, clipboard: false, firstPage: false, error: false, spa: true, tabs: false, persistence: 'localStorage'})
there are many opinions on the auto-capture
v.s. precision tracking
debate; there are (valid) pros and cons to both sides, and i've had the "do-we-collect-everything-automatically-and-tag-it-later?" or "do-we-explicitly-tag-and-try-not-to-miss-anything?" conversation many times in my career. having that conversation is (in part) what led me to make this.
my ultimate conclusion on the topic is this:
Regardless of your organization's chosen data collection strategy, someone is going to have to manage the schema. Someone is going to need to define what the events and props mean. And ideally it's not just one person who holds that knowledge...
Data must be organized in order to be leveraged. And if everyone can participate in the taxonomy then everyone who needs the data has some familiarity with it.
this utility aims to find a middle ground between two opposing camps. it removes some of the "gatekeeping" in event-driven analytics, and allows for non-technical users to work through a useful taxonomy in a GUI, using Mixpanel's governance tools. It's not precision tracking ... but it's also not auto-capture ... it's mpEZTrack
π₯³
- a quick way to add mixpanel to a web app without writing code
- a tool for non-engineers to get high fidelity, actionable behavioral data, ready for analysis
- a templated and wrapped implementation for the mixpanel-js SDK, that will work on most websites + browsers
- anyone who needs "lite" event tracking on their web application
- anyone who want to run a simple proof-of-concept on mixpanel with minimal development effort
- a substitute for a tracking plan
- a full implementation of mixpanel, uniquely configured for specific KPIs and business goals
- large enterprises with millions of active users
- sites with strict
CORS
policies - experimental web applications that do not use standard HTML elements and attributes to construct UIs
you may wish to test mpEZTrack
before putting it into production. it's a good idea!
to facilitate this, this repo also includes a chrome extension to make it painless and fun to test an mpEZTrack
implementation, locally on your computer.
- download and extract this zip archive or clone this repo:
git clone https://github.com/ak--47/mpEZTrack.git
- go to chrome://extensions/ in your browser.
- turn on developer mode (top right)
-
for single page injection: click the button, open the developer console on your current tab and type
mpEZTrack.init("your-project-token", {debug: true})
replacingyour-project-token
with your mixpanel project token -
for persistent tab injection: plug in your token and press
start
;mpEZTrack
is now streaming all of your clicks/actions on a single tab your mixpanel project. the extension's icon will turn into aβ οΈ , and the browser window will be retitled to let you know that all of your actions are streaming events to mixpanel. pressstop
in the UI to turn this off. -
now preform some actions on the webpage, and you'll see the events in your console (and in your mixpanel project!)
π₯³ celebrate! you just implemented mpEZTrack
locally!
mpEZTrack
queries the DOM once for trackable elements when the page is fully loaded. this is not feasible for client-rendered single page applications (SPAs) like react, vue, angular, etc... which construct the DOM reactively, as an end-user navigates through an application.
by default, mpEZTrack
uses { spa : true}
in order to track UI elements which are constructed after the page has fully loaded.
in SPA mode, mpEZTrack
will listen to all page clicks and preform introspection to conclude which element on the page was clicked:
function singlePageAppTracking(mp, opts) {
window.addEventListener("click", (ev) => {
introspectElement(ev.target, ev, mp, opts);
}
if that element is "trackable" based on your configuration, mpEZTrack
will send an event to mixpanel.
SPA mode is an appropriate choice for any apps which construct their UI components dynamically; since many web apps will do this, it is enabled by default.
if you wish to turn this off, you can:
mpEZTrack.init('YOUR-PROJECT-TOKEN', {spa: false})
one of the biggest drawbacks to purely codeless analytics SDKs, is that they lose the ability to properly resolve the end-user's identity. while mpEZTrack
will happily persist a user's identity across sessions on a single device, without a signal (at run time) from your app, it is not possible to identify the same user across multiple devices.
if you are using the { profiles : true }
option (or the defaults), you may notice that all of your user profiles show users as anonymous
:
this is expected behavior.
if your application can supply a canonical unique user identifier you can extend
the EZTrack
implementation and call any Mixpanel SDK methods under the mixpanel.ez
namespace. when doing this, it is recommended to turn profiles
off...
an example implementation of custom identity management might look like this:
mpEZTrack.init('project-token', { extend: true, profiles: false }); // expose the mixpanel object
mixpanel.ez.identify(currentUser.id); // tell mixpanel who the user is
mixpanel.ez.track('log in'); // precision-track any events
//set any other props on the user
mixpanel.ez.people.set({$name: currentUser.name, $email: currentUser.name, plan: currentUser.planType})
where currentUser
has the following shape:
{
id: "3cd1e0f8-2366-42d7-b302-3c70f827fd51",
name: "AK",
email: "ak@notmixpanel.com",
plan: "blue"
}
when using the {extend: true}
configuration, mpEZTrack
will also broadcast a mpEZTrackLoaded
event on the window; this makes it possible to "listen" for completion of the library's init()
method in other script files:
// analytics.js
mpEZTrack.init('project-token', { extend: true });
// someOtherFile.js
window.addEventListener('mpEZTrackLoaded', ()=>{
// mixpanel.ez is always available in this scope
mixpanel.ez.people.set({$name: currentUser.name})
})
future versions of this module may improve upon this API. please submit an enhancement if you have (an idea or suggestion) about how this should work.
youtube makes it possible (through an officially supported iframe API) to track video play actions on videos you embed within your web application using youtube's generated embed code:
in order to "enable" video tracking, the src
attribute of each embedded video must have a URL parameter of enablejsapi=1
:
<iframe id="existing-iframe-example" src="https://www.youtube.com/embed/V9rPJ-kBb5s?enablejsapi=1"></iframe>
if you do not include an enablejsapi=1
URL param on your video embed and you enable { youtube: true }
when calling mpEZTrack.init()
, mpEZTrack
will automatically enable the enablejsapi
parameter on all embedded videos on your page. when this occurs, you may see a small "flicker" on the video player as the content is reloaded.
this is expected behavior, and if you don't wish to see it, add ?enablejsapi=1
to the src
param of your videos.
mpEZTrack
will send the following events for embedded youtube videos:
youtube player load
youtube video play
youtube video pause
youtube video finish
these video events will contain event properties which describe the video being played, as well as the "watch time":
'VIDEO β quality': STRING
'VIDEO β length (sec)': NUMBER
'VIDEO β elapsed (sec)': NUMBER
'VIDEO β url': URL
'VIDEO β title': STRING
'VIDEO β id': STRING
'VIDEO β author': STRING
"VIDEO β fullscreen": BOOL
when tracking "every click on every element" on any webpage, in combination with some of the other options it is possible to end up with 2 distinct click events in mixpanel that represent a single user click
for example, consider the following HTML, which represents an invisible button styled by an SVG:
<button class="more-info">
<svg width="24" height="24" viewBox="0 0 24 24" class="feather feather-more-horizontal">
</svg>
</button>
if the buttons
and clicks
options were passed to .init()
, mpEZTrack
may recognize these as two separate elements and in certain cases a single click from a user may result in a button click
event AND a page click
event getting sent to Mixpanel as part of the same interaction.
mpEZTrack
attempt to avoid such collisions by employing the following filters on general click
tracking:
let allThings = this.query(ALL_SELECTOR)
.filter(node => node.children.length === 0) //most specific element (no children)
.filter(node => !this.domElements.some(el => el === node)) //not already tracked node
.filter(node => !this.domElements.some(el => el.contains(node))) //not a child of already tracked node
the above-mention HTML catches the <svg>
inside a <button>
case, which would not result in duplicated click events, however, since it is not feasible to anticipate all possible combinations of the DOM's structure in every application, it seems prudent to explicitly state this limitation.
if you find that mpEZTrack
is double-tracking certain elements on your page, please submit an enhancement request and include a URL to the page in question, as well as the element(s) you observe such behavior on.
for these reasons, "tracking all clicks" with the clicks
option is disabled by default as this tends to be "noisy" on most pages.
mpEZTrack
is served as a minified script from a multi-region GCP-hosted CDN. it also bundles the mixpanel
Javascript SDK and is ~80KB (uncompressed) and ~20KB compressed.
all outgoing requests are implemented as non-blocking asynchronous network calls are therefore adds no latency to the user experience of your app. the mixpanel
SDK will batch network requests (by default) further decreasing the network overhead.
adding mpEZTrack
at the bottom of your HTML (before the closing </body>
tag) helps ensure that it is parsed and interpreted after all user-facing content is rendered, and therefore has no observable impact on the user experience.
under the hood, mpEZTrack
attaches event listeners to DOM elements (by reference) based on the options passed to init()
. all listeners are attached in passive
mode, which is a well known best-practice to prevent any interference with the end users' scrolling experience.
please feel free to review the selectors and fields used to identify DOM elements in your application. you may also peak into the mpEZTrack.domElements
property in your browser's console to see which elements mpEZTrack
has identified as "trackable" on your application.
many web applications may handle user-entered secrets (passwords, tokens, private keys, etc...). while all network requests made by mixpanel-js
are encrypted in transit and at rest, it's never a good idea to send or store secrets in plain text
with the default settings, mpEZTrack
will not collect any end-user input ... if you choose to turn on { inputs: true }
and/or { clipboard: true }
options, mpEZTrack
will employ several strategies to "detect and ignore" sensitive fields:
- do not track (blacklist) DOM elements of selectors matching
<input type="password" />
and other well-known sensitive fields:
const BLACKLIST_ELEMENTS = `*[type="password"], *[type="hidden"], *.sensitive, *.pendo-ignore, *[data-heap-redact-text], *[data-heap-redact-attributes]`;
- redact clipboard text when tracking clipboard activity:
const CLIPBOARD_TEXT = (ev, guard = true) => ({
"ELEM β text": guard ? "******" : ev.target.textContent || ev.target.value,
});
- redact fields where values appear to be sensitive data such as credit card numbers or social security numbers:
const INPUT_TEXT_FIELDS = (el) =>
({ "CONTENT β user content": isSensitiveData(el.value) ? "******" : el.value })
- do not collect HTML attributes that include
passw
if (HTMLAttr?.toLowerCase()?.includes('passw')) continue loopAttributes;
this library is open source so you are free to review the attribute selectors & sensitive data classifications to feel confident that all cases are covered. if you have a concern in this area, please submit an issue.
while these heuristics are appropriate for most applications, if your web application frequently displays highly sensitive data in non-standard ways, mpEZTrack
, or any other auto-capture analytics tools, are probably not viable for your product.