Skip to content

A tiny, type-safe and zero-dependency solution for triggering Google Tag Manager track events.

License

Notifications You must be signed in to change notification settings

emkis/gtm-event-tracker

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

gtm-event-tracker

Downloads Bundle Size Version

A tiny (1KB gzip), type-safe and zero-dependency solution for triggering Google Tag Manager track events.

It's designed for applications that need to trigger a lot of track events, and it solves common problems like writing similar events multiple times, managing "global" event properties, and debugging track events. This solution is framework agnostic and configurable, so it's probably the only solution you need for your web application for triggering track events.

Installation

It's required you have Google Tag Manager installed already in your application because it depends on window.dataLayer by default. If you use a different targetProperty to push your events, you can use the configure function to change this behavior.

yarn add gtm-event-tracker # or npm install gtm-event-tracker

Why this library over dataLayer.push?

  • Type-safe events
  • Less repetition of similar track events
  • Centralized way to manage "global" event properties
  • Easy way to debug your track events
  • Server-side compatible

Server-Side Rendering (SSR)

This library is SSR compatible, there is only one caveat though. The targetProperty isn't available on Server-Side, this means you can't call the trackEvent function. The reason why is because Google Tag Manager creates the targetProperty (window.dataLayer by default) only on Client-Side.

If you're using some modern framework such as React or Vue, you need to call the trackEvent function on useEffect or onMounted callback. This way you ensure that track events are being only triggered on Client-Side.

API

createTrackerContext

It creates a tracker context, which is responsible for centralizing all "global" event properties you want to include in your track events.

This function accepts shallow objects only. This is a restriction from this library to prevent issues with object references.

Usage

// no arguments are allowed
// you can create a tracker context with no initial properties
createTrackerContext()

// or you can create a tracker context with some properties
createTrackerContext({
  some: 'data',
  foo: 'bar',
})

// if you need, you can provide options as well
createTrackerContext({ foo: 'bar' }, { name: 'main-context' })

Options

In the second argument, you can provide options to customize it.

No options are required.

Name Type Default Description
name string undefined It's the name of this tracker context that is used by the Logger. Is only useful if your application has more than one tracker context, and you want to be able to identify them on Logs by their names.

Updating values

You have full control of the values of a tracker context with the setProps function, you can change them completely. After you set a new value, all next track events which use this tracker context with withTrackerContext will contain the updated tracker context value.

// a common tracker context example, no user id is defined by default
const trackerContext = createTrackerContext({ userId: null })

// we can set the same properties with different values
trackerContext.setProps({ userId: 'uuid' })

// we can set completely different properties
trackerContext.setProps({ new: 'values' })

// or both
trackerContext.setProps({ userId: 'uuid', new: 'values' })

withTrackerContext

Accepts a tracker context as the first argument and returns functions responsible for triggering the track events and pushing them to the targetProperty.

Usage

With Typescript (recommended):

type TrackEventProperties = {
  event: string
  category?: string
  current_page: string
  business_context: string
}

// this Generic will ensure that track events have the same contract
withTrackerContext<TrackEventProperties>(trackerContext)

Without Typescript:

withTrackerContext(trackerContext)

Track functions

The return of the withTrackerContext function is an object containing all available functions for tracking events.

const tracker = withTrackerContext<TrackEventProperties>(trackerContext)
trackEvent

It pushes events to the targetProperty (window.dataLayer by default). It receives an object as the first argument, and it must contain all required properties in the type provided as the Generic (TrackEventProperties in this case).

tracker.trackEvent({
  event: 'recommended_page_viewed',
  current_page: 'feed/recommended-for-you',
  business_context: 'feed',
  category: 'views',
})

// you can add different properties if you need
tracker.trackEvent({
  event: 'notifications_enabled',
  channel_id: 'jqk-aof', // this property was not declared in TrackEventProperties type
  current_page: 'channel/:id',
  business_context: 'content',
})

