{"id":100662,"date":"2025-02-21T02:54:42","date_gmt":"2025-02-21T02:54:42","guid":{"rendered":"https:\/\/peraltafinancing.com\/analytics\/gtmtips-use-localstorage-for-client-id-persistence-in-google-analytics\/"},"modified":"2025-02-21T02:54:42","modified_gmt":"2025-02-21T02:54:42","slug":"gtmtips-use-localstorage-for-client-id-persistence-in-google-analytics","status":"publish","type":"post","link":"https:\/\/fivemor.com\/?p=100662","title":{"rendered":"#GTMTips: Use localStorage For Client ID Persistence In Google Analytics"},"content":{"rendered":"<p> <br \/>\n<\/p>\n<div>\n<blockquote>\n<p><strong>Updated 1 October 2019<\/strong> With <a href=\"https:\/\/webkit.org\/blog\/9521\/intelligent-tracking-prevention-2-3\/\">ITP 2.3<\/a> it looks like Safari is reducing the usefulness of <code>localStorage<\/code> as well, so this solution should <strong>not<\/strong> be considered future-proof. The only stable way to persist client-side data at the moment seems to be <a href=\"https:\/\/www.simoahava.com\/google-cloud\/create-cookie-rewrite-web-service-google-cloud\/\">HTTP cookies<\/a>.<\/p>\n<\/blockquote>\n<blockquote>\n<p><strong>Updated 7 March 2019<\/strong> &#8211; Added some extra <a href=\"#caveats\">caveats<\/a> to this solution. Also, be sure to read my article on <a href=\"https:\/\/www.simoahava.com\/analytics\/itp-2-1-and-web-analytics\/\">ITP 2.1<\/a>, which has far more detail on what Intelligent Tracking Prevention is and how to work with it.<\/p>\n<\/blockquote>\n<p>Looks like Safari is tightening the noose around browser cookies with the introduction of <a href=\"https:\/\/webkit.org\/blog\/8613\/intelligent-tracking-prevention-2-1\/\">ITP 2.1<\/a> (Intelligent Tracking Prevention). Among other things, <strong>ITP 2.1<\/strong> caps the expiration of client-side cookies to <strong>7 days<\/strong>. <em>Client-side cookies<\/em> in this context refer to cookies set with the <code>document.cookie<\/code> API, which would cover pretty much all cookies set by vendored JavaScript libraries like Google Analytics\u2019 <strong>analytics.js<\/strong>.<\/p>\n<p>Personally, I think anything that forces a rethink of the cookie model is welcome. It\u2019s awkward and twisted that we still rely on such flimsy and fragile browser storage for persisting important data such as anonymous analytics identifiers, authentication flags, and basically anything that needs to persist from one page to the next.<\/p>\n<p>Having said that, this can make <em>potentially benevolent<\/em> tracking such as that done with <a href=\"https:\/\/analytics.google.com\/\">Google Analytics<\/a> on a website quite difficult for users with Safari browsers. Since GA relies on a browser cookie capped at 2 years\u2019 expiration (by default), this 7 days max can really hurt data quality.<\/p>\n<p>In this article, I want to show you how to use <a href=\"https:\/\/www.simoahava.com\/analytics\/customtask-the-guide\/\"><code>customTask<\/code><\/a> with <code>localStorage<\/code> to serve as a <em>backup<\/em> of sorts for your cookie-based Client ID persistence.<\/p>\n<p>It\u2019s not perfect. Check out the <a href=\"#caveats\">caveats<\/a> chapter for details. Please treat this as a technology demo rather than a be-all, end-all solution to cookie woes. We\u2019ll need to rely on the vendors and browsers finding consensus for how to make life harder on the bad agents without compromising the work us \u201cregular folks\u201d need to do to improve our websites with first-party analytics data.<\/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=\"tip-96-persist-the-client-id-in-localstorage\">Tip 96: Persist the Client ID in <code>localStorage<\/code><\/h2>\n<div style=\"aspect-ratio: 960 \/ 540;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/02\/localstorage-clientid-google-analytics.jpg\" title=\"localStorage to persist clientId\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"540\" width=\"960\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/02\/localstorage-clientid-google-analytics.jpg#ZgotmplZ\" alt=\"localStorage to persist clientId\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>It\u2019s not a novel trick. In fact, Google themselves <a href=\"https:\/\/developers.google.com\/analytics\/devguides\/collection\/analyticsjs\/cookies-user-id#using_localstorage_to_store_the_client_id\">show you how to do this<\/a> in the developer documentation. The thing is, though, that Google only shows you how to <strong>replace<\/strong> cookie storage with <code>localStorage<\/code>. I still want to leverage cookies, because they make things like cross-domain tracking far easier to do.<\/p>\n<p>The process is basically this:<\/p>\n<ol>\n<li>\n<p>When a Google Analytics tracker is created, check if Client ID is persisted in <code>localStorage<\/code>.<\/p>\n<\/li>\n<li>\n<p>If this is the case, check if its expiration is in the future.<\/p>\n<\/li>\n<li>\n<p>If this is the case, build the tracker with the Client ID from <code>localStorage<\/code> (the tracker will generate the <code>_ga<\/code> cookie with this data, too).<\/p>\n<\/li>\n<li>\n<p>If there is no Client ID in <code>localStorage<\/code> use GA\u2019s default Client ID creation and storage mechanisms.<\/p>\n<\/li>\n<li>\n<p>When the tag fires, write the Client ID in <code>localStorage<\/code>.<\/p>\n<\/li>\n<\/ol>\n<p>It should be pretty smooth sailing. Since the <code>_ga<\/code> cookie and <code>localStorage<\/code> are kept in sync, this should work across origins which is something that <code>localStorage<\/code> by default doesn\u2019t do.<\/p>\n<p><strong>NOTE!<\/strong> Since you\u2019re always checking <code>localStorage<\/code> first, this means that if the user deletes their cookies, they don\u2019t actually delete the Client ID, because that would require <code>localStorage<\/code> to be flushed, too. You might want to discuss this solution with your legal team before going forward with it.<\/p>\n<h3 id=\"how-to-set-the-client-id\">How to set the Client ID<\/h3>\n<p>You\u2019ll need <strong>two Custom JavaScript variables<\/strong>. One to <strong>set<\/strong> the Client ID, and one to <strong>get<\/strong> it.<\/p>\n<p>To <strong>set<\/strong> the Client ID, go to the <a href=\"https:\/\/www.simoahava.com\/tools\/customtask-builder\/\"><code>customTask<\/code> Builder tool<\/a>, select the option named <strong>Use localStorage To Persist ClientId<\/strong>, and then click <strong>Copy to clipboard<\/strong>.<\/p>\n<div style=\"aspect-ratio: 1884 \/ 858;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/02\/copy-customtask.jpg\" title=\"Copy customTask\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"858\" width=\"1884\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/02\/copy-customtask.jpg#ZgotmplZ\" alt=\"Copy customTask\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>In <a href=\"https:\/\/tagmanager.google.com\/\">Google Tag Manager<\/a>, create a new Custom JavaScript variable, and paste the clipboard content within. Then, do the following two maintenance operations:<\/p>\n<ol>\n<li>\n<p>Delete the following text from the start of the code block: <code>var customTask = <\/code>. Just that, nothing else.<\/p>\n<\/li>\n<li>\n<p>Delete the last character of the entire code block, which should be a semi-colon.<\/p>\n<\/li>\n<\/ol>\n<p>Then, modify the configuration object, if you wish.<\/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\">var<\/span> localStorageCid = {\n  objectName: <span style=\"color:#a50\">'ga_client_id'<\/span>,\n  expires: <span style=\"color:#099\">1000<\/span>*<span style=\"color:#099\">60<\/span>*<span style=\"color:#099\">60<\/span>*<span style=\"color:#099\">24<\/span>*<span style=\"color:#099\">365<\/span>*<span style=\"color:#099\">2<\/span>\n};\n<\/code><\/pre>\n<\/div>\n<p>If you want the name of the object written to <code>localStorage<\/code> to be something other than <code>'ga_client_id'<\/code>, change the respective string value. The <code>expires<\/code> value is a number <strong>in milliseconds<\/strong> denoting how long the object should be in storage. It\u2019s updated every time a tag fires with this <code>customTask<\/code> script. If you want the storage to expire sooner or later than two years\u2019 time, change the value of <code>expires<\/code> accordingly.<\/p>\n<blockquote>\n<p><strong>NOTE!<\/strong> There is no automatic expiration mechanism with <code>localStorage<\/code>. \u201cExpires\u201d here simply means that if the expiration of the item is in the past, it will be overwritten with the Client ID generated by GA.<\/p>\n<\/blockquote>\n<p>Finally, add this <code>customTask<\/code> to <strong>all your Google Analytics tags<\/strong>. Easiest way to do it is to use a <a href=\"https:\/\/www.simoahava.com\/analytics\/google-analytics-settings-variable-in-gtm\/\">Google Analytics Settings variable<\/a>. Add it like this:<\/p>\n<div style=\"aspect-ratio: 1780 \/ 466;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/02\/customtask-to-tag.jpg\" title=\"Customtask added to tag\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"466\" width=\"1780\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/02\/customtask-to-tag.jpg#ZgotmplZ\" alt=\"Customtask added to tag\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Remember that you can only add one <code>customTask<\/code> per tag or Google Analytics Settings variable. Use the <a href=\"https:\/\/www.simoahava.com\/tools\/customtask-builder\/\"><code>customTask<\/code> Builder tool<\/a> to compile a <code>customTask<\/code> that incorporates multiple different functions.<\/p>\n<h3 id=\"how-to-get-the-client-id\">How to get the Client ID<\/h3>\n<p>The second <strong>Custom JavaScript variable<\/strong> you\u2019ll need is something that will pull the Client ID from <code>localStorage<\/code> and use that instead of the value stored in the <code>_ga<\/code> cookie (if any).<\/p>\n<p>This is what the Custom JavaScript variable code 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:#00a\">var<\/span> objectName = <span style=\"color:#a50\">'ga_client_id'<\/span>;\n  <span style=\"color:#00a\">if<\/span> (<span style=\"color:#0aa\">window<\/span>.localStorage) {\n    <span style=\"color:#00a\">var<\/span> jsonObj = <span style=\"color:#0aa\">window<\/span>.localStorage.getItem(objectName) || <span style=\"color:#a50\">'{}'<\/span>;\n    <span style=\"color:#00a\">var<\/span> obj = JSON.parse(jsonObj);\n    <span style=\"color:#00a\">var<\/span> now = <span style=\"color:#00a\">new<\/span> <span style=\"color:#0aa\">Date<\/span>().getTime();\n    <span style=\"color:#00a\">if<\/span> (obj.clientId &amp;&amp; obj.expires) {\n      <span style=\"color:#00a\">if<\/span> (now return obj.clientId;\n      }\n    }\n  }\n  <span style=\"color:#00a\">return<\/span>;\n}\n<\/code><\/pre>\n<\/div>\n<p>Change the value of <code>objectName<\/code> to match the name of the <code>localStorage<\/code> object you set in the <code>customTask<\/code> above. By default, it\u2019s <code>'ga_client_id'<\/code>.<\/p>\n<p>This script checks if a Google Analytics Client ID is found in <code>localStorage<\/code>, and that if found, its expiration time hasn\u2019t whizzed past yet. If both of these conditions pass, the Client ID is returned from <code>localStorage<\/code>.<\/p>\n<p>If no item is found, or if object has expired, or if the browser doesn\u2019t support <code>localStorage<\/code>, the variable returns <code>undefined<\/code> which basically means that GA falls back to its default method of Client ID generation, retrieval, and storage.<\/p>\n<p>You need to add this variable to <strong>every single tag to which you added the <code>customTask<\/code> from above<\/strong>. Again, it\u2019s imperative that every single tag uses this new <code>localStorage<\/code> method consistently, or you might end up with tags firing with the wrong Client ID, thus skewing your data.<\/p>\n<p>You add this variable with the field name <code>clientId<\/code>, like this:<\/p>\n<div style=\"aspect-ratio: 1770 \/ 578;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/02\/clientid-fields-to-set.jpg\" title=\"ClientID get\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"578\" width=\"1770\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/02\/clientid-fields-to-set.jpg#ZgotmplZ\" alt=\"ClientID get\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>That\u2019s it for this simple solution. There is <strong>one big problem<\/strong> with this approach, though. It will <strong>always<\/strong> take the Client ID from <code>localStorage<\/code> if it\u2019s available and hasn\u2019t expired. This is problematic in one specific scenario: <strong>cross-domain tracking<\/strong>.<\/p>\n<h3 id=\"how-to-respect-gas-cross-domain-linker-parameter\">How to respect GA\u2019s cross-domain linker parameter<\/h3>\n<p>Turns out, this isn\u2019t totally trivial to do. Even if you set the <code>allowLinker<\/code> field to <code>true<\/code> in the tag, the Client ID from <code>localStorage<\/code> will always overwrite the respective field in the tag, no matter how valid the linker parameter was.<\/p>\n<p>So, you need to replicate how <code>allowLinker<\/code> works, checking if the URL has a valid linker parameter. If it does, then you need to bypass the <code>localStorage<\/code> fetch, so that analytics.js can build the Client ID with the linker parameter, and then write the updated ID into <code>localStorage<\/code> in the <code>customTask<\/code>.<\/p>\n<p>Unfortunately, <strong>analytics.js<\/strong> doesn\u2019t expose the <code>allowLinker<\/code> functionality as an API you could simply query to know whether the URL has a valid linker parameter or not.<\/p>\n<p>This leaves us with very few options. Your best bet is to actually <strong>reproduce<\/strong> what <code>allowLinker<\/code> does. It\u2019s not trivial, so I did the legwork for you (thanks to <a href=\"https:\/\/www.thyngster.com\/\">David Vallejo\u2019s<\/a> generous help). You can find the source code in <a href=\"https:\/\/gist.github.com\/sahava\/f3718f981bb01768c0eba714ee94e2d2\">this Gist<\/a>.<\/p>\n<p>To add it to your setup, open the Custom JavaScript variable you created in the previous chapter, and edit it to 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\">var<\/span> objectName = <span style=\"color:#a50\">'ga_client_id'<\/span>;\n  \n  <span style=\"color:#00a\">var<\/span> checkLinker=<span style=\"color:#00a\">function<\/span>(t){<span style=\"color:#00a\">var<\/span> n,e,i=<span style=\"color:#00a\">function<\/span>(t,n){<span style=\"color:#00a\">for<\/span>(<span style=\"color:#00a\">var<\/span> e=<span style=\"color:#00a\">new<\/span> <span style=\"color:#0aa\">Date<\/span>,i=<span style=\"color:#0aa\">window<\/span>.navigator,r=i.plugins||[],a=[t,i.userAgent,e.getTimezoneOffset(),e.getYear(),e.getDate(),e.getHours(),e.getMinutes()+n],s=<span style=\"color:#099\">0<\/span>;s<r.length style=\"color:#00a\">return o(a.join(<span style=\"color:#a50\">\".\"<\/span>))},r=<span style=\"color:#00a\">function<\/span>(t,n){<span style=\"color:#00a\">var<\/span> e=<span style=\"color:#00a\">new<\/span> <span style=\"color:#0aa\">Date<\/span>,i=<span style=\"color:#0aa\">window<\/span>.navigator,r=e.getHours()+<span style=\"color:#0aa\">Math<\/span>.floor((e.getMinutes()+n)\/<span style=\"color:#099\">60<\/span>);<span style=\"color:#00a\">return<\/span> o([t,i.userAgent,i.language||<span style=\"color:#a50\">\"\"<\/span>,e.getTimezoneOffset(),e.getYear(),e.getDate()+<span style=\"color:#0aa\">Math<\/span>.floor(r\/<span style=\"color:#099\">24<\/span>),(<span style=\"color:#099\">24<\/span>+r)%<span style=\"color:#099\">24<\/span>,(<span style=\"color:#099\">60<\/span>+e.getMinutes()+n)%<span style=\"color:#099\">60<\/span>].join(<span style=\"color:#a50\">\".\"<\/span>))},o=<span style=\"color:#00a\">function<\/span>(t){<span style=\"color:#00a\">var<\/span> n,e=<span style=\"color:#099\">1<\/span>;<span style=\"color:#00a\">if<\/span>(t)<span style=\"color:#00a\">for<\/span>(e=<span style=\"color:#099\">0<\/span>,n=t.length-<span style=\"color:#099\">1<\/span>;<span style=\"color:#099\">0<\/span>var i=t.charCodeAt(n);e=<span style=\"color:#099\">0<\/span>!=(i=<span style=\"color:#099\">266338304<\/span>&amp;(e=(e6&amp;<span style=\"color:#099\">268435455<\/span>)+i+(i14)))?e^i&gt;&gt;<span style=\"color:#099\">21<\/span>:e}<span style=\"color:#00a\">return<\/span> e.toString()};<span style=\"color:#00a\">if<\/span>(<span style=\"color:#a50\">\"string\"<\/span>==<span style=\"color:#00a\">typeof<\/span> t&amp;&amp;t.length){<span style=\"color:#00a\">if<\/span>(!<span style=\"color:#099\">\/_ga=\/<\/span>.test(t))<span style=\"color:#00a\">return<\/span><span style=\"color:#a50\">\"Invalid linker format in string argument!\"<\/span>;e=t.split(<span style=\"color:#a50\">\"&amp;\"<\/span>).filter(<span style=\"color:#00a\">function<\/span>(t){<span style=\"color:#00a\">return<\/span><span style=\"color:#a50\">\"_ga\"<\/span>===t.split(<span style=\"color:#a50\">\"=\"<\/span>)[<span style=\"color:#099\">0<\/span>]}).shift()}<span style=\"color:#00a\">else<\/span> e=(n=<span style=\"color:#099\">\/[?&amp;]_ga=\/<\/span>.test(<span style=\"color:#0aa\">window<\/span>.location.search)?<span style=\"color:#a50\">\"search\"<\/span>:<span style=\"color:#099\">\/[#&amp;]_ga=\/<\/span>.test(<span style=\"color:#0aa\">window<\/span>.location.hash)?<span style=\"color:#a50\">\"hash\"<\/span>:<span style=\"color:#00a\">void<\/span> <span style=\"color:#099\">0<\/span>)&amp;&amp;<span style=\"color:#0aa\">window<\/span>.location[n].substring(<span style=\"color:#099\">1<\/span>).split(<span style=\"color:#a50\">\"&amp;\"<\/span>).filter(<span style=\"color:#00a\">function<\/span>(t){<span style=\"color:#00a\">return<\/span><span style=\"color:#a50\">\"_ga\"<\/span>===t.split(<span style=\"color:#a50\">\"=\"<\/span>)[<span style=\"color:#099\">0<\/span>]}).shift();<span style=\"color:#00a\">if<\/span>(<span style=\"color:#00a\">void<\/span> <span style=\"color:#099\">0<\/span>===e||!e.length)<span style=\"color:#00a\">return<\/span><span style=\"color:#a50\">\"Invalid linker format in URL!\"<\/span>;<span style=\"color:#00a\">var<\/span> a,s,g,u,f=e.indexOf(<span style=\"color:#a50\">\".\"<\/span>);<span style=\"color:#00a\">return<\/span> f&gt;-<span style=\"color:#099\">1<\/span>&amp;&amp;(e.substring(<span style=\"color:#099\">0<\/span>,f),s=(a=e.substring(f+<span style=\"color:#099\">1<\/span>)).indexOf(<span style=\"color:#a50\">\".\"<\/span>),g=a.substring(<span style=\"color:#099\">0<\/span>,s),u=a.substring(s+<span style=\"color:#099\">1<\/span>)),<span style=\"color:#00a\">void<\/span> <span style=\"color:#099\">0<\/span>!==u?g===i(u=u.split(<span style=\"color:#a50\">\"-\"<\/span>).join(<span style=\"color:#a50\">\"\"<\/span>),<span style=\"color:#099\">0<\/span>)||g===i(u,-<span style=\"color:#099\">1<\/span>)||g===i(u,-<span style=\"color:#099\">2<\/span>)||g===r(u,<span style=\"color:#099\">0<\/span>)||g===r(u,-<span style=\"color:#099\">1<\/span>)||g===r(u,-<span style=\"color:#099\">2<\/span>):<span style=\"color:#00a\">void<\/span> <span style=\"color:#099\">0<\/span>};\n  \n  <span style=\"color:#00a\">if<\/span> (checkLinker()) {\n    <span style=\"color:#00a\">return<\/span>;\n  }\n  \n  <span style=\"color:#00a\">if<\/span> (<span style=\"color:#0aa\">window<\/span>.localStorage) {\n    <span style=\"color:#00a\">var<\/span> jsonObj = <span style=\"color:#0aa\">window<\/span>.localStorage.getItem(objectName) || <span style=\"color:#a50\">'{}'<\/span>;\n    <span style=\"color:#00a\">var<\/span> obj = JSON.parse(jsonObj);\n    <span style=\"color:#00a\">var<\/span> now = <span style=\"color:#00a\">new<\/span> <span style=\"color:#0aa\">Date<\/span>().getTime();\n    <span style=\"color:#00a\">if<\/span> (obj.clientId &amp;&amp; obj.expires) {\n      <span style=\"color:#00a\">if<\/span> (now return obj.clientId;\n      }\n    }\n  }\n  <span style=\"color:#00a\">return<\/span>;\n}\n<\/r.length><\/code><\/pre>\n<\/div>\n<p>That big block of code starting with <code>var checkLinker=<\/code> contains the code in the <a href=\"https:\/\/gist.github.com\/sahava\/f3718f981bb01768c0eba714ee94e2d2\">GitHubGist<\/a> just minified to reduce clutter.<\/p>\n<p>The <code>checkLinker()<\/code> method parses the URL for a valid linker parameter. If one is found, then the variable ignores the Client ID stored in <code>localStorage<\/code> and allows the GA tag to build the Client ID from the linker parameter instead.<\/p>\n<p>Read on for one major caveat in this approach.<\/p>\n<h3 id=\"caveats\">Caveats<\/h3>\n<p>There are, naturally, some caveats to this workaround.<\/p>\n<ul>\n<li>\n<p>There\u2019s no mechanism involved to handle multiple <code>_ga<\/code> cookies. So if you have a set of tags that need to have their Client ID handled separately (e.g. due to <a href=\"https:\/\/www.simoahava.com\/gtm-tips\/cross-domain-tracking-with-multiple-ga-trackers\/\">roll-up tracking<\/a>, simply use a different <code>localStorage<\/code> object name for that set of tags.<\/p>\n<\/li>\n<li>\n<p>Unlike typically with <code>localStorage<\/code>, you don\u2019t have to worry about cross-origin tracking, since the <code>_ga<\/code> cookie would persist across subdomains (assuming it has the <code>cookieDomain<\/code> field set to <code>auto<\/code>), and thus when the user lands on a subdomain without the <code>localStorage<\/code> object, the <code>_ga<\/code> cookie is used instead, and this is then written into <code>localStorage<\/code> in the <code>customTask<\/code>.<\/p>\n<\/li>\n<li>\n<p>Tracking across subdomains is nevertheless a problem, because the <code>_ga<\/code> cookie only survives 7 days without returning visits. Thus if the user visits one subdomain on day 1 and another subdomain on day 8, the <code>localStorage<\/code> solution described here will be of little help.<\/p>\n<\/li>\n<li>\n<p>Cross-domain tracking, even with the linker trick mentioned in the previous chapter, is still a pain. Google is experimenting with different linker parameters, so the solution above might become outdated soon. I\u2019ll update the code when the linker parameter format changes.<\/p>\n<\/li>\n<li>\n<p>There are potential <strong>legal implications<\/strong> of disregarding cookie purges as described in this article (thanks to <a href=\"https:\/\/brianclifton.com\/\">Brian Clifton<\/a>) for pointing this out in the comments. I seriously recommend to only treat this solution as a technical demo of what <em>could<\/em> be done, not what <em>should<\/em> be done.<\/p>\n<\/li>\n<li>\n<p>Safari is already blocking <code>localStorage.setItem()<\/code> in private browsing mode, and other browsers might follow suit.<\/p>\n<\/li>\n<\/ul>\n<p>And then, of course, the biggest caveat:<\/p>\n<p><strong>This isn\u2019t the last we\u2019ve seen of the concentrated attack against browser cookie storage<\/strong>. Other browsers will likely follow suit, after they let Safari take the blow for being the first one audacious enough to delimit first-party cookies in this way.<\/p>\n<h3 id=\"final-thoughts\">Final thoughts<\/h3>\n<p>I want you to consider this a <strong>tech demo<\/strong> first and foremost. It\u2019s not robust enough to carry your entire enterprise web analytics setup, but it should be usable in situations where you want to minimize data loss especially with Safari users.<\/p>\n<p>Please do note that you are potentially making it more difficult for your visitors to clear their tracking identifiers. Make it obvious in your privacy statement that this type of persistence is happening, even if it\u2019s not necessary per the regulations or laws of your region. It\u2019s just good behavior. Users should be allowed to purge their tracking identifiers without having to be rocket scientists to do so.<\/p>\n<p>I\u2019m also expecting this solution to be temporary. It\u2019s still unclear how Intelligent Tracking Prevention evolves. One of the purposes of the 7-day cap to client-side cookies is to prevent third party JavaScript libraries from writing cookies on one domain and accessing them on another subdomain. If Apple considers this <code>localStorage<\/code> trick to be a hack around this limitation, it\u2019s possible they\u2019ll invest in measures to prevent this from working, too.<\/p>\n<p>Also, I\u2019m expecting big vendors who suffer most from this (such as Google and Facebook) to introduce their own solutions that help avoid the potential compromisation of data quality that\u2019s at stake here. As such, I do hope that this article becomes outdated soon, when the platforms suggest an officially supported way of persisting data reliably.<\/p>\n<p>Finally, many might be now even more tempted to move towards server-side tracking due to the increased fragility of client-side persistence. <strong>You don\u2019t need a full server-side proxy to cope with ITP 2.1<\/strong>. Since it only targets cookies written with <code>document.cookie<\/code>, you could simply generate anonymous identifiers like GA\u2019s Client ID server-side, and set them in the <code>Set-Cookies<\/code> header of the HTTP response. Be sure to check out my more extensive article on <a href=\"https:\/\/www.simoahava.com\/analytics\/itp-2-1-and-web-analytics\/\">ITP 2.1 and web analytics<\/a> for more details.<\/p>\n<p>What do you think of this latest ITP 2.1 release? Feel free to join the discussion in the comments!<\/p>\n<\/p><\/div>\n\n","protected":false},"excerpt":{"rendered":"<p>Updated 1 October 2019 With ITP 2.3 it looks like Safari is reducing the usefulness of localStorage as well, so this solution should not be considered future-proof. The only stable way to persist client-side data at the moment seems to be HTTP cookies. Updated 7 March 2019 &#8211; Added some extra caveats to this solution. [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":100663,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[12033],"tags":[12765,12982,1531,14492,46839,13499],"dealstore":[],"offerexpiration":[],"class_list":["post-100662","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-analytics","tag-analytics","tag-client","tag-google","tag-gtmtips","tag-localstorage","tag-persistence"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v26.4 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>#GTMTips: Use localStorage For Client ID Persistence In Google Analytics - 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=100662\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"#GTMTips: Use localStorage For Client ID Persistence In Google Analytics - Som2ny Network\" \/>\n<meta property=\"og:description\" content=\"Updated 1 October 2019 With ITP 2.3 it looks like Safari is reducing the usefulness of localStorage as well, so this solution should not be considered future-proof. The only stable way to persist client-side data at the moment seems to be HTTP cookies. Updated 7 March 2019 &#8211; Added some extra caveats to this solution. [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/fivemor.com\/?p=100662\" \/>\n<meta property=\"og:site_name\" content=\"Som2ny Network\" \/>\n<meta property=\"article:published_time\" content=\"2025-02-21T02:54:42+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/localstorage-clientid-google-analytics.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"960\" \/>\n\t<meta property=\"og:image:height\" content=\"540\" \/>\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=\"12 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/fivemor.com\/?p=100662#article\",\"isPartOf\":{\"@id\":\"https:\/\/fivemor.com\/?p=100662\"},\"author\":{\"name\":\"admin\",\"@id\":\"https:\/\/fivemor.com\/#\/schema\/person\/b85e3c3dc0e1daea076524dc8810c371\"},\"headline\":\"#GTMTips: Use localStorage For Client ID Persistence In Google Analytics\",\"datePublished\":\"2025-02-21T02:54:42+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/fivemor.com\/?p=100662\"},\"wordCount\":1968,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/fivemor.com\/#organization\"},\"image\":{\"@id\":\"https:\/\/fivemor.com\/?p=100662#primaryimage\"},\"thumbnailUrl\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/localstorage-clientid-google-analytics.jpg\",\"keywords\":[\"Analytics\",\"client\",\"Google\",\"GTMTips\",\"localStorage\",\"persistence\"],\"articleSection\":[\"Analytics\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/fivemor.com\/?p=100662#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/fivemor.com\/?p=100662\",\"url\":\"https:\/\/fivemor.com\/?p=100662\",\"name\":\"#GTMTips: Use localStorage For Client ID Persistence In Google Analytics - Som2ny Network\",\"isPartOf\":{\"@id\":\"https:\/\/fivemor.com\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/fivemor.com\/?p=100662#primaryimage\"},\"image\":{\"@id\":\"https:\/\/fivemor.com\/?p=100662#primaryimage\"},\"thumbnailUrl\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/localstorage-clientid-google-analytics.jpg\",\"datePublished\":\"2025-02-21T02:54:42+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/fivemor.com\/?p=100662#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/fivemor.com\/?p=100662\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/fivemor.com\/?p=100662#primaryimage\",\"url\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/localstorage-clientid-google-analytics.jpg\",\"contentUrl\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/localstorage-clientid-google-analytics.jpg\",\"width\":960,\"height\":540},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/fivemor.com\/?p=100662#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/fivemor.com\/?bp_activities=1\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"#GTMTips: Use localStorage For Client ID Persistence In Google Analytics\"}]},{\"@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":"#GTMTips: Use localStorage For Client ID Persistence In Google Analytics - 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=100662","og_locale":"en_US","og_type":"article","og_title":"#GTMTips: Use localStorage For Client ID Persistence In Google Analytics - Som2ny Network","og_description":"Updated 1 October 2019 With ITP 2.3 it looks like Safari is reducing the usefulness of localStorage as well, so this solution should not be considered future-proof. The only stable way to persist client-side data at the moment seems to be HTTP cookies. Updated 7 March 2019 &#8211; Added some extra caveats to this solution. [&hellip;]","og_url":"https:\/\/fivemor.com\/?p=100662","og_site_name":"Som2ny Network","article_published_time":"2025-02-21T02:54:42+00:00","og_image":[{"width":960,"height":540,"url":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/localstorage-clientid-google-analytics.jpg","type":"image\/jpeg"}],"author":"admin","twitter_card":"summary_large_image","twitter_misc":{"Written by":"admin","Est. reading time":"12 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/fivemor.com\/?p=100662#article","isPartOf":{"@id":"https:\/\/fivemor.com\/?p=100662"},"author":{"name":"admin","@id":"https:\/\/fivemor.com\/#\/schema\/person\/b85e3c3dc0e1daea076524dc8810c371"},"headline":"#GTMTips: Use localStorage For Client ID Persistence In Google Analytics","datePublished":"2025-02-21T02:54:42+00:00","mainEntityOfPage":{"@id":"https:\/\/fivemor.com\/?p=100662"},"wordCount":1968,"commentCount":0,"publisher":{"@id":"https:\/\/fivemor.com\/#organization"},"image":{"@id":"https:\/\/fivemor.com\/?p=100662#primaryimage"},"thumbnailUrl":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/localstorage-clientid-google-analytics.jpg","keywords":["Analytics","client","Google","GTMTips","localStorage","persistence"],"articleSection":["Analytics"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/fivemor.com\/?p=100662#respond"]}]},{"@type":"WebPage","@id":"https:\/\/fivemor.com\/?p=100662","url":"https:\/\/fivemor.com\/?p=100662","name":"#GTMTips: Use localStorage For Client ID Persistence In Google Analytics - Som2ny Network","isPartOf":{"@id":"https:\/\/fivemor.com\/#website"},"primaryImageOfPage":{"@id":"https:\/\/fivemor.com\/?p=100662#primaryimage"},"image":{"@id":"https:\/\/fivemor.com\/?p=100662#primaryimage"},"thumbnailUrl":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/localstorage-clientid-google-analytics.jpg","datePublished":"2025-02-21T02:54:42+00:00","breadcrumb":{"@id":"https:\/\/fivemor.com\/?p=100662#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/fivemor.com\/?p=100662"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/fivemor.com\/?p=100662#primaryimage","url":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/localstorage-clientid-google-analytics.jpg","contentUrl":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/localstorage-clientid-google-analytics.jpg","width":960,"height":540},{"@type":"BreadcrumbList","@id":"https:\/\/fivemor.com\/?p=100662#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/fivemor.com\/?bp_activities=1"},{"@type":"ListItem","position":2,"name":"#GTMTips: Use localStorage For Client ID Persistence In Google Analytics"}]},{"@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\/100662","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=100662"}],"version-history":[{"count":0,"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/posts\/100662\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/media\/100663"}],"wp:attachment":[{"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=100662"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=100662"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=100662"},{"taxonomy":"dealstore","embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fdealstore&post=100662"},{"taxonomy":"offerexpiration","embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fofferexpiration&post=100662"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}