Skip to content

GTM Variables

GTM Variables

CoreCategory24 Variables

Other categories transform values. This one accesses the GTM runtime itself — dataLayer history that GTM doesn’t natively persist, consent state for tag-firing logic, GA4 measurement IDs for cross-tag consistency, browser storage, DOM, Preview-mode detection. GTM is the runtime-environment access layer — 24 sandbox-safe variable templates that bridge pure value transformation and the actual GTM runtime, including a persistent dataLayer that solves a gap the community has been hand-rolling localStorage workarounds for over a decade.

24 variablesSandbox-safeWeb-contextMIT licensed

GTM has runtime features that aren’t exposed cleanly through the variable interface. The dataLayer doesn’t persist between page loads. Consent state is checked at tag-firing time but isn’t easy to read from a variable. Preview mode detection requires reading internal state. The list of GA4 ecommerce events isn’t a single named value anywhere.

Each gap has the same pattern: someone writes a Custom JavaScript variable, it works, nobody else in the team knows it exists, the next analyst writes a slightly different one. The GTM category packages these access patterns as named templates. Six concerns:

  • Cross-page persistence. The dataLayer resets every page load. Values that should survive — user identifiers, attribution touchpoints, A/B test assignments — need persistent storage. The community has been hand-rolling localStorage solutions since at least 2014.
  • Consent state checks. Tag-firing logic that conditions on whether ad_storage, analytics_storage, etc. have been granted. Eight predicate variables, one per consent type.
  • DataLayer access patterns. Reading the latest value of a key, reading values associated with a specific event, accessing the full current state.
  • Container introspection. Preview mode, Debug mode, GA4 measurement IDs — features baked into the GTM runtime that don’t have native variable types.
  • Browser storage. Cookies, localStorage — read access for tag and trigger configuration.
  • DOM access. Page title, element attributes, parent element traversal, reading-time estimation for engagement signals.

This category is structurally different from the others. Items, String, Number, Date, URL, Logic, Value, and Array all transform values you provide as inputs — same input, same output. The GTM category accesses the runtime, which means the “input” is the current state of the container, the browser, or the page.

  • Timing matters. A consent state read at Initialization may differ from the same read after the user clicks a consent banner.
  • Side effects exist. Some variables — getRootDomain!, the COMMAND TABLE — have side effects. The naming convention (! suffix) signals which.
  • Environment dependencies are explicit. Browser-context variables (document.title, findClosestElement) only work in web containers.

Twenty-six variables, with two hero groups: dataLayer persistence and consent state.

The single longest-standing gap in GTM. The dataLayer object is recreated empty on every page load. Values that should logically persist across the user’s journey — a user ID captured at signup, a campaign source from the landing page, an A/B test variant assignment — vanish on navigation unless the backend re-pushes them on every page.

Source — Simo Ahava on persistent dataLayer (2018)

“Google Tag Manager still doesn’t offer us a native way to persist the dataLayer array or its internal data model from one page to the other, so I thought it was about time I revisit this idea.”

getPersistedDataLayerValue packages the persistence pattern as a named template. The variable writes the value to both localStorage (for cross-page durability) and templateStorage (for in-memory access during the current page).

Recipe — Persist user_id across the user’s journey
Variable: getPersistedDataLayerValue · Direct or Apply mode
FieldValue
DataLayer keyuser_id
Storage scopelocalStorage + templateStorage
Reset triggerLogout event or explicit reset tag
Returns the stored user_id on every subsequent page, even before the new page’s dataLayer push has fired

Use cases: user properties on second pageview, custom attribution, multi-step form completion, A/B test variant consistency.

The reset semantic matters. Unlike a session-scoped value, the persisted value remains until a Reset tag explicitly clears it. Use getGA4SessionDataLayerValue when the value should reset with the GA4 session boundary.

getGA4SessionDataLayerValue uses GA4’s session_id as the matching key. The value persists for the duration of the session and resets when the session does.

