One of the difficulties of working with Google Tag Manager and the dataLayer structure is that GTM doesn’t preserve history of the items collected into its data model. Or, at least, it doesn’t preserve it in a manner that would let us access it.

This is typically a very niche problem, but it does surface every now and then. For example, say you wanted to query whether an event with some specific value has already been pushed into dataLayer. Well, this is impossible, since any event you use to trigger this check would overwrite this previous event value in the data model (since any given key can only have one value at a time).

In other words, we need a mechanism that lets us query for earlier values of specific keys which have been (possibly) overwritten since then. For this, we’ll need some Custom JavaScript variable magic.

Tip 91: Looking for key-value pairs

First, create the Custom JavaScript variable. This is the code you’ll need to add:

function() {
  // Modify the searchObject below.
  //
  // Add each key-value pair you want to look for directly into the searchObject object. Use
  // strings for keys. 
  //
  // The variable will look for any key-value pair you specify, and return true if any one of them
  // is found. If you use dot notation, the variable will try to find a key with this name first,
  // after which it will parse the nested structure looking for a match.
  var searchObject = {
    'user.consentGiven': 'false'
  };
  
  // Change this if you have renamed the dataLayer array.
  var dataLayerName = 'dataLayer';
  
  // Don't edit anything below this line.
  var getValueForObjectString = function(obj, key) {
    return key.split(".").reduce(function(o, x) {
        return (typeof o == "undefined" || o === null) ? o : o[x];
    }, obj);
  };
  
  return window[dataLayerName].filter(function(obj) {
    var found = false;
    var prop;
    
    for (prop in searchObject) {
      if (obj[prop] == searchObject[prop] || getValueForObjectString(obj, prop) == searchObject[prop]) {
        found = true;
      }
    }
    return found;
  }).length > 0;
}

Here, you’ll need to modify the searchObject by adding key-value pairs you want to search for in the dataLayer array.

For example, if you wanted to find whether the event key has had the value askForConsent at some point, the object would look like this:

var searchObject = {
  'event': 'askForConsent'
};

If you wanted to check whether that event value was pushed earlier OR whether the user has already given consent, you could do this:

var searchObject = {
  'event': 'askForConsent',
  'user.consentGiven': true
};

Note that if you use dot notation in the key name, the variable will first check whether a key with exactly that name (user.consentGiven) is found, after which it will check whether a nested structure like that ({user: {consentGiven: true}}) is found.

Next, you’ll need to modify the dataLayerName variable value in case you have renamed the dataLayer array to something else than the default.

And then you’re all set. The variable will return true in case any of the key-value pairs you specify in the searchObject is found in the dataLayer array.

Caveats

There are two major caveats with this solution.

1. dataLayer is unreliable

Nothing’s stopping site code from deleting items from the dataLayer. In fact, if your array size surpasses 300 members, Google Tag Manager will automatically start removing items from the beginning.

Similarly, if you’re running a single-page app, it’s possible the array is cleared between each page transition.

In other words, there’s no guarantee that this variable will find anything simply because the dataLayer array is not immutable nor is it supposed to be.

2. Timing matters

Another important thing to note is that the dataLayer array is checked when the variable is run. This might lead to some unexpected results, especially if you’re comparing the variable to what Preview mode is showing.

When a tag fires with a trigger, Preview mode will show the state of Google Tag Manager’s variables at the time the trigger event was pushed into dataLayer. This might be a significant amount of time before the tag and its associated variables are actually executed.

Thus, the state of the web page that the variable tries to access might differ from the state of the web page when the tag was actually triggered. This is mainly a problem if you try to consolidate what you try to look for with searchObject and what you try to access with Data Layer variables.

For example, if the object with user.consentGiven is pushed a very short while after the event that triggered the tag (and the search variable), the tag will not be able to access this with a Data Layer variable, but the Custom JavaScript variable’s searchObject will find the match in the dataLayer array.

This is just something to pay attention to, especially if you’re wondering why the variable claims something is found in dataLayer but the tag still can’t access that particular value.

Summary

Do you have ideas where this variable might be useful? I’ve mainly come across two use cases:

  1. On a single-page app, blocking the All Pages trigger from firing a Page View if there already is a “virtual pageview” event in dataLayer.

  2. Checking for state transitions, such as whether a user used to be logged out, but is now logged in (compared to being logged in all the time).

What else?