Parse Gtag Ordered dataLayer Array to Find Object by Index
Parse Gtag Ordered dataLayer Array to Find Object by Index — A GTM Variable Recipe Without Custom JavaScript
I was recently asked to send the currency and amount from a purchase event to Meta / Facebook using
just GTM and the data already available in the dataLayer. The catch: the purchase event wasn't pushed in
the flat, named-key shape GTM's built-in dataLayer variable expects. It was pushed the way
gtag() pushes everything — as an ordered, numerically-indexed array. Reading a value out of
that array by name doesn't work, because there is no name; there's only position.
Why gtag() Pushes Arrays Instead of Objects
gtag() is a thin wrapper around dataLayer.push(). When you call
gtag('event', 'purchase', {...}), the function doesn't push a single object shaped like
{ event: 'purchase', ... }. It pushes the raw arguments object of that function
call — the command queue syntax — straight into the dataLayer as an indexed structure:
{
"0": "event",
"1": "purchase",
"2": {
"currency": "USD",
"value": "99.99",
...
}
}
That's a meaningfully different shape from the { event: "purchase" } style dataLayer push
that GTM's out-of-the-box dataLayer variable was built to read. The default variable template looks for
a named event key. It has no concept of "index 0 says event, index 1 says purchase, index 2
holds the payload." Reading currency and value out of a gtag()-pushed purchase event therefore requires
something that understands command queue syntax specifically, not generic dataLayer flattening.
The Structural Problem the GTM Variable Solves
The GTM variable template built for this solves a specific structural gap that none of the standard,
out-of-the-box GTM variables cover: parsing the arguments-object style of gtag() pushes
across the entire dataLayer history, in reverse order, to reliably find the most recent matching
event.
It does this by scanning the dataLayer from the newest entry backward and identifying pushes that are
arrays where index [0] equals "event" and index [1] equals
"purchase" (or whatever event name you're targeting). To avoid noisy, unnecessary errors in
the browser console, the loop only runs at all when GTM's own {{Event}} variable already
matches the desired value — there's no point scanning the full dataLayer history on every single tag fire
across the container.
Although the example here is built around "purchase", any ecommerce (or non-ecommerce)
event pushed into the dataLayer using gtag() syntax works the same way — swap the event name
and value and the same template parses it.
Why Sandboxed JavaScript Instead of a Custom JavaScript Variable
As with the other recipes on this blog, the goal is to deploy as little custom JavaScript as possible.
A Custom JavaScript variable that reads window.dataLayer directly would work functionally,
but it runs as an inline script the browser has to be told to trust — meaning a new CSP allowance or hash
every time the logic changes. Building this as a GTM Custom Template using Sandboxed JavaScript avoids
that entirely: the template requests narrowly-scoped permissions (read-only access to
window.dataLayer, plus GTM's own read_data_layer API) instead of running as an
arbitrary inline script.
Template Parameters
The published GTM Variable Template (type MACRO) exposes four parameters so the same
template can be reused for any gtag()-pushed event, not just purchase:
| Parameter | Type | Default | Purpose |
|---|---|---|---|
eventName | Text | event | Expected value at index 0 of the gtag() arguments array — almost always the literal string event. |
eventValue | Text | purchase | Expected value at index 1 — the actual event name, e.g. purchase, add_to_cart, begin_checkout. |
eventIndex | Text (single digit, regex-validated) | 2 | The array index to return once a match is found — index 2 is the payload object in a standard gtag() event push. |
dataLayerCurrentEvent | Select (macros enabled) | {{Event}} | The trigger-scoped variable to compare against eventValue before scanning — normally GTM's built-in {{Event}} variable, so the dataLayer is only parsed when the current tag fire actually matches. |
The Sandboxed JavaScript
This may look like a lot of additional structure for what is functionally a one-line lookup, but it is just doing the array scan above with GTM's sandboxed APIs instead of raw JavaScript:
const log = require('logToConsole');
const copyFromWindow = require('copyFromWindow');
const makeNumber = require('makeNumber');
const copyFromDataLayer = require('copyFromDataLayer');
const dataLayer = copyFromWindow('dataLayer');
const eventName = data.eventName || 'event';
const eventValue = data.eventValue || 'purchase';
const eventIndex = data.eventIndex || '2';
const dataLayerCurrentEvent = data.dataLayerCurrentEvent;
var indexInt = makeNumber(eventIndex);
var returnResult;
if (dataLayerCurrentEvent == eventValue) {
for (var i = dataLayer.length - 1; i > -1; i--) {
if (dataLayer[i][0] === eventName && dataLayer[i][1] === eventValue) {
returnResult = dataLayer[i];
break;
}
}
if (returnResult[1] === eventValue) {
return returnResult[indexInt];
} else {
return undefined;
}
} else {
return undefined;
}
Two details are worth calling out:
- The loop runs backward (
dataLayer.length - 1down to0), because the most recent matching push is almost always the one you want — especially on pages where the same event type could theoretically fire more than once. - The outer
if (dataLayerCurrentEvent == eventValue)guard means the scan is skipped entirely unless the variable is being evaluated in the context of the matching event. This is what keeps the template from throwing console errors on every unrelated tag fire across the container.
Required Web Permissions
Because the template runs inside GTM's sandbox, every capability it touches has to be explicitly
granted in the template's ___WEB_PERMISSIONS___ block:
| API | Scope granted |
|---|---|
logging | Console logging permitted in all environments. |
access_globals | Read-only access to the single window.dataLayer key — write and execute are explicitly denied. |
read_data_layer | Permission to use GTM's own copyFromDataLayer API, with allowedKeys set to any. |
Notice that access_globals is granted read-only for dataLayer — this
template only ever inspects the array. It never pushes to it or mutates an existing entry, which is
exactly the level of access a lookup-only variable should request.
MACRO — a GTM variable template,
not a tag template. You'll find it under Variables → New → Custom Variable Type after
import, not under Tags.Putting It to Use: Sending Currency and Value to Meta
The returned JSON object at index [2] — the actual ecommerce payload — can then be
evaluated further inside GTM and forwarded to Meta / Facebook or any other endpoint. The final piece of
that puzzle, pulling currency and value back out of the returned payload object,
is accomplished by pairing this template with a second, general-purpose template: the
Nested Properties Object Getter
variable. This recipe identifies which dataLayer push to read; the Nested Properties Object Getter
then reaches into the resulting object to pull out the specific keys (currency,
value) that the Meta tag needs.
What's on GitHub
| File | Contents |
|---|---|
Parse Gtag Ordered dataLayer Array to Find Object by Index.tpl | The full GTM Custom Variable Template: metadata block, the four template parameters (eventName, eventValue, eventIndex, dataLayerCurrentEvent), the Sandboxed JavaScript body, and the logging / access_globals / read_data_layer permission grants. |
README.md | Describes the template's purpose: parsing gtag() command-queue-style dataLayer pushes ("0": "event", "1": "purchase") to return the payload object at a chosen index, gated on a match against GTM's {{Event}} variable to avoid console errors. |
How to Install
- Download
Parse Gtag Ordered dataLayer Array to Find Object by Index.tplfrom the GitHub repository. - In GTM, go to Variables → New → Custom Variable Type, then use the overflow menu's Import option to select the
.tplfile. - Create a new variable from the imported template. Set
eventValueto the gtag() event name you want to capture (e.g.purchase), and leaveeventNameat the defaulteventunless your implementation differs. - Set
dataLayerCurrentEventto{{Event}}so the scan only runs when the firing trigger actually matcheseventValue. - Set
eventIndexto2to return the full payload object, or adjust if your gtag() push places the data you need at a different index. - Reference the new variable inside a second variable built on the Nested Properties Object Getter template to extract individual keys like
currencyandvalue. - Open GTM Preview, trigger the target event, and confirm the variable resolves to the expected payload object — and resolves to
undefined, with no console errors, on every other event.
This post is a Google Blogger adaptation of my original "Parse Gtag Ordered dataLayer Array to Find Object by Index", first published on my Weebly blog (DREW SPENCER) on March 10, 2026. The GTM Variable Template source — parameters, permissions, and Sandboxed JavaScript — is published on GitHub at drewspen/Parse-Gtag-Ordered-dataLayer-Array-to-Find-Object-by-Index. The companion Nested Properties Object Getter variable template referenced above is a separate, third-party GTM Custom Template by justia.
Comments
Post a Comment