GTM sessionStorage Read and Write

>

sessionStorage in GTM via Sandboxed JavaScript — Read & Write With Just One CSP Hash

Published · Tags: GTM, Sandboxed JavaScript, sessionStorage, Web Storage API, Custom Templates, CSP, callInWindow, access_globals, Custom HTML

Note:

This GTM container recipe works on Google Blogger / Blogspot as well as AEM, Sitecore, WordPress, React.js, and any other platform running a standard GTM snippet. It is a small, self-contained pattern — one Custom HTML tag plus two Sandboxed JavaScript Custom Templates — built specifically to keep sessionStorage access out of Content Security Policy's way. The JSON can be downloaded from Google Drive below.

A number of published community GTM templates manipulate localStorage directly, since it's a natural fit for a "utility" template. sessionStorage is the same API shape — getItem, setItem, removeItem, clear, key, length — but it isn't something a Sandboxed JavaScript template can reach on its own. Templates run in a locked-down sandbox with a fixed set of permitted built-in functions; there's no require('sessionStorage'). To read or write it at all, a template has to reach into the page's real window object.

The usual way to reach window from GTM is a Custom HTML tag with an inline <script> block. That works, but on any site enforcing a strict Content Security Policy with SHA-256 source hashes, every inline script needs its own hash — and that hash has to be recomputed and redeployed every single time the script's text changes by so much as a byte. This recipe avoids that entirely for the read/write logic: only one small script, the listener, ever needs a CSP hash. Everything else — every read, every write, every future tweak to the read/write logic — runs as GTM Sandboxed JavaScript and needs no hash at all.

⬇ Download the GTM Container JSON from Google Drive

What's Inside the Container

The exported container (gtm-session-storage-read-write.json) contains one tag folder — Process sessionStorage — holding one Custom HTML tag, one Custom Template tag, three variables, one trigger, and two Custom Templates (a variable-type template and a tag-type template).

Tags
TagTypeFires OnPurpose
sessionStorage Listener Custom HTML Early page-load trigger, priority 99, Once Per Event Defines window.gtmSessionStorage — a small set of window-scoped helper functions wrapping the native sessionStorage API. This is the only tag in the recipe with an inline <script> element.
sessionStorage Write Custom Template (sessionStorage Write) sessionStorage Write trigger (Initialization) Calls window.gtmSessionStorage.setItem via callInWindow — no inline script, so no CSP hash required for this tag.
Variables
VariableTypeConfiguration
sessionStorage ReadCustom Template (sessionStorage Read)Action: getItem, Key: {{sessionStorage Name}}
sessionStorage NameConstantValue: sessionTest
sessionStorage ValueConstantValue: something-written-2-sessionStorage
Trigger
TriggerTypeCondition
sessionStorage Write Initialization {{sessionStorage Read}} matches RegEx ^(undefined|null|0|false|NaN|)$ — i.e. fire the write only when the key doesn't already hold a value, so an existing session value is never clobbered on every page.

The sessionStorage Listener: How It Works

This is the one tag in the recipe that injects real inline JavaScript. It defines a single window-scoped object, guards against re-initialization if the tag fires more than once, and wraps every native call in a try/catch so a browser that blocks storage (private browsing, storage-partitioning edge cases) fails safely instead of throwing.

<script>
(function() {
  if (window.gtmSessionStorage) return; // avoid re-init on multiple tag fires

  window.gtmSessionStorage = {
    setItem: function(key, value) {
      try {
        sessionStorage.setItem(key, value);
        return true;
      } catch (e) {
        console.error('gtmSessionStorage.setItem failed:', e);
        return false;
      }
    },
    getItem: function(key) {
      try {
        return sessionStorage.getItem(key);
      } catch (e) {
        console.error('gtmSessionStorage.getItem failed:', e);
        return null;
      }
    },
    removeItem: function(key) {
      try {
        sessionStorage.removeItem(key);
        return true;
      } catch (e) {
        console.error('gtmSessionStorage.removeItem failed:', e);
        return false;
      }
    },
    clear: function() {
      try {
        sessionStorage.clear();
        return true;
      } catch (e) {
        console.error('gtmSessionStorage.clear failed:', e);
        return false;
      }
    },
    key: function(index) {
      try {
        return sessionStorage.key(index);
      } catch (e) {
        console.error('gtmSessionStorage.key failed:', e);
        return null;
      }
    },
    length: function() {
      // exposed as a function, not a property — callInWindow calls functions
      try {
        return sessionStorage.length;
      } catch (e) {
        console.error('gtmSessionStorage.length failed:', e);
        return 0;
      }
    }
  };
})();
</script>

