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

Published · Tags: GTM, dataLayer, gtag, Command Queue Syntax, Sandboxed JavaScript, Custom Template, Meta Pixel, Ecommerce, CSP

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:

ParameterTypeDefaultPurpose
eventNameTexteventExpected value at index 0 of the gtag() arguments array — almost always the literal string event.
eventValueTextpurchaseExpected value at index 1 — the actual event name, e.g. purchase, add_to_cart, begin_checkout.
eventIndexText (single digit, regex-validated)2The array index to return once a match is found — index 2 is the payload object in a standard gtag() event push.
dataLayerCurrentEventSelect (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 - 1 down to 0), 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:

APIScope granted
loggingConsole logging permitted in all environments.
access_globalsRead-only access to the single window.dataLayer key — write and execute are explicitly denied.
read_data_layerPermission 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.

The template type is 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

FileContents
Parse Gtag Ordered dataLayer Array to Find Object by Index.tplThe 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.mdDescribes 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

  1. Download Parse Gtag Ordered dataLayer Array to Find Object by Index.tpl from the GitHub repository.
  2. In GTM, go to Variables → New → Custom Variable Type, then use the overflow menu's Import option to select the .tpl file.
  3. Create a new variable from the imported template. Set eventValue to the gtag() event name you want to capture (e.g. purchase), and leave eventName at the default event unless your implementation differs.
  4. Set dataLayerCurrentEvent to {{Event}} so the scan only runs when the firing trigger actually matches eventValue.
  5. Set eventIndex to 2 to return the full payload object, or adjust if your gtag() push places the data you need at a different index.
  6. Reference the new variable inside a second variable built on the Nested Properties Object Getter template to extract individual keys like currency and value.
  7. 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

Popular posts from this blog

Site Landing & Site Referrer Preservation

GTM Browser Viewport Measurement

UTM & URL Query String 2 Cookies