Ed note: This article was updated on October 19, 2023.
Google’s Universal Analytics stops collecting data in one month, on July 1, 2023. Also, recent rulings related to Google Analytics and HIPAA has health care services providers scrambling for a HIPAA compliant alternative. So making it easy for these people to transition has been top of mind for me.
If you’ve already invested in building out your Google Data Layer, it can feel like a pain to ask your engineers to re-implement event tracking and enrichment for another tool like Heap. There are two ways to get this data into Heap without re-work:
- Using Google Tag Manager (something I plan to write a post on another time, let me know in the comments if you want to see this sooner than later)
- “Hijacking” the call to dataLayer.push() (which is what your engineers do to put data in it). That’s what this blog post is about.
Here’s the code, which you should make sure fires after the Heap snippet:
// For flattening the object so we don't have to worry about the details
function flattenObject(obj) {
var result = {};
function recurse(cur, prop) {
if (Object(cur) !== cur) {
result[prop] = cur;
} else if (Array.isArray(cur)) {
var arr = [];
for (var i = 0, l = cur.length; i < l; i++) {
if (typeof cur[i] === 'object' && !Array.isArray(cur[i])) {
arr.push(JSON.stringify(cur[i]));
} else {
arr.push(cur[i]);
}
}
result[prop] = arr.join(';');
} else {
var isEmpty = true;
for (var p in cur) {
isEmpty = false;
recurse(cur[p], prop ? prop + "." + p : p);
}
if (isEmpty && prop) {
result[prop] = {};
}
}
}
recurse(obj, "");
return result;
}
// Array of events to track, change to match your own events
events = ["page_view", "add_to_cart", "purchase"]
// Check for events push before Heap loaded
var initialEvents = dataLayer.filter(function(obj) {
if (events.includes(obj.event)) {
return obj;
}
});
if (initialEvents) {
initialEvents.forEach(function(obj) {
heap.track(obj.event, flattenObject(obj));
});
}
// Cache the original dataLayer.push function (important)
var originalPush = dataLayer.push;
// Modify the function to send data to Heap and then continue
dataLayer.push = function(obj) {
if (events.includes(obj.event)) {
heap.track(obj.event, flattenObject(obj))
}
originalPush.call(this, obj);
};
Note that this assumes you are using the more common dataLayer.push() approach to add an object to the array, and not gtag() to add an array. Your engineers should be able to easily modify it to use that pattern if you are using both.
You can extend this code to enrich your dataset further, for example by ingesting page and user properties pushed to the dataLayer. For example, if you push page
and user
objects, you can capture those with a few extra lines of code.
// Enrich with data pushed before Heap loaded
var initialPageProps = dataLayer.find(function(obj) {
return obj.page;
})?.page;
var initialUserProps = dataLayer.find(function(obj) {
return obj.user;
})?.user;
if (initialPageProps) {
heap.addEventProperties(initialPageProps);
}
if (initialUserProps) {
heap.addEventProperties(initialUserProps);
heap.addUserProperties(initialUserProps);
}
// Capture ongoing changes
dataLayer.push = function(obj) {
if (obj.page) {
heap.addEventProperties(obj.page);
}
if (obj.user) {
heap.addEventProperties(obj.user);
heap.addUserProperties(obj.user);
}
... rest of the code
originalPush.call(this, obj);
};
Got questions? Leave them in the comments.