This is the only script that needs a CSP hash. If your site enforces script-src with SHA-256 sources instead of a nonce, this is the single inline block you compute a hash for and add to your CSP header. Fire this tag once, early (priority 99, an early page-load trigger), and every Read/Write template call after it talks to window.gtmSessionStorage through GTM's own sandbox — no further hashes, ever.

The sessionStorage Read Template (Variable)

sessionStorage Read is a MACRO (variable) type template. It exposes three read-only operations against the helper object, gated behind queryPermission checks so the template fails closed — returning undefined and logging to the console — if the container's permission grant for that specific function is missing.

sessionStorage Read — Actions
ActionParameterCallsReturns
Get ItemKey (text)window.gtmSessionStorage.getItem(key)Stored string, or null
Get Key By IndexIndex (non-negative number)window.gtmSessionStorage.key(index)Key name at that index, or null
Get Lengthwindow.gtmSessionStorage.length()Number of stored items
const callInWindow = require('callInWindow');
const queryPermission = require('queryPermission');
const makeString = require('makeString');
const makeNumber = require('makeNumber');
const logToConsole = require('logToConsole');

const action = data.action;

if (action === 'getItem') {
  if (queryPermission('access_globals', 'execute', 'gtmSessionStorage.getItem')) {
    return callInWindow('gtmSessionStorage.getItem', makeString(data.key));
  }
  logToConsole('Session Storage: Read - missing execute permission for gtmSessionStorage.getItem');
  return undefined;
}

if (action === 'key') {
  if (queryPermission('access_globals', 'execute', 'gtmSessionStorage.key')) {
    return callInWindow('gtmSessionStorage.key', makeNumber(data.index));
  }
  logToConsole('Session Storage: Read - missing execute permission for gtmSessionStorage.key');
  return undefined;
}

if (action === 'length') {
  if (queryPermission('access_globals', 'execute', 'gtmSessionStorage.length')) {
    return callInWindow('gtmSessionStorage.length');
  }
  logToConsole('Session Storage: Read - missing execute permission for gtmSessionStorage.length');
  return undefined;
}

logToConsole('Session Storage: Read - unknown action: ' + action);
return undefined;

The sessionStorage Write Template (Tag)

sessionStorage Write is a TAG type template, so it calls data.gtmOnSuccess() / data.gtmOnFailure() instead of returning a value. It covers the three mutating operations.

sessionStorage Write — Actions
ActionParametersCalls
Set ItemKey (text, required), Value (text)window.gtmSessionStorage.setItem(key, value)
Remove ItemKey (text, required)window.gtmSessionStorage.removeItem(key)
Clear Allwindow.gtmSessionStorage.clear()
const callInWindow = require('callInWindow');
const queryPermission = require('queryPermission');
const logToConsole = require('logToConsole');
const makeString = require('makeString');

const action = data.action;

if (action === 'setItem') {
  if (queryPermission('access_globals', 'execute', 'gtmSessionStorage.setItem')) {
    callInWindow('gtmSessionStorage.setItem', makeString(data.key), makeString(data.value));
    data.gtmOnSuccess();
  } else {
    logToConsole('Session Storage: Write - missing execute permission for gtmSessionStorage.setItem');
    data.gtmOnFailure();
  }
} else if (action === 'removeItem') {
  if (queryPermission('access_globals', 'execute', 'gtmSessionStorage.removeItem')) {
    callInWindow('gtmSessionStorage.removeItem', makeString(data.key));
    data.gtmOnSuccess();
  } else {
    logToConsole('Session Storage: Write - missing execute permission for gtmSessionStorage.removeItem');
    data.gtmOnFailure();
  }
} else if (action === 'clear') {
  if (queryPermission('access_globals', 'execute', 'gtmSessionStorage.clear')) {
    callInWindow('gtmSessionStorage.clear');
    data.gtmOnSuccess();
  } else {
    logToConsole('Session Storage: Write - missing execute permission for gtmSessionStorage.clear');
    data.gtmOnFailure();
  }
} else {
  logToConsole('Session Storage: Write - unknown action: ' + action);
  data.gtmOnFailure();
}

Why access_globals Instead of a Storage-Specific Permission

Both templates request the access_globals permission, scoped to exactly six named window paths: gtmSessionStorage.getItem, .setItem, .removeItem, .clear, .key, and .length — each with read, write, and execute granted individually. This is the standard pattern for reaching a window-scoped helper from a Sandboxed JavaScript template: the template never touches sessionStorage directly, it calls a named function on window that the Listener tag already defined, and GTM's permission system whitelists that exact function name at template-approval time. Anyone reviewing the container in GTM's UI can see precisely which six window paths the templates are allowed to touch — nothing broader.

Demo Wiring: sessionTest

The container ships with a working example wired end-to-end so you can see the pattern fire in Preview mode immediately after import:

  • The sessionStorage Read variable checks for a key named sessionTest (from the sessionStorage Name constant).
  • The sessionStorage Write trigger fires only when that variable currently resolves to an empty/falsy value — meaning the key hasn't been set yet in this session.
  • When the trigger fires, the sessionStorage Write tag sets sessionTest to something-written-2-sessionStorage (the sessionStorage Value constant).
  • On any later page in the same tab/session, sessionStorage Read now resolves to that string, the trigger's condition is no longer met, and the write tag does not fire again — the value persists untouched until the browser tab's session ends.

Swap the two constant variables for real values, and add more actions/keys to the two templates as needed — the read/write logic scales to any number of sessionStorage keys without adding a single additional line of inline script or a single additional CSP hash.


How to Import

  1. Download gtm-session-storage-read-write.json from the Google Drive link above.
  2. In GTM, go to Admin → Import Container.
  3. Choose the downloaded JSON file, select the workspace to import into, and pick Merge (not Overwrite) if you already have other tags in the container.
  4. When prompted for conflicting Custom Templates, choose Rename if you want to keep both, or overwrite if this is a clean container.
  5. Confirm the import. You should now see the Process sessionStorage folder containing the sessionStorage Listener tag, the sessionStorage Write tag, the sessionStorage Write trigger, and the three variables.
  6. Check Templates in the left nav — you should see two new Custom Templates: sessionStorage Read (Variable) and sessionStorage Write (Tag).
  7. If your site enforces CSP with SHA-256 sources, compute the hash for the sessionStorage Listener tag's script block and add it to your script-src directive. No other tag in this recipe requires a CSP change.
  8. Open GTM Preview mode and load a page. Confirm the sessionStorage Listener tag fires first, then the sessionStorage Write tag fires on the first page of a new session.
  9. In the browser DevTools console, run sessionStorage.getItem('sessionTest') and confirm it returns something-written-2-sessionStorage.
  10. Reload the page — the write trigger should not fire again, since sessionStorage Read now resolves to a truthy value.
  11. Publish the container version once everything checks out in Preview.

The full source — container JSON and documentation — is also published on GitHub for anyone who wants to fork the read/write action list, add new sessionStorage keys, or adapt the same callInWindow pattern to a different window-scoped helper entirely.

⬇ Download GTM Container JSON

Comments

Popular posts from this blog

Site Landing & Site Referrer Preservation

GTM Browser Viewport Measurement

UTM & URL Query String 2 Cookies