Two Ways to Persist Data via Google Tag Manager

Introducing browser cookies and HTML5 storage as options for data persistence when using Google Tag Manager.

The web is stateless. It’s basically blind to your past, and it does a poor job of predicting what you might do in the future. When you browse to a website, the browser requests the page from the web server, and then proceeds to render it for you. This is a detached, clinical process, and any personalized or stateful data transfer is left to the sophistication of your web server.

Statelessness has some implications to us concerned about web analytics and measuring our visitors. In Google Tag Manager, this is an ever-present elephant in the room. Indeed, many suffer over not being able to fire Tags for e.g. returning visitors, or after five page views, or if the original source of the Session was AdWords. GTM is (for now) blind to this type of thinking, as it’s simply a JavaScript machine operating in a stateless environment. Any notion of state needs to be injected manually into the machine.

And how is this done? Cookies and the Web Storage API.

Browser cookies

Cookies in the browser are the de facto way of persisting information in the web. They’ve been around for a long time (over 20 years!), and they’re supported by pretty much all browsers - legacy and modern. For a refresher on what cookies are and how they work, check out this Wikipedia article. The most relevant features of browser cookies are:

  1. 4KB size limitation (per cookie)

  2. Unencrypted in a single string, stored in document.cookie

  3. Sent to the server with every single HTTP request

  4. Can be set with an expiration date

The size limitation quickly becomes an issue with large payloads, and the fact that they are sent to the server unencrypted with each request can be a turn-off for those with security concerns.

Web Storage API

The Web Storage API is a more recent innovation, and this is represented by lack of support from Internet Explorer 7 and older (who cares, though!). The API has two interfaces: localStorage and sessionStorage. The main difference is that the latter persists for the duration of the browser session (i.e. is flushed when the browser instance is shut down), and the former persists indefinitely.

Features of the Web Storage API are:

  1. Can only be read client-side in the browser. No data is sent to the server.

  2. Stores a maximum of 5MB per domain

  3. Stores data in a hash table, meaning you can make exact queries without having to parse the data

Basically, the Web Storage API is a logical evolution of persistence, taking the best of cookies and adding better data access and management features. The big drawback is the lack of server-side communication, so if you rely on cookies to pass information to the web server, migrating to the Web Storage API wouldn’t be logical unless you manually send the data to the server (e.g. via POST request).

Cookies and GTM

Google Tag Manager, thankfully, already provides us an interface to query for cookie values: the 1st Party Cookie Variable. When you set the Variable to a cookie name, it will always return whatever value is stored in that particular cookie. This is a huge help, since you don’t need to go through the rather tedious process of parsing the entire cookie string for the name-value pair you’re looking for.

Setting a cookie, however, still needs to be done manually. The easiest way to set a cookie is to add the following code in a Custom HTML Tag:

document.cookie = 'cookieName=cookieValue';

This would set a cookie with the name cookieName and the value cookieValue. However, using this code alone would set the cookie to expire at the end of the browser session (i.e. when the browser is shut down), and the cookie would be set on the current path and the current domain only. The path and domain combination is especially important, as the cookie would only be accessible via the page it was set on.

This is why it’s customary to create a helper function, with which you can provide some important parameters. To create this type of helper in GTM’s context, I suggest using a Custom JavaScript Variable named {{JS - setCookie}}:

function() {
  return function(name, value, ms, path, domain) {
    if (!name || !value) {
    var d;
    var cpath = path ? '; path=' + path : '';
    var cdomain = domain ? '; domain=' + domain : '';
    var expires = '';
    if (ms) {
      d = new Date();
      d.setTime(d.getTime() + ms);
      expires = '; expires=' + d.toUTCString();
    document.cookie = name + "=" + value + expires + cpath + cdomain;

This handy little function actually returns another function, which takes a number of parameters:

  • name: Required. The cookie name.

  • value: Required. Value for the cookie.

  • ms: Expiration time of the cookie in milliseconds. If not set, defaults to Session.

  • path: Path of the cookie. If not set, defaults to the current path.

  • domain: Domain of the cookie. If not set, defaults to the current domain.

To use this Variable in a script, you need to use the following syntax:

{{JS - setCookie}}('session', 'true', 1800000, '/', '');

This would set a cookie with the name session and value true, which persists for 30 minutes, and is written on the root page of the root domain.

If you want to leave a parameter out, you’ll need to replace it with undefined, if you still want to send one of the latter parameters. For example:

{{JS - setCookie}}('session', 'true', undefined, undefined, '');

Would set a cookie with name session, value true, which would be a Session cookie, written on the current page of the root domain.

Phew! That’s a mouthful of code. But it does its job nicely. Combine this with the excellent 1st Party Cookie Variable, and you’ve managed to create a nice and handy cookie setter/getter.

Web Storage API and GTM

You could create a fancy set of Custom JavaScript Variables for localStorage and sessionStorage as well, but why bother? They’re dead simple to interact with.

To set a key-value pair in storage, you can use the following syntax:

if (window['Storage']) {
  // localStorage persists indefinitely
  localStorage.setItem('session', 'true'); 
  // sessionStorage persists until the browser is closed
  sessionStorage.setItem('session', 'true');
} else {
  // Fallback for when the browser doesn't support Web Storage API
  {{JS - setCookie}}('session', 'true');

First of all, you need to check if the browser supports the Web Storage API. That’s just a simple if...else block. See what I did with the fallback? That’s right! I used the {{JS - setCookie}} method we just created! What a beautiful segue.

Web Storage only accepts strings as values, so you need to make sure whatever you’re storing can be safely converted into a String. Don’t worry, the Storage API does the casting for you, if you fail to explicitly save a String type.

Unfortunately, as of yet GTM does not have a handy Variable you can use to fetch items from storage (I hope this changes soon!). But to retrieve a value is just as simple as setting one:

if (window['Storage']) {
  var localSession = localStorage.getItem('session');
  var sessionSession = sessionStorage.getItem('session');

That’s right, it’s the getItem() method of the API that lets you access stored keys. It returns null if the item isn’t found.

And that’s how you roll with the Web Storage API. You get by with a LOT less code, and the value lookup is way more intuitive than with cookies, though GTM does take a lot of the burden away from you with its awesome 1st Party Cookie Variable.


For many interactions, persisting data in the browser is very important. However, not all persistence should be left for the client, since servers do a far better and more robust job of storing user- and usage-based data. That’s why Google Analytics shifted from client-side calculations (ga.js and older) to server-side algorithms (Universal Analytics). And that’s why if you really have pressing and business-critical needs to persist information, you might want to talk to your developers about storing this in a proper data store or database, exposing the information in the dataLayer when and where relevant.

But for the simple, session-scoped persistence, the two methods outlined in this guide should be quite useful. I strongly recommend using the Web Storage API, but unfortunately you might need to introduce a fallback with browser cookies, since there are still many fools out there using outdated browsers.