Recipe — Track first-touch source within the current session
Variable: getGA4SessionDataLayerValue · Direct or Apply mode
FieldValue
DataLayer keyfirst_touch_source
Set onFirst pageview of the session (write-once-per-session)
Read onEvery subsequent event in the same session
Same value across the session; resets when GA4 starts a new session

Read dataLayer values by event or current state

Section titled “Read dataLayer values by event or current state”
  • getDataLayerValue — read a single key by name. Supports dot-notation.
  • getDataLayerByEvent — read the dataLayer state at the moment a specific event fired.
  • getDataLayerCurrent — return the full current dataLayer state as an object.
Recipe — Read the ecommerce items array from a specific event
Variable: getDataLayerByEvent · Direct or Apply mode
FieldValue
Event nameadd_to_cart
DataLayer pathecommerce.items
The items array as it existed at add_to_cart firing time, even after later events

Pre-built GA4 event lists for ecommerce and lead gen

Section titled “Pre-built GA4 event lists for ecommerce and lead gen”

gaEcommerceEventList and gaLeadGenerationEventList return the canonical lists as arrays. Use with includesAny from the Array category to fire a tag on any matching event.

Recipe — Fire on any GA4 ecommerce event
FieldValue
Array input{{gaEcommerceEventList}}
Lookup values[{{Event}}] (the current event name wrapped in array)
true when the firing event matches any GA4 ecommerce event name

Modern containers fire most tags conditionally on consent. The isGranted* family provides eight predicates, one per consent type, each returning a boolean.

Source — Simo Ahava on consent mode

”Tags with built-in consent checks include logic that changes the tag’s execution behavior based on the user’s consent state.”

Recipe — Fire CRM-sync tag only with both analytics and ad consent
Variable: 🧩 AND with consent predicates
FieldValue
Input 1{{isGrantedAnalyticsStorage}} equals true
Input 2{{isGrantedAdStorage}} equals true
Trigger conditionAND result equals true
Tag fires only when both consent types are granted

Detect Preview and Debug mode for safe testing

Section titled “Detect Preview and Debug mode for safe testing”

Tag testing in GTM Preview mode can have real-world consequences. Order confirmation tags fire on test purchases. Conversion uploads to ad platforms inflate test data.

Recipe — Suppress real conversion firing during Preview
Variable: gtmIsPreviewMode · Direct mode · Use as trigger exception
FieldValue
TagGoogle Ads conversion
TriggerStandard conversion trigger
Trigger exception{{gtmIsPreviewMode}} equals true
Conversion fires in production, blocked in Preview
  • getCookieValue — read a cookie by name. Consistent naming and Apply-mode chainability.
  • getLocalStorage — read a localStorage value by key. GTM has no native equivalent.
  • hasLocalStorageKey — boolean test for key presence without reading the value.

For values stored as JSON strings, chain parseJSON from String to get a usable object:

getLocalStorageApplyparseJSONDirect

Get GA4 measurement IDs for cross-tag consistency

Section titled “Get GA4 measurement IDs for cross-tag consistency”

getGaMeasurementIds returns the list of GA4 measurement IDs configured in the container. Useful for multi-property forwarding, debug output, and server-side GTM wiring.

Build lazy lookup tables for side-effecting operations

Section titled “Build lazy lookup tables for side-effecting operations”

Regular RegEx Tables evaluate every row’s output upfront. The COMMAND TABLE only evaluates the matching row’s output. Essential when outputs have side effects or are expensive.

Recipe — Lookup with lazy-evaluated side-effecting fallback
Variable: ⚡ COMMAND TABLE › Lazy (Advanced) · Direct mode
Match conditionOutput (only evaluated on match)
{{Page Hostname}} equals example.com”.example.com” (static fallback)
{{Page Hostname}} equals staging.example.com”.staging.example.com” (static)
Default (no match){{getRootDomain!}} — only runs when needed
Static rows match cheaply on production; getRootDomain! runs only on unknown hostnames

DOM access — title, attributes, reading time

Section titled “DOM access — title, attributes, reading time”
  • document.title — the current page title at evaluation time.
  • findClosestElement — walk up from a click target to find a parent matching a CSS selector.
  • getElementAttribute — read an attribute from a DOM element. data-* attributes, href, custom attributes.
  • estimateReadingTime — estimated reading time as a number for engagement signals.

Persistent user ID with session-scoped first-touch

All-time user ID via persistent storage; session-scoped first-touch via session storage.

getPersistedDataLayerValueDirect+getGA4SessionDataLayerValueDirect

Consent-gated CRM-sync tag

Two consent states must both be granted for the tag to fire.

isGrantedAnalyticsStorage+isGrantedAdStorage🧩 ANDDirect

Preview-aware test routing

Production conversion to live endpoint; Preview conversion to test endpoint.

gtmIsPreviewMode🧩 IF (else)Directtest_endpoint / live_endpoint
GoalVariableMode
Persist value across pages (until reset)getPersistedDataLayerValueDirect / Apply
Persist value across GA4 sessiongetGA4SessionDataLayerValueDirect / Apply
DataLayer state at a specific eventgetDataLayerByEventDirect / Apply
Full current dataLayer stategetDataLayerCurrentDirect
Single dataLayer key by pathgetDataLayerValueDirect / Apply
GA4 ecommerce event listgaEcommerceEventListDirect
GA4 lead-generation event listgaLeadGenerationEventListDirect
Consent: ad_storage / analytics_storageisGrantedAdStorage / isGrantedAnalyticsStorageDirect
Consent: ad_user_data / ad_personalizationisGrantedAdUserData / isGrantedAdPersonalizationDirect
Consent: functionality / personalization / securityisGrantedFunctionalityStorage / isGrantedPersonalizationStorage / isGrantedSecurityStorageDirect
Detect Preview / Debug modegtmIsPreviewMode / gtmIsDebugModeDirect
GA4 measurement IDs in containergetGaMeasurementIdsDirect
Cookie value by namegetCookieValueDirect / Apply
localStorage value / key existencegetLocalStorage / hasLocalStorageKeyDirect
Lazy lookup table⚡ COMMAND TABLE › LazyDirect
Page title / element attributedocument.title / getElementAttributeDirect / Apply
Closest parent matching selectorfindClosestElementDirect / Apply
Estimated reading time of pageestimateReadingTimeDirect
Root domain (side-effecting)getRootDomain!Direct

How do I persist dataLayer values across pages in GTM?

Use getPersistedDataLayerValue, which writes to localStorage and templateStorage and reads from storage on subsequent pages. The value remains available until explicitly reset. Solves a gap GTM has had since launch.

How do I keep dataLayer values for the duration of a GA4 session?

Use getGA4SessionDataLayerValue, which uses the GA4 session_id as a matching key. The value persists for the duration of the session and resets when the session does.

How do I check consent state before firing a tag?

Use the isGranted* family — eight variables, one per consent type. Each returns a boolean. Use as trigger conditions or as inputs to 🧩 AND / 🧩 OR for compound consent rules.

How do I detect when GTM is in Preview mode?

gtmIsPreviewMode returns true when loaded via Tag Assistant Preview, false in production. Use as a trigger exception to suppress real-world side effects during testing.

How do I read all GA4 ecommerce events from a single variable?

gaEcommerceEventList returns the canonical list of GA4 ecommerce event names. Use with includesAny from Array to fire on any matching event.

What does the COMMAND TABLE Lazy variable do that a regular lookup table doesn’t?

Each row’s output is only evaluated when the row matches. Essential for side-effecting operations like getRootDomain! or expensive computations.

Are these variables sandbox-safe in server-side GTM?

Most are web-only by nature — Preview mode, DOM access, and browser storage are browser-context features. Consent state and dataLayer access patterns work in both web and server contexts.