{"id":102880,"date":"2025-02-22T06:36:47","date_gmt":"2025-02-22T06:36:47","guid":{"rendered":"https:\/\/peraltafinancing.com\/analytics\/persist-google-tag-managers-datalayer-across-pages\/"},"modified":"2025-02-22T06:36:47","modified_gmt":"2025-02-22T06:36:47","slug":"persist-google-tag-managers-datalayer-across-pages","status":"publish","type":"post","link":"https:\/\/fivemor.com\/?p=102880","title":{"rendered":"Persist Google Tag Manager&#8217;s dataLayer Across Pages"},"content":{"rendered":"<p> <br \/>\n<\/p>\n<div>\n<blockquote>\n<p><strong>UPDATE 4 June 2020<\/strong>: Instead of copying the Custom HTML code from the article, please load it from the <a href=\"https:\/\/gist.github.com\/sahava\/baee63650eed471a2d1eac5825037a05\">GitHub Gist<\/a> instead.<\/p>\n<\/blockquote>\n<p>Four years ago, I wrote an article on <a href=\"https:\/\/www.simoahava.com\/analytics\/persist-datalayer-google-tag-manager\/\">how to persist GTM\u2019s <code>dataLayer<\/code> from page to page<\/a>. Unfortunately, the solution was a bit clumsy, requiring you to give specific <em>commands<\/em> for the interactions, which made it really unwieldy in the long run. <a href=\"https:\/\/tagmanager.google.com\/\">Google Tag Manager<\/a> still doesn\u2019t offer us a native way to persist the <code>dataLayer<\/code> array or its <a href=\"https:\/\/www.simoahava.com\/analytics\/google-tag-manager-data-model\/\">internal data model<\/a> from one page to the other, so I thought it was about time I revisit this idea.<\/p>\n<div style=\"aspect-ratio: 2006 \/ 982;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/persist-datalayer.jpg\" title=\"Persist dataLayer\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"982\" width=\"2006\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/persist-datalayer.jpg#ZgotmplZ\" alt=\"Persist dataLayer\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>This time, there won\u2019t be an <em>API<\/em> to interact with. Instead, the solution will simply store the contents of the <code>dataLayer<\/code> array AND the internal data model from page to page, until the user hasn\u2019t interacted with GTM\u2019s <code>dataLayer<\/code> for a given amount of time.<\/p>\n<p>                <span class=\"simmer\"><br \/>\n  <span class=\"close\">X<\/span><\/p>\n<p>\n    <span class=\"fa fa-md fa-bell\"\/><br \/>\n    <strong>The Simmer Newsletter<\/strong>\n  <\/p>\n<p>\n    Subscribe to the <a href=\"https:\/\/www.simoahava.com\/newsletter\/\">Simmer newsletter<\/a> to get the latest news and content from Simo Ahava into your email inbox!\n  <\/p>\n<p>  <\/span><\/p>\n<h2 id=\"setting-it-up\">Setting it up<\/h2>\n<p>To set it up, you need to create a Custom HTML tag, in which you\u2019ll copy the following code. If you like, you can also copy the code from <a href=\"https:\/\/gist.github.com\/sahava\/baee63650eed471a2d1eac5825037a05\">this gist<\/a>.<\/p>\n<blockquote>\n<p><strong>UPDATE 4 June 2020<\/strong>: Please copy the code from the <strong>gist<\/strong> link above rather than from the article below. The gist is kept up-to-date, and has stability fixes that help resolve some issues users have been having with the solution.<\/p>\n<\/blockquote>\n<h3 id=\"the-custom-html-tag-code\">The Custom HTML tag code<\/h3>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-html\" data-lang=\"html\">script&gt;\n  (<span style=\"color:#00a\">function<\/span>() {\n    <span style=\"color:#aaa;font-style:italic\">\/\/ Set the timeout for when the dataLayer history should be purged. The default is 30 minutes.\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>    <span style=\"color:#aaa;font-style:italic\">\/\/ The timeout needs to be in milliseconds.\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>    <span style=\"color:#00a\">var<\/span> timeout = <span style=\"color:#099\">30<\/span>*<span style=\"color:#099\">60<\/span>*<span style=\"color:#099\">1000<\/span>;\n    \n    <span style=\"color:#aaa;font-style:italic\">\/\/ Change dataLayerName only if you've defined another named for the dataLayer array in your\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>    <span style=\"color:#aaa;font-style:italic\">\/\/ GTM container snippet.\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>    <span style=\"color:#00a\">var<\/span> dataLayerName = <span style=\"color:#a50\">'dataLayer'<\/span>;\n    \n    <span style=\"color:#aaa;font-style:italic\">\/\/ Don't change anything below.\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>    <span style=\"color:#aaa;font-style:italic\">\/\/ Initial settings\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>    <span style=\"color:#00a\">var<\/span> initialLoad     = <span style=\"color:#00a\">true<\/span>,\n        oldPush         = <span style=\"color:#0aa\">window<\/span>[dataLayerName].push;\n    \n    <span style=\"color:#aaa;font-style:italic\">\/\/ Method to copy items from dataLayer from before the GTM container snippet was loaded.\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>    <span style=\"color:#00a\">var<\/span> backfillHistory = <span style=\"color:#00a\">function<\/span>() {\n      <span style=\"color:#00a\">var<\/span> tempHistory = [],\n          i           = <span style=\"color:#099\">0<\/span>,\n          len         = <span style=\"color:#0aa\">window<\/span>[dataLayerName].length - <span style=\"color:#099\">1<\/span>;\n      <span style=\"color:#00a\">for<\/span> (; i window[dataLayerName][i]);\n      }\n      <span style=\"color:#00a\">return<\/span> tempHistory;\n    };\n    \n    <span style=\"color:#aaa;font-style:italic\">\/\/ Method to check if object is a plain object.\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>    <span style=\"color:#aaa;font-style:italic\">\/\/ From https:\/\/bit.ly\/2A3Fuqe\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>    <span style=\"color:#00a\">var<\/span> isPlainObject = <span style=\"color:#00a\">function<\/span>(value) {\n      <span style=\"color:#00a\">if<\/span> (!value || <span style=\"color:#00a\">typeof<\/span> value !== <span style=\"color:#a50\">'object'<\/span> ||    <span style=\"color:#aaa;font-style:italic\">\/\/ Nulls, dates, etc.\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>          value.nodeType ||                             <span style=\"color:#aaa;font-style:italic\">\/\/ DOM nodes.\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>          value === value.<span style=\"color:#0aa\">window<\/span>) {                      <span style=\"color:#aaa;font-style:italic\">\/\/ Window objects.\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>        <span style=\"color:#00a\">return<\/span> <span style=\"color:#00a\">false<\/span>;\n      }\n      <span style=\"color:#00a\">try<\/span> {\n        <span style=\"color:#00a\">if<\/span> (value.constructor &amp;&amp; !value.hasOwnProperty(<span style=\"color:#a50\">'constructor'<\/span>) &amp;&amp;\n            !value.constructor.prototype.hasOwnProperty(<span style=\"color:#a50\">'isPrototypeOf'<\/span>)) {\n          <span style=\"color:#00a\">return<\/span> <span style=\"color:#00a\">false<\/span>;\n        }\n      } <span style=\"color:#00a\">catch<\/span> (e) {\n        <span style=\"color:#00a\">return<\/span> <span style=\"color:#00a\">false<\/span>;\n      }\n      <span style=\"color:#00a\">var<\/span> key;\n      <span style=\"color:#00a\">for<\/span> (key <span style=\"color:#00a\">in<\/span> value) {}\n      <span style=\"color:#00a\">return<\/span> key === <span style=\"color:#00a\">undefined<\/span> || value.hasOwnProperty(key);\n    };\n    \n    <span style=\"color:#aaa;font-style:italic\">\/\/ Method to merge the stored data model and the history model together.\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>    <span style=\"color:#aaa;font-style:italic\">\/\/ From https:\/\/bit.ly\/2FrPQWL\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>    <span style=\"color:#00a\">var<\/span> mergeStates = <span style=\"color:#00a\">function<\/span>(storedModel, historyModel) {\n      <span style=\"color:#00a\">for<\/span> (<span style=\"color:#00a\">var<\/span> property <span style=\"color:#00a\">in<\/span> storedModel) {\n        <span style=\"color:#00a\">if<\/span> (storedModel.hasOwnProperty(property)) {\n          <span style=\"color:#00a\">var<\/span> storedProperty = storedModel[property];\n          <span style=\"color:#00a\">if<\/span> (<span style=\"color:#0aa\">Array<\/span>.isArray(storedProperty)) {\n            <span style=\"color:#00a\">if<\/span> (!<span style=\"color:#0aa\">Array<\/span>.isArray(historyModel[property])) historyModel[property] = [];\n            mergeStates(storedProperty, historyModel[property]);\n          } <span style=\"color:#00a\">else<\/span> <span style=\"color:#00a\">if<\/span> (isPlainObject(storedProperty)) {\n            <span style=\"color:#00a\">if<\/span> (!isPlainObject(historyModel[property])) historyModel[property] = {};\n            mergeStates(storedProperty, historyModel[property]);\n          } <span style=\"color:#00a\">else<\/span> {\n            historyModel[property] = storedProperty;\n          }\n        }\n      }\n    };\n    \n    <span style=\"color:#0aa\">window<\/span>[dataLayerName].push = <span style=\"color:#00a\">function<\/span>() {\n      <span style=\"color:#00a\">try<\/span> {\n        \n        <span style=\"color:#aaa;font-style:italic\">\/\/ Build the history array from local storage\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>        <span style=\"color:#0aa\">window<\/span>._dataLayerHistory = JSON.parse(\n          <span style=\"color:#0aa\">window<\/span>.localStorage.getItem(<span style=\"color:#a50\">'_dataLayerHistory'<\/span>) || \n          <span style=\"color:#a50\">'{\"timeout\": null, \"history\": [], \"model\": {}}'<\/span>\n        );\n        \n        <span style=\"color:#aaa;font-style:italic\">\/\/ Initial settings\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>        <span style=\"color:#00a\">var<\/span> timeNow     = <span style=\"color:#00a\">new<\/span> <span style=\"color:#0aa\">Date<\/span>().getTime(),\n            states      = [].slice.call(arguments, <span style=\"color:#099\">0<\/span>),\n            results     = oldPush.apply(<span style=\"color:#0aa\">window<\/span>[dataLayerName], states),\n            oDataLayer  = <span style=\"color:#0aa\">window<\/span>[dataLayerName],\n            dHistory    = <span style=\"color:#0aa\">window<\/span>._dataLayerHistory,\n            oDataModel  = <span style=\"color:#0aa\">window<\/span>.google_tag_manager[{{Container ID}}].dataLayer.get({split: <span style=\"color:#00a\">function<\/span>() { <span style=\"color:#00a\">return<\/span> []; }});\n        \n        <span style=\"color:#aaa;font-style:italic\">\/\/ Method to reset the history array to the current page state only\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>        dHistory.reset = <span style=\"color:#00a\">function<\/span>() {\n          dHistory.timeout = <span style=\"color:#00a\">null<\/span>;\n          dHistory.history = backfillHistory();\n          dHistory.model = {};\n          mergeStates(oDataModel, dHistory.model);\n          <span style=\"color:#0aa\">window<\/span>.localStorage.setItem(<span style=\"color:#a50\">'_dataLayerHistory'<\/span>, JSON.stringify(dHistory));\n        };\n      \n        <span style=\"color:#aaa;font-style:italic\">\/\/ From https:\/\/bit.ly\/2A2ZcCG\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>        dHistory.model.get = <span style=\"color:#00a\">function<\/span>(key) {\n          <span style=\"color:#00a\">var<\/span> target = dHistory.model;\n          <span style=\"color:#00a\">var<\/span> split = key.split(<span style=\"color:#a50\">'.'<\/span>);\n          <span style=\"color:#00a\">for<\/span> (<span style=\"color:#00a\">var<\/span> i = <span style=\"color:#099\">0<\/span>; i if (target[split[i]] === <span style=\"color:#00a\">undefined<\/span>) <span style=\"color:#00a\">return<\/span> <span style=\"color:#00a\">undefined<\/span>;\n            target = target[split[i]];\n          }\n          <span style=\"color:#00a\">return<\/span> target;\n        };\n\n        <span style=\"color:#aaa;font-style:italic\">\/\/ Add history if this is the initialization event itself\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>        <span style=\"color:#00a\">if<\/span> (initialLoad) {\n          dHistory.history = dHistory.history.concat(backfillHistory());\n          initialLoad = <span style=\"color:#00a\">false<\/span>;\n        }\n        \n        <span style=\"color:#aaa;font-style:italic\">\/\/ If timeout is reached, reset the history array\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>        <span style=\"color:#00a\">if<\/span> (dHistory.hasOwnProperty(<span style=\"color:#a50\">'timeout'<\/span>) &amp;&amp; dHistory.timeout \/\/ Push latest item from dataLayer into the history array\n<span style=\"color:#aaa;font-style:italic\"\/>        dHistory.history.push(oDataLayer[oDataLayer.length-<span style=\"color:#099\">1<\/span>]);\n        \n        <span style=\"color:#aaa;font-style:italic\">\/\/ Merge GTM's data model with the history model\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>        mergeStates(oDataModel, dHistory.model);\n        \n        <span style=\"color:#aaa;font-style:italic\">\/\/ Update the timeout\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>        dHistory.timeout = timeNow + timeout;\n        \n        <span style=\"color:#aaa;font-style:italic\">\/\/ Write the new history into localStorage\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>        <span style=\"color:#0aa\">window<\/span>.localStorage.setItem(<span style=\"color:#a50\">'_dataLayerHistory'<\/span>, JSON.stringify(dHistory));\n        <span style=\"color:#00a\">return<\/span> results;\n      } <span style=\"color:#00a\">catch<\/span>(e) {\n        console.log(<span style=\"color:#a50\">'Problem interacting with dataLayer history: '<\/span> + e);\n        <span style=\"color:#00a\">var<\/span> states  = [].slice.call(arguments, <span style=\"color:#099\">0<\/span>),\n            results = oldPush.apply(<span style=\"color:#0aa\">window<\/span>[dataLayerName], states);\n        <span style=\"color:#00a\">return<\/span> results;\n      }\n    };\n  })();\n<span style=\"color:#1e90ff;font-weight:bold\">script<\/span>&gt;<\/code><\/pre>\n<\/div>\n<p>In the very beginning of the snippet, there are two variables whose values you need to modify.<\/p>\n<p><code>var timeout = 30*60*1000;<\/code><\/p>\n<p>The line above establishes the <strong>timeout<\/strong> for the local storage. This means that once the user hasn\u2019t interacted with <code>dataLayer<\/code> for as long as you set in the timeout, the history will be reset to start from the current page. The default value is <strong>30 minutes<\/strong>, and if you want to modify it make sure you set the timeout in <strong>milliseconds<\/strong>, as in the default value.<\/p>\n<p><code>var dataLayerName=\"dataLayer\";<\/code><\/p>\n<p>The line above is the name of the <code>dataLayer<\/code> array that Google Tag Manager uses, and it defaults to the unmodified container snippet. If you\u2019ve changed the <code>dataLayer<\/code> name in the container snippet, make sure it\u2019s updated here, too.<\/p>\n<h3 id=\"other-tag-settings\">Other tag settings<\/h3>\n<p>In addition to copy-pasting the code above, set the <strong>Tag Priority<\/strong> value to <code>9999<\/code> or any number that\u2019s higher than any other Tag Priority for tags firing on the All Pages trigger.<\/p>\n<div style=\"aspect-ratio: 1822 \/ 568;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/tag-priority.jpg\" title=\"tag priority\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"568\" width=\"1822\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/tag-priority.jpg#ZgotmplZ\" alt=\"tag priority\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<h3 id=\"trigger\">Trigger<\/h3>\n<p>Set this Custom HTML tag to fire on the <strong>All Pages<\/strong> trigger. You want it to be the first tag that fires on the page. Naturally, if you have tags firing on an event that\u2019s pushed into <code>dataLayer<\/code> before the Google Tag Manager container snippet, you need to make sure this tag fires on <strong>that<\/strong> trigger instead.<\/p>\n<div style=\"aspect-ratio: 1858 \/ 390;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/all-pages-trigger.jpg\" title=\"All pages trigger\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"390\" width=\"1858\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/all-pages-trigger.jpg#ZgotmplZ\" alt=\"All pages trigger\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<h2 id=\"how-it-works\">How it works<\/h2>\n<p>Whenever something is pushed into <code>dataLayer<\/code>, it is also pushed into a new array under <code>window._dataLayerHistory<\/code>. This is a global object, and you can access it from anywhere on the page, including GTM\u2019s Custom HTML tags and Custom JavaScript variables.<\/p>\n<p>In addition to being added to this history array, this array is also consistently written into the <code>window.localStorage<\/code> structure, which persists across pages until the user decides to clear their browser storage.<\/p>\n<div style=\"aspect-ratio: 1856 \/ 624;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/local-storage.jpg\" title=\"browser localstorage\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"624\" width=\"1856\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/local-storage.jpg#ZgotmplZ\" alt=\"browser localstorage\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>In short, there\u2019s a new <code>window._dataLayerHistory<\/code> object that contains information about all the items pushed into <code>dataLayer<\/code> across pages, and you can access this object from any JavaScript context on the page.<\/p>\n<div style=\"aspect-ratio: 1368 \/ 792;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/datalayer-history.jpg\" title=\"data layer history\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"792\" width=\"1368\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/datalayer-history.jpg#ZgotmplZ\" alt=\"data layer history\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<h3 id=\"the-history-array\">The history array<\/h3>\n<p>The array itself, representing the history of the <code>window.dataLayer<\/code> array, can be found at <code>window._dataLayerHistory.history<\/code>.<\/p>\n<p>When the Custom HTML tag is first loaded on any page, this history array is first <strong>back-filled<\/strong> with items from the current <code>window.dataLayer<\/code> that were pushed before the Custom HTML tag was fired. This is necessary, because the Custom HTML tag creates its own <code>.push()<\/code> listener only when it fires, at which point the <code>window.dataLayer<\/code> array will already contain items.<\/p>\n<p>One quirky thing you might notice is that if there\u2019s a <code>window.dataLayer.push()<\/code> call taking place in a <a href=\"https:\/\/www.simoahava.com\/analytics\/understanding-tag-sequencing-in-google-tag-manager\/\">tag sequence<\/a>, the object pushed into the history array will not contain the <code>gtm.uniqueEventId<\/code> key. There\u2019s not much I can do about this, unfortunately, but it shouldn\u2019t be a big deal.<\/p>\n<h3 id=\"the-data-model\">The data model<\/h3>\n<p>If you\u2019re not familiar with <a href=\"https:\/\/www.simoahava.com\/analytics\/google-tag-manager-data-model\/\">GTM\u2019s data model<\/a>, it\u2019s essentially a lookup table to which GTM copies and merges the key-value pairs you push into the <code>dataLayer<\/code> array.<\/p>\n<p>It\u2019s important to understand this distinction, because GTM uses the internal data model for <strong>Data Layer variables<\/strong>.<\/p>\n<div style=\"aspect-ratio: 2022 \/ 384;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/datalayer-model.jpg\" title=\"data model\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"384\" width=\"2022\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/datalayer-model.jpg#ZgotmplZ\" alt=\"data model\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>The data model is also persisted from page to page in <code>window._dataLayerHistory.model<\/code>. This object has a <code>get()<\/code> method you can use to fetch data model values, just like GTM\u2019s own native interface does:<\/p>\n<p><code>window._dataLayerHistory.model.get('someOldVariableFromAPreviousPage');<\/code><\/p>\n<p>This might be useful. For example, if your site writes to <code>dataLayer<\/code> something like <code>{userLoggedIn: true}<\/code> when the user logs into the site, but it only does this when the user actual logs in, you can fetch this value on later pages by querying the history object:<\/p>\n<p><code>window._dataLayerHistory.model.get('userLoggedIn');<\/code><\/p>\n<p>The history model applies the same type of <strong>recursive merge<\/strong> that GTM does with its internal data model. This might lead to unexpected outcomes with objects and arrays, so be sure to read up on <a href=\"https:\/\/www.simoahava.com\/analytics\/google-tag-manager-data-model\/#add-and-modify-the-key\">recursive merge<\/a> before moving on.<\/p>\n<h3 id=\"reset\">Reset<\/h3>\n<p>You can also reset the history by executing this command:<\/p>\n<p><code>window._dataLayerHistory.reset();<\/code><\/p>\n<p>This nulls the timeout and resets the history array and history model to the states of the current page, thus removing any history from both. It also resets the object stored in browser storage to this, reset state.<\/p>\n<h2 id=\"applications\">Applications<\/h2>\n<p>There are many things you could do with a persistent <code>dataLayer<\/code> and data model. Here are some examples.<\/p>\n<h3 id=\"get-number-of-pages-loaded\">Get number of pages loaded<\/h3>\n<p>To identify how many pages the user has visited, you could have a Custom JavaScript variable that does this:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-javascript\" data-lang=\"javascript\"><span style=\"color:#00a\">function<\/span>() {\n  <span style=\"color:#00a\">return<\/span> <span style=\"color:#0aa\">window<\/span>._dataLayerHistory.history.filter(<span style=\"color:#00a\">function<\/span>(obj) { <span style=\"color:#00a\">return<\/span> obj.event === <span style=\"color:#a50\">'gtm.js'<\/span>; }).length;\n}\n<\/code><\/pre>\n<\/div>\n<p>This returns the number of times that the <code>gtm.js<\/code> event has been pushed into <code>dataLayer<\/code>, and you can use this as a reasonably good proxy for determining how many pages the user has visited. Note that if you use either <a href=\"https:\/\/developers.google.com\/analytics\/devguides\/collection\/gtagjs\/\">gtag.js<\/a> or <a href=\"https:\/\/marketingplatform.google.com\/about\/optimize\/\">Google Optimize<\/a>, this variable might not return an accurate result.<\/p>\n<h3 id=\"check-the-entire-datalayer-history-for-some-key-or-value\">Check the entire <code>dataLayer<\/code> history for some key or value<\/h3>\n<p>Here\u2019s an extension of <a href=\"https:\/\/www.simoahava.com\/analytics\/search-datalayer-for-key-value-pair\/\">this solution<\/a> I wrote for the <code>window.dataLayer<\/code> array (i.e. the <code>dataLayer<\/code> of the <strong>current<\/strong> page only). With this, you can search the entire history of the <code>dataLayer<\/code> array for a given key-value pair. This is what the modified Custom JavaScript variable looks like:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-javascript\" data-lang=\"javascript\"><span style=\"color:#00a\">function<\/span>() {\n  <span style=\"color:#aaa;font-style:italic\">\/\/ Modify the searchObject below.\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>  <span style=\"color:#aaa;font-style:italic\">\/\/\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>  <span style=\"color:#aaa;font-style:italic\">\/\/ Add each key-value pair you want to look for directly into the searchObject object. Use\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>  <span style=\"color:#aaa;font-style:italic\">\/\/ strings for keys. \n<\/span><span style=\"color:#aaa;font-style:italic\"\/>  <span style=\"color:#aaa;font-style:italic\">\/\/\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>  <span style=\"color:#aaa;font-style:italic\">\/\/ The variable will look for any key-value pair you specify, and return true if any one of them\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>  <span style=\"color:#aaa;font-style:italic\">\/\/ is found. If you use dot notation, the variable will try to find a key with this name first,\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>  <span style=\"color:#aaa;font-style:italic\">\/\/ after which it will parse the nested structure looking for a match.\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>  <span style=\"color:#00a\">var<\/span> searchObject = {\n    <span style=\"color:#a50\">'user.consentGiven'<\/span>: <span style=\"color:#a50\">'false'<\/span>\n  };\n  \n  <span style=\"color:#00a\">var<\/span> dataLayerName = <span style=\"color:#a50\">'_dataLayerHistory'<\/span>;\n  \n  <span style=\"color:#aaa;font-style:italic\">\/\/ Don't edit anything below this line.\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>  <span style=\"color:#00a\">var<\/span> getValueForObjectString = <span style=\"color:#00a\">function<\/span>(obj, key) {\n    <span style=\"color:#00a\">return<\/span> key.split(<span style=\"color:#a50\">\".\"<\/span>).reduce(<span style=\"color:#00a\">function<\/span>(o, x) {\n        <span style=\"color:#00a\">return<\/span> (<span style=\"color:#00a\">typeof<\/span> o == <span style=\"color:#a50\">\"undefined\"<\/span> || o === <span style=\"color:#00a\">null<\/span>) ? o : o[x];\n    }, obj);\n  };\n  \n  <span style=\"color:#00a\">return<\/span> <span style=\"color:#0aa\">window<\/span>[dataLayerName].history.filter(<span style=\"color:#00a\">function<\/span>(obj) {\n    <span style=\"color:#00a\">var<\/span> found = <span style=\"color:#00a\">false<\/span>;\n    <span style=\"color:#00a\">var<\/span> prop;\n    \n    <span style=\"color:#00a\">for<\/span> (prop <span style=\"color:#00a\">in<\/span> searchObject) {\n      <span style=\"color:#00a\">if<\/span> (obj[prop] == searchObject[prop] || getValueForObjectString(obj, prop) == searchObject[prop]) {\n        found = <span style=\"color:#00a\">true<\/span>;\n      }\n    }\n    <span style=\"color:#00a\">return<\/span> found;\n  }).length &gt; <span style=\"color:#099\">0<\/span>;\n}\n<\/code><\/pre>\n<\/div>\n<h2 id=\"summary\">Summary<\/h2>\n<p>You can use this script to persist the <code>dataLayer<\/code> array as well as Google Tag Manager\u2019s internal data model from one page to the next.<\/p>\n<p>It\u2019s not foolproof. For example, it doesn\u2019t understand <strong>command arrays<\/strong> or <strong>command functions<\/strong>, and it doesn\u2019t understand any manual <code>.set()<\/code> commands you run against GTM\u2019s own, internal data model. (Note, if any of the terms in this paragraph were alien to you, please read my article on <a href=\"https:\/\/www.simoahava.com\/analytics\/google-tag-manager-data-model\/\">GTM\u2019s internal data model<\/a>).<\/p>\n<p>As always, this was more a tech demo than a turnkey solution. Please let me know in the comments if you have uses for this kind of a solution. Also, if you have improvement suggestions, let me know of those, too!<\/p>\n<\/p><\/div>\n\n","protected":false},"excerpt":{"rendered":"<p>UPDATE 4 June 2020: Instead of copying the Custom HTML code from the article, please load it from the GitHub Gist instead. Four years ago, I wrote an article on how to persist GTM\u2019s dataLayer from page to page. Unfortunately, the solution was a bit clumsy, requiring you to give specific commands for the interactions, [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":102881,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[12033],"tags":[38540,1531,11419,3845,21084,1791],"dealstore":[],"offerexpiration":[],"class_list":["post-102880","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-analytics","tag-datalayer","tag-google","tag-managers","tag-pages","tag-persist","tag-tag"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v26.4 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Persist Google Tag Manager&#039;s dataLayer Across Pages - Som2ny Network<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/fivemor.com\/?p=102880\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Persist Google Tag Manager&#039;s dataLayer Across Pages - Som2ny Network\" \/>\n<meta property=\"og:description\" content=\"UPDATE 4 June 2020: Instead of copying the Custom HTML code from the article, please load it from the GitHub Gist instead. Four years ago, I wrote an article on how to persist GTM\u2019s dataLayer from page to page. Unfortunately, the solution was a bit clumsy, requiring you to give specific commands for the interactions, [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/fivemor.com\/?p=102880\" \/>\n<meta property=\"og:site_name\" content=\"Som2ny Network\" \/>\n<meta property=\"article:published_time\" content=\"2025-02-22T06:36:47+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/persist-datalayer.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"2006\" \/>\n\t<meta property=\"og:image:height\" content=\"982\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"admin\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"admin\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"9 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/fivemor.com\/?p=102880#article\",\"isPartOf\":{\"@id\":\"https:\/\/fivemor.com\/?p=102880\"},\"author\":{\"name\":\"admin\",\"@id\":\"https:\/\/fivemor.com\/#\/schema\/person\/b85e3c3dc0e1daea076524dc8810c371\"},\"headline\":\"Persist Google Tag Manager&#8217;s dataLayer Across Pages\",\"datePublished\":\"2025-02-22T06:36:47+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/fivemor.com\/?p=102880\"},\"wordCount\":1182,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/fivemor.com\/#organization\"},\"image\":{\"@id\":\"https:\/\/fivemor.com\/?p=102880#primaryimage\"},\"thumbnailUrl\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/persist-datalayer.jpg\",\"keywords\":[\"dataLayer\",\"Google\",\"Managers\",\"pages\",\"persist\",\"TAG\"],\"articleSection\":[\"Analytics\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/fivemor.com\/?p=102880#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/fivemor.com\/?p=102880\",\"url\":\"https:\/\/fivemor.com\/?p=102880\",\"name\":\"Persist Google Tag Manager's dataLayer Across Pages - Som2ny Network\",\"isPartOf\":{\"@id\":\"https:\/\/fivemor.com\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/fivemor.com\/?p=102880#primaryimage\"},\"image\":{\"@id\":\"https:\/\/fivemor.com\/?p=102880#primaryimage\"},\"thumbnailUrl\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/persist-datalayer.jpg\",\"datePublished\":\"2025-02-22T06:36:47+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/fivemor.com\/?p=102880#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/fivemor.com\/?p=102880\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/fivemor.com\/?p=102880#primaryimage\",\"url\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/persist-datalayer.jpg\",\"contentUrl\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/persist-datalayer.jpg\",\"width\":2006,\"height\":982},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/fivemor.com\/?p=102880#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/fivemor.com\/?bp_activities=1\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Persist Google Tag Manager&#8217;s dataLayer Across Pages\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/fivemor.com\/#website\",\"url\":\"https:\/\/fivemor.com\/\",\"name\":\"Som2ny Network\",\"description\":\"Daily Deals\",\"publisher\":{\"@id\":\"https:\/\/fivemor.com\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/fivemor.com\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/fivemor.com\/#organization\",\"name\":\"Som2ny Network\",\"url\":\"https:\/\/fivemor.com\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/fivemor.com\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2026\/07\/4a0953c4-logo-300x86-1.png\",\"contentUrl\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2026\/07\/4a0953c4-logo-300x86-1.png\",\"width\":300,\"height\":86,\"caption\":\"Som2ny Network\"},\"image\":{\"@id\":\"https:\/\/fivemor.com\/#\/schema\/logo\/image\/\"}},{\"@type\":\"Person\",\"@id\":\"https:\/\/fivemor.com\/#\/schema\/person\/b85e3c3dc0e1daea076524dc8810c371\",\"name\":\"admin\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/fivemor.com\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/729ae85bf62b9917e93538db2f2688ca?s=96&r=g&default=https%3A%2F%2Ffivemor.com%2Fwp-content%2Fplugins%2Fbuddypress-first-letter-avatar%2Fimages%2Fdefault%2F96%2Flatin_a.png\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/729ae85bf62b9917e93538db2f2688ca?s=96&r=g&default=https%3A%2F%2Ffivemor.com%2Fwp-content%2Fplugins%2Fbuddypress-first-letter-avatar%2Fimages%2Fdefault%2F96%2Flatin_a.png\",\"caption\":\"admin\"},\"sameAs\":[\"https:\/\/fivemor.com\"],\"url\":\"https:\/\/fivemor.com\/?author=1\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Persist Google Tag Manager's dataLayer Across Pages - Som2ny Network","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/fivemor.com\/?p=102880","og_locale":"en_US","og_type":"article","og_title":"Persist Google Tag Manager's dataLayer Across Pages - Som2ny Network","og_description":"UPDATE 4 June 2020: Instead of copying the Custom HTML code from the article, please load it from the GitHub Gist instead. Four years ago, I wrote an article on how to persist GTM\u2019s dataLayer from page to page. Unfortunately, the solution was a bit clumsy, requiring you to give specific commands for the interactions, [&hellip;]","og_url":"https:\/\/fivemor.com\/?p=102880","og_site_name":"Som2ny Network","article_published_time":"2025-02-22T06:36:47+00:00","og_image":[{"width":2006,"height":982,"url":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/persist-datalayer.jpg","type":"image\/jpeg"}],"author":"admin","twitter_card":"summary_large_image","twitter_misc":{"Written by":"admin","Est. reading time":"9 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/fivemor.com\/?p=102880#article","isPartOf":{"@id":"https:\/\/fivemor.com\/?p=102880"},"author":{"name":"admin","@id":"https:\/\/fivemor.com\/#\/schema\/person\/b85e3c3dc0e1daea076524dc8810c371"},"headline":"Persist Google Tag Manager&#8217;s dataLayer Across Pages","datePublished":"2025-02-22T06:36:47+00:00","mainEntityOfPage":{"@id":"https:\/\/fivemor.com\/?p=102880"},"wordCount":1182,"commentCount":0,"publisher":{"@id":"https:\/\/fivemor.com\/#organization"},"image":{"@id":"https:\/\/fivemor.com\/?p=102880#primaryimage"},"thumbnailUrl":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/persist-datalayer.jpg","keywords":["dataLayer","Google","Managers","pages","persist","TAG"],"articleSection":["Analytics"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/fivemor.com\/?p=102880#respond"]}]},{"@type":"WebPage","@id":"https:\/\/fivemor.com\/?p=102880","url":"https:\/\/fivemor.com\/?p=102880","name":"Persist Google Tag Manager's dataLayer Across Pages - Som2ny Network","isPartOf":{"@id":"https:\/\/fivemor.com\/#website"},"primaryImageOfPage":{"@id":"https:\/\/fivemor.com\/?p=102880#primaryimage"},"image":{"@id":"https:\/\/fivemor.com\/?p=102880#primaryimage"},"thumbnailUrl":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/persist-datalayer.jpg","datePublished":"2025-02-22T06:36:47+00:00","breadcrumb":{"@id":"https:\/\/fivemor.com\/?p=102880#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/fivemor.com\/?p=102880"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/fivemor.com\/?p=102880#primaryimage","url":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/persist-datalayer.jpg","contentUrl":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/persist-datalayer.jpg","width":2006,"height":982},{"@type":"BreadcrumbList","@id":"https:\/\/fivemor.com\/?p=102880#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/fivemor.com\/?bp_activities=1"},{"@type":"ListItem","position":2,"name":"Persist Google Tag Manager&#8217;s dataLayer Across Pages"}]},{"@type":"WebSite","@id":"https:\/\/fivemor.com\/#website","url":"https:\/\/fivemor.com\/","name":"Som2ny Network","description":"Daily Deals","publisher":{"@id":"https:\/\/fivemor.com\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/fivemor.com\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/fivemor.com\/#organization","name":"Som2ny Network","url":"https:\/\/fivemor.com\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/fivemor.com\/#\/schema\/logo\/image\/","url":"https:\/\/fivemor.com\/wp-content\/uploads\/2026\/07\/4a0953c4-logo-300x86-1.png","contentUrl":"https:\/\/fivemor.com\/wp-content\/uploads\/2026\/07\/4a0953c4-logo-300x86-1.png","width":300,"height":86,"caption":"Som2ny Network"},"image":{"@id":"https:\/\/fivemor.com\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":"https:\/\/fivemor.com\/#\/schema\/person\/b85e3c3dc0e1daea076524dc8810c371","name":"admin","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/fivemor.com\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/729ae85bf62b9917e93538db2f2688ca?s=96&r=g&default=https%3A%2F%2Ffivemor.com%2Fwp-content%2Fplugins%2Fbuddypress-first-letter-avatar%2Fimages%2Fdefault%2F96%2Flatin_a.png","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/729ae85bf62b9917e93538db2f2688ca?s=96&r=g&default=https%3A%2F%2Ffivemor.com%2Fwp-content%2Fplugins%2Fbuddypress-first-letter-avatar%2Fimages%2Fdefault%2F96%2Flatin_a.png","caption":"admin"},"sameAs":["https:\/\/fivemor.com"],"url":"https:\/\/fivemor.com\/?author=1"}]}},"_links":{"self":[{"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/posts\/102880","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=102880"}],"version-history":[{"count":0,"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/posts\/102880\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/media\/102881"}],"wp:attachment":[{"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=102880"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=102880"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=102880"},{"taxonomy":"dealstore","embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fdealstore&post=102880"},{"taxonomy":"offerexpiration","embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fofferexpiration&post=102880"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}