When this function is called an object will be pushed to the targetProperty. This object is created combining the tracker context properties and these event properties you provide. They are combined using the spread operator, so you can override the "global" event properties from tracker context in your events if you need to.

Check out the examples below:

Common usage
import { createTrackerContext, withTrackerContext } from 'gtm-event-tracker'

type TrackEventProperties = {
  foo: string
  bar: string
  baz: string
}

const trackerContext = createTrackerContext({ global_property: 'from context' })
const { trackEvent } = withTrackerContext<TrackEventProperties>(trackerContext)

trackEvent({ foo: 'AAA', bar: 'BBB', baz: 'CCC' })

Object pushed to the targetProperty:

{
  "global_property": "from context",
  "foo": "AAA",
  "bar": "BBB",
  "baz": "CCC"
}
Overriding track context properties
import { createTrackerContext, withTrackerContext } from 'gtm-event-tracker'

type TrackEventProperties = {
  foo: string
  bar: string
  baz: string
}

const trackerContext = createTrackerContext({ global_property: 'from context' })
const { trackEvent } = withTrackerContext<TrackEventProperties>(trackerContext)

trackEvent({
  global_property: 'overwritten in this event',
  foo: 'DDD',
  bar: 'EEE',
  baz: 'FFF',
})

Object pushed to the targetProperty:

{
  "global_property": "overwritten in this event",
  "foo": "DDD",
  "bar": "EEE",
  "baz": "FFF"
}
Adding new properties to specific events
import { createTrackerContext, withTrackerContext } from 'gtm-event-tracker'

type TrackEventProperties = {
  foo: string
  bar: string
  baz: string
}

const trackerContext = createTrackerContext({ global_property: 'from context' })
const { trackEvent } = withTrackerContext<TrackEventProperties>(trackerContext)

trackEvent({
  foo: 'GGG',
  bar: 'HHH',
  baz: 'III',
  some_property: 'some property that I will need just for this event',
  another_one: 'you got it',
})

Object pushed to the targetProperty:

{
    "global_property": "from context",
    "foo": "GGG",
    "bar": "HHH",
    "baz": "III",
    "some_property": "some property that I will need just for this event",
    "another_one": "you got it"
}
Using TypeScript Generics with annotations
// ℹ️ These are the track event properties we support in this app.
type TrackEventProperties = {
  event: string
  category: string

  // ℹ️ you can create optional properties too
  current_page?: string
  business_context?: string

  // ℹ️ you can add custom descriptions that helps your
  // team to use the correct properties.

  /**
   * This property isn't supported anymore. Use the `currentPage` property instead.
   * @deprecated
   */
  url?: string
}

const trackerContext = createTrackerContext()
const { trackEvent } = withTrackerContext<TrackEventProperties>(trackerContext)

trackEvent({
  event: 'user_photo_updated',
  category: 'settings',
  business_context: 'account',
  current_page: 'user/settings',
})
partialTrackEvent

It receives track event properties in the same way as the trackEvent function, but all properties are optional here. The return is the trackEvent function with these track event properties injected by default. Useful when you need to track a lot of similar events on the same page with multiple repeated properties.

const trackAwesomeEvent = tracker.partialTrackEvent(repeatedProperties)
trackAwesomeEvent(missingRequiredProperties)

Once the returned function is called, the final object will be pushed to the targetProperty. This object is also created by combining event properties using the spread operator, this means you can override all of them wherever you think is appropriate.

How's the final object is created:

{ ...trackerContextProps, ...repeatedProperties, ...missingRequiredProperties }

Check out the examples below:

TypeScript auto-completion demo

Common usage
type TrackEventProperties = {
  event: string
  category?: string
  current_page: string
  business_context: string
}

const trackerContext = createTrackerContext({
  user_type: user.profileType,
  user_id: user.id,
  B2B_partner_id: partner.id,
})

const tracker = withTrackerContext<TrackEventProperties>(trackerContext)

const trackAccountEvent = tracker.partialTrackEvent({
  current_page: 'account/security',
  business_context: 'account',
})

trackAccountEvent({ event: 'security_page_viewed', category: 'views' })
trackAccountEvent({ event: 'password_changed' })
trackAccountEvent({ event: 'new_device_authorized' })

First object pushed to the targetProperty:

{
  "user_type": "administrator",
  "user_id": "fancy-uuid",
  "B2B_partner_id": "fancy-uuid",
  "current_page": "account/security",
  "business_context": "account",
  "event": "security_page_viewed",
  "category": "views"
}

setLogger

Is a function that allows you to replace globally the default logger used to log track events and context changes. By default, the window.console is used.

The logger is disabled by default, to enable it, use the configure function.

Usage

setLogger({
   log: action => {
      yourCustomLogger(action)
   },
 })

Logger actions

There are different types of log actions, each one of them can be identified by the type property in the action object. Each action type has different properties in it's object, because they need different information to give you more context when debugging.

The available logger action types are:

event It's logged when a tracker event is triggered by the `trackEvent` function.

Output object format:

Object key Type Description
type string The logger action type
properties object All properties included in this track event
contextName string? The name of the tracker context which is being used for this track event

Example:

const context = createTrackerContext({ foo: 'bar' }, { name: 'main-context' })
const tracker = withTrackerContext(context)
tracker.trackEvent({ lorem: 'ipsum' }) // it's triggered here

Logger output:

{
  "type": "event",
  "contextName": "main-context",
  "properties": {
    "foo": "bar",
    "lorem": "ipsum"
  }
}
context-created It's logged when a tracker context is created.

Output object format:

Object key Type Description
type string The logger action type
properties object All initial properties provided on the createTrackerContext function
contextName string? The name of the tracker context which is being updated

Example:

createTrackerContext({ foo: 'bar', oi: 'tchau' }, { name: 'sample-context' }) // it's triggered here

Logger output:

{
  "type": "context-created",
  "contextName": "sample-context",
  "properties": {
    "foo": "bar",
    "oi": "tchau"
  }
}
context-updated It's logged when a tracker context is updated.

Output object format:

Object key Type Description
type string The logger action type
previousProperties object Previous properties within this tracker context, before calling setProps
currentProperties object Current properties within this tracker context, after calling setProps
contextName string? The name of the tracker context created

Example:

const context = createTrackerContext({ user: null, cool: false })
context.setProps({ user: 'jf9hd0' })  // it's triggered here

Logger output:

{
  "type": "context-updated",
  "previousProperties": {
    "user": null,
    "cool": false
  },
  "currentProperties": {
    "user": "jf9hd0"
  }
}

configure

It's the function responsible for configuring/customizing this package. Each option is responsible for one specific thing, so you can read the full list below.

Usage

configure({
  debugAll: false,
  debugEvents: true,
  targetProperty: () => myCustomDataLayer
})

Available options

Object key Type Default value Description
debugEvents boolean false Enables logging track events, these events are logged when the trackEvent function is called.
debugContext boolean false Enables logs for context operations. These events are logged when you call createTrackerContext, and when you call setProps in a created tracker context.
debugAll boolean false Enables logs for everything, is equivalent of setting all debug* options to true.
targetProperty function () => window.dataLayer This is the function that returns the array which the events are going to be pushed into. You should change it if you don't use the default window.dataLayer.

Error codes

To reduce the amount of data transferred over the network, the full error messages are excluded in the production build of this package. This means in development mode you will be able to see the full error messages when debugging your application, but if you have an error in production, this error will be identified by an error code.

Available error codes:

Code Message
0 You've called configure function without a configuration object.
1 Triggering events is not possible on server-side. Make sure to only trigger events after your app is running on the client-side.
2 The targetProperty is not defined. Make sure you didn't forget to add Google Tag Manager's script in your application. If you did but you don't use the default 'window.dataLayer' array, you can set your custom targetProperty with the configure function.
3 The targetProperty is not an array. Either you didn't installed Google Tag Manager correctly or you configured the targetProperty incorrectly.