A lightweight, extendable analytics library designed to work with any third party analytics provider to track page views, custom events, & identify users.
The analytics
library allows users to:
- Connect with your favorite analytics providers
- Trigger custom logic based on user activity
- Extend with functionality via plugins
- Easily allow visitors to opt out of tracking
- ... and lots more
Click to expand
- Features
- Why
- Philosophy
- Install
- Usage
- Demo
- API
- Configuration
- analytics.identify
- analytics.track
- analytics.page
- analytics.user
- analytics.reset
- analytics.ready
- analytics.on
- analytics.once
- analytics.getState
- analytics.enablePlugin
- analytics.disablePlugin
- analytics.storage
- analytics.storage.getItem
- analytics.storage.setItem
- analytics.storage.removeItem
- Analytic plugins
- Creating analytics plugins
- Plugin Naming Conventions
- Contributing
- Extendable - Bring your own third party tool & plugins
- Test & Debug analytics integrations with time travel & offline mode.
- Exposes lifecycle for analytic calls allowing for per event cancellation or provider specific payloads
- Works on client & server-side
- Queues events to send when analytic libraries are loaded
- (WIP) works offline
Companies frequently change analytics & collection requirements. This results in adding & removing analytic services a painful time consuming process.
This library aims to solves that with a simple pluggable abstraction layer.
You should never be locked into a tool
To add or remove an analytics provider adjust the plugins
you load into analytics
.
npm install analytics --save
Or as a script tag:
<script src="https://unpkg.com/analytics/dist/analytics.min.js"></script>
import Analytics from 'analytics'
import googleAnalyticsPlugin from 'analytics-plugin-ga'
import customerIOPlugin from 'analytics-plugin-customerio'
/* Initialize analytics */
const analytics = Analytics({
app: 'my-app-name',
version: 100,
plugins: [
googleAnalyticsPlugin({
trackingId: 'UA-121991291',
}),
customerIOPlugin({
siteId: '123-xyz'
})
]
})
/* Track a page view */
analytics.page()
/* Track a custom event */
analytics.track('userPurchase', {
price: 20
item: 'pink socks'
})
/* Identify a visitor */
analytics.identify('user-id-xyz', {
firstName: 'bill',
lastName: 'murray',
email: 'da-coolest@aol.com'
})
Node.js usage
const { analytics } = require('analytics')
const analytics = Analytics({
app: 'my-app-name',
version: 100,
plugins: [
googleAnalyticsPlugin({
trackingId: 'UA-121991291',
}),
customerIOPlugin({
siteId: '123-xyz'
})
]
})
// Fire a page view
analytics.page()
Browser usage
<script src="https://unpkg.com/analytics/dist/analytics.min.js"></script>
<script>
const Analytics = analytics({
app: 'my-app-name',
version: 100,
...plugins
})
Analytics.track()
// optionally expose to window
window.Analytics = Analytics
</script>
See Analytics Demo for a site example.
Below is the core API analytics expose once initialized.
Analytics library
Arguments
- config
object
- analytics core config - [config.app] (optional)
string
- Name of site / app - [config.version] (optional)
string
- Version of your app - [config.plugins] (optional)
array
- Array of analytics plugins
Example
import Analytics from 'analytics'
// initialize analytics
const analytics = Analytics({
app: 'my-awesome-app',
plugins: [
...importedPlugins
]
})
Identify a user. This will trigger identify
calls in any installed plugins and will set user data in localStorage
Arguments
- userId
String
- Unique ID of user - [traits] (optional)
Object
- Object of user traits - [options] (optional)
Object
- Options to pass to identify call - [callback] (optional)
Function
- Callback function after identify completes
Example
// Basic user id identify
analytics.identify('xyz-123')
// Identify with additional traits
analytics.identify('xyz-123', {
name: 'steve',
company: 'hello-clicky'
})
// Disable identify for specific plugin
analytics.identify('xyz-123', {}, {
plugins: {
// disable for segment plugin
segment: false
}
})
// Fire callback with 2nd or 3rd argument
analytics.identify('xyz-123', () => {
console.log('do this after identify')
})
Track an analytics event. This will trigger track
calls in any installed plugins
Arguments
- eventName
String
- Event name - [payload] (optional)
Object
- Event payload - [options] (optional)
Object
- Event options - [callback] (optional)
Function
- Callback to fire after tracking completes
Example
// Basic event tracking
analytics.track('buttonClicked')
// Event tracking with payload
analytics.track('itemPurchased', {
price: 11,
sku: '1234'
})
// Disable specific plugin on track
analytics.track('cartAbandoned', {
items: ['xyz', 'abc']
}, {
plugins: {
// disable track event for segment
segment: false
}
})
// Fire callback with 2nd or 3rd argument
analytics.track('newsletterSubscribed', () => {
console.log('do this after track')
})
Trigger page view. This will trigger page
calls in any installed plugins
Arguments
- [data] (optional)
String
- Page data overrides. - [options] (optional)
Object
- Page tracking options - [callback] (optional)
Function
- Callback to fire after page view call completes
Example
// Basic page tracking
analytics.page()
// Page tracking with page data overides
analytics.page({
url: 'https://google.com'
})
// Disable specific plugin page tracking
analytics.page({}, {
plugins: {
// disable page tracking event for segment
segment: false
}
})
// Fire callback with 1st, 2nd or 3rd argument
analytics.page(() => {
console.log('do this after page')
})
Get user data
Arguments
- [key] (optional)
String
- dot.prop.path of user data. Example: 'traits.company.name'
Example
// Get all user data
const userData = analytics.user()
// Get user id
const userId = analytics.user('userId')
// Get user company name
const companyName = analytics.user('traits.company.name')
Clear all information about the visitor & reset analytic state.
Arguments
- [callback] (optional)
Function
- Handler to run after reset
Example
// Reset current visitor
analytics.reset()
Fire callback on analytics ready event
Arguments
- callback
Function
- function to trigger when all providers have loaded
Example
analytics.ready() => {
console.log('all plugins have loaded or were skipped', payload)
})
Attach an event handler function for one or more events to the selected elements.
Arguments
- name
String
- Name of event to listen to - callback
Function
- function to fire on event
Example
// Fire function when 'track' calls happen
analytics.on('track', ({ payload }) => {
console.log('track call just happened. Do stuff')
})
// Remove listener before it is called
const removeListener = analytics.on('track', ({ payload }) => {
console.log('This will never get called')
})
removeListener() // cleanup .on listener
Attach a handler function to an event and only trigger it only once.
Arguments
- name
String
- Name of event to listen to - callback
Function
- function to fire on event
Example
// Fire function only once 'track'
analytics.once('track', ({ payload }) => {
console.log('This will only triggered once')
})
// Remove listener before it is called
const removeOnce = analytics.once('track', ({ payload }) => {
console.log('This will never get called')
})
removeOnce() // cleanup once function
Get data about user, activity, or context. Access sub-keys of state with dot.prop
syntax.
Arguments
- [key] (optional)
string
- dot.prop.path value of state
Example
// Get the current state of analytics
analytics.getState()
// Get a subpath of state
analytics.getState('context.offline')
Enable analytics plugin
Arguments
- plugins
String
|Array
- name of plugins(s) to disable - [callback] (optional)
Function
- callback after enable runs
Example
analytics.enablePlugin('google')
// Enable multiple plugins at once
analytics.enablePlugin(['google', 'segment'])
Disable analytics plugin
Arguments
- name
String
|Array
- name of integration(s) to disable - callback
Function
- callback after disable runs
Example
analytics.disablePlugin('google')
analytics.disablePlugin(['google', 'segment'])
Storage utilities for persisting data. These methods will allow you to save data in localStorage, cookies, or to the window.
Example
// Pull storage off analytics instance
const { storage } = analytics
// Get value
storage.getItem('storage_key')
// Set value
storage.setItem('storage_key', 'value')
// Remove value
storage.removeItem('storage_key')
Get value from storage
Arguments
- key
String
- storage key - [options] (optional)
Object
- storage options
Example
analytics.storage.getItem('storage_key')
Set storage value
Arguments
- key
String
- storage key - value any - storage value
- [options] (optional)
Object
- storage options
Example
analytics.storage.setItem('storage_key', 'value')
Remove storage value
Arguments
- key
String
- storage key - [options] (optional)
Object
- storage options
Example
analytics.storage.removeItem('storage_key')
The analytics
has a robust plugin system. Here is a list of currently available plugins:
- analytics-plugin-customerio Customer.io plugin for 'analytics' npm link.
- analytics-plugin-do-not-track Disable tracking for opted out visitors npm link.
- analytics-plugin-ga Google analytics integration for 'analytics' pkg npm link.
- analytics-plugin-google-tag-manager Google tag manager plugin for 'analytics' pkg npm link.
- analytics-plugin-lifecycle-example Example plugin with lifecycle methods npm link.
- analytics-plugin-original-source Save original referral source of visitor npm link.
- analytics-plugin-segment Segment integration for 'analytics' module for browser & node npm link.
- analytics-plugin-tab-events Expose tab visibility events for analytics npm link.
- analytics-plugin-window-events Expose window events for analytics npm link.
- analytics-utils Analytics utility functions npm link.
- gatsby-plugin-analytics Easily add analytics to your Gatsby site. npm link.
- analytics-plugin-template Example plugin with browser + node module build with treeshaking npm link.
- Add yours! π
The library is designed to work with any third party analytics tool.
Plugins are just plain javascript objects that expose methods for analytics
to register and call.
Here is a quick example of a plugin:
// plugin-example.js
export default function pluginExample(userConfig) {
// return object for analytics to use
return {
/* All plugins require a NAMESPACE */
NAMESPACE: 'my-example-plugin',
/* Everything else below this is optional depending on your plugin requirements */
config: {
whatEver: userConfig.whatEver,
elseYouNeed: userConfig
6D47
span>.elseYouNeed
},
initialize: ({ config }) => {
// load provider script to page
},
page: ({ payload }) => {
// call provider specific page tracking
},
track: ({ payload }) => {
// call provider specific event tracking
},
identify: ({ payload }) => {
// call provider specific user identify method
},
loaded: () => {
// return boolean so analytics knows when it can send data to third party
return !!window.myPluginLoaded
}
}
}
NAMESPACE
is required for all plugins. All other methods are optional.
If you don't need to hook into page
tracking, just omit the page
key from your plugin object.
To use a plugin, import it and pass it into the plugins
array when you bootstrap analytics
.
import Analytics from 'analytics'
import pluginExample from './plugin-example.js'
const analytics = Analytics({
app: 'my-app-name',
plugins: [
pluginExample({
whatEver: 'hello',
elseYouNeed: 'there'
}),
...otherPlugins
]
})
Plugins can react to any event flowing through the analytics
library.
For example, if you wanted to trigger custom logic when analytics
bootstraps you can attach a function handler to the bootstrap
event.
For a full list of core events, checkout events.js
.
// Example Plugin plugin.js
export default function myPlugin(userConfig) {
return {
NAMESPACE: 'my-plugin',
bootstrap: ({ payload, config, instance }) => {
// Do whatever on `bootstrap` event
},
pageStart: ({ payload, config, instance }) => {
// Fire custom logic before analytics.page() calls
},
pageEnd: ({ payload, config, instance }) => {
// Fire custom logic after analytics.page() calls
},
trackStart: ({ payload, config, instance }) => {
// Fire custom logic before analytics.track() calls
},
'track:customerio': ({ payload, config, instance }) => {
// Fire custom logic before customer.io plugin runs.
// Here you can customize the data sent to individual analytics providers
},
trackEnd: ({ payload, config, instance }) => {
// Fire custom logic after analytics.track() calls
},
// ... hook into other events
}
}
Using this plugin is the same as any other.
import Analytics from 'analytics'
import customerIoPlugin from 'analytics-plugin-customerio'
import myPlugin from './plugin.js'
const analytics = Analytics({
app: 'my-app-name',
plugins: [
// include myPlugin
myPlugin(),
customerIoPlugin({
trackingId: '1234'
})
...otherPlugins
]
})
Alternatively, you can also attach any middleware functionality you'd like from the redux
ecosystem.
// logger-plugin.js redux middleware
const logger = store => next => action => {
if (action.type) {
console.log(`>> analytics dispatching ${action.type}`, JSON.stringify(action))
}
return next(action)
}
export default logger
Using this plugin is the same as any other.
import Analytics from 'analytics'
import loggerPlugin from './logger-plugin.js'
const analytics = Analytics({
app: 'my-app-name',
plugins: [
...otherPlugins,
loggerPlugin
]
})
This is a vanilla redux middleware that will opt out users from tracking. There are many ways to implement this type of functionality using analytics
const optOutMiddleware = store => next => action => {
const { type } = action
if (type === 'trackStart' || type === 'pageStart' || type === 'trackStart') {
// Check cookie/localStorage/Whatever to see if visitor opts out
// Here I am checking user traits persisted to localStorage
const { user } = store.getState()
// user has optOut trait cancel action
if (user && user.traits.optOut) {
return next({
...action,
...{
abort: true,
reason: 'User opted out'
},
})
}
}
return next(finalAction)
}
export default optOutMiddleware
Plugins should follow this naming convention before being published to npm
analytics-plugin-{your-plugin-name}
E.g. An analytics plugin that does awesome-stuff
should be named
npm install analytics-plugin-awesome-stuff
Contributions are always welcome, no matter how large or small. Before contributing, please read the code of conduct.