Engineers Love This One Weird (Google Data Layer) Trick!

  • 2 June 2023
  • 3 replies
Engineers Love This One Weird (Google Data Layer) Trick!
Userlevel 5
Badge +3

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:

  1. 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)
  2. “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])) {
} else {
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))
}, 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) {

var initialUserProps = dataLayer.find(function(obj) {
return obj.user;

if (initialPageProps) {

if (initialUserProps) {

// Capture ongoing changes

dataLayer.push = function(obj) {
if ( {

if (obj.user) {

... rest of the code, obj);


Got questions? Leave them in the comments.

3 replies

Userlevel 3
Badge +3

@jonathan This is amazing! And since you mentioned it, we look forward to your Google Tag Manager write-up!

Userlevel 5
Badge +3

Code has been updated to accommodate use of gtag() to send events.


@jonathan this is great, we have a very rich datalayer buildout for GA360 accommodating virtual pageviews and about 150 custom variables. We and are currently transitioning to HEAP. I would love to be collecting this data as well. We leverage GTM across most sites which is how we are implementing the HEAP js. I’d really appreciate the suggested GTM implementation of the above code.

You’ve included some code to capture ongoing changes to the datalayer. It would be great to see how to best implement via GTM as we have all user interactions post to the datalayer.