LW IT Solutions logo LW IT Solutions logo LW IT Solutions
« Blog Overview /Digital Analytics/Tutorials / Tutorial: Turning a Nested dataLayer Object Into...
Read this article in other languages:

Tutorial: Turning a Nested dataLayer Object Into Durable GTM Variables

Tutorial: Turning a Nested dataLayer Object Into Durable GTM VariablesTutorial: Turning a Nested dataLayer Object Into Durable GTM Variables

Management Summary

The dataLayer is a shared list where a website places information for Google Tag Manager to read. In a shop, it carries details such as products, quantities and prices from the shopping process into measurement. Consistent structure helps the correct information reach reports even when baskets change. A product's position in a list is not its identity, and older entries can remain if replacement is incomplete. Clear product data prevents one basket's details from being mistaken for another's.

Contents
  1. A complete add_to_cart event
  2. Reading nested fields with version 2
  3. Position is different from identity
  4. Message queue and merged model
  5. Optional validation without invented defaults
  6. Checking the selected event
  7. Questions and answers
  8. Sources

A data layer variable in Google Tag Manager (GTM) can return a number, a string, an object or an entire array. Dot notation selects a value inside nested data. The important distinction is between a field name and an array position: items.0 means the first item, not a particular product.

Durable variables need a documented data structure, complete event payloads and checks for missing values. An index is useful when position is the intended meaning; product identity requires an identifier.

Named paths select fields; numeric indices select positions. The whole items array avoids a fixed product count.
Named paths select fields; numeric indices select positions. The whole items array avoids a fixed product count.

A complete add_to_cart event

The example adds two chairs and one table in a single action. The event contains those newly added items, rather than the whole existing cart. The value is 64.95 × 2 + 219.00 × 1 = 348.90, excluding shipping and tax. Currency is required when a value is supplied.

JavaScript
window.dataLayer = window.dataLayer || [];
dataLayer.push({ ecommerce: null });
dataLayer.push({
  event: "add_to_cart",
  ecommerce: {
    currency: "EUR",
    value: 348.90,
    items: [
      { item_id: "SKU-42", item_name: "Chair", price: 64.95, quantity: 2,
        item_category: "Furniture", index: 0 },
      { item_id: "SKU-77", item_name: "Table", price: 219.00, quantity: 1,
        item_category: "Furniture", index: 1 }
    ]
  },
  user: { logged_in: true, segment: "b2b" }
});

The initialization preserves an existing queue. Clearing ecommerce prevents old fields from leaking into this complete payload. The event name and related data belong in the same push. user.segment is an example of separate application state; a push alone does not configure a GA4 user property.

Reading nested fields with version 2

A Data Layer Variable named DLV - ecommerce.currency, with Data Layer Variable Name ecommerce.currency and version 2, returns EUR. The display name is arbitrary; the configured path determines the value. Further variables can use ecommerce.value, ecommerce.items and ecommerce. The last two return an array and an object, respectively.

Version 1 treats dots as literal characters in a key. Version 2 interprets dots as nested structure and can also expand dotted keys such as {"page.type":"pdp"}. A literal dotted key therefore does not automatically require version 1. Consistent nested objects avoid ambiguous mixtures of dotted keys and object properties.

Names are case-sensitive. A missing path without a configured default returns undefined. The resulting behavior depends on the tag and how it converts values into the outgoing request: a parameter may be omitted or converted, or custom code may fail. A missing value does not guarantee an empty parameter or a particular reporting difference.

Position is different from identity

PathMeaning
ecommerce.currencyEvent currency
ecommerce.valueEvent value
user.segmentApplication segment
ecommerce.itemsWhole item array
ecommerce.items.0.item_idID of the first item
ecommerce.items.1.pricePrice of the second item; absent with only one item

A named path remains useful only while the schema and event data remain consistent. An index path remains correct when the requirement is “the first item”; a change in order changes the product at that position. A product-specific lookup should search by item_id. The whole ecommerce.items array is normally the suitable input for GA4 ecommerce, with no custom mapping required when the source already follows the expected schema.

Message queue and merged model

window.dataLayer is an array of queued messages. GTM processes messages and maintains an internal data model. Version 2 can merge objects and arrays at every nested level: omitted fields or trailing items can survive from earlier messages. A shorter array does not reliably clear earlier entries.

Google’s GTM ecommerce examples clear the previous ecommerce object with dataLayer.push({ecommerce:null}) before sending a complete event. This pattern fits the example above. Integrations deliberately building state across several messages need a coordinated reset and payload design; blindly clearing an incomplete payload can remove required data.

Optional validation without invented defaults

The following Custom JavaScript variable, named JS - ecommerce valid, reads a version-2 Data Layer Variable named DLV - ecommerce. The function returns a boolean and leaves the source untouched.

The example contract is intentionally strict: EUR, numeric nonnegative prices and values, positive whole-number quantities, and a nonempty string in at least one of item_id or item_name. Every supplied identifier or name must be a nonempty string. GA4 also accepts an item with only a name; an ID is not universally mandatory. GA4 defaults an omitted quantity to 1, but this example requires an explicit quantity to expose missing shop data.

JavaScript
function () {
  var e = {{DLV - ecommerce}};
  function number(x) { return typeof x === "number" && isFinite(x); }
  function text(x) { return typeof x === "string" && x.trim() !== ""; }
  if (!e || e.currency !== "EUR" || !number(e.value) || e.value < 0 ||
      !Array.isArray(e.items) || !e.items.length) { return false; }
  var sum = 0;
  for (var i = 0; i < e.items.length; i++) {
    var a = e.items[i];
    if (!a || typeof a !== "object" || Array.isArray(a)) { return false; }
    if (!text(a.item_id) && !text(a.item_name)) { return false; }
    if (a.item_id !== undefined && !text(a.item_id)) { return false; }
    if (a.item_name !== undefined && !text(a.item_name)) { return false; }
    if (!number(a.price) || a.price < 0 ||
        !number(a.quantity) || a.quantity <= 0 ||
        Math.floor(a.quantity) !== a.quantity) { return false; }
    sum += a.price * a.quantity;
  }
  return isFinite(sum) && Math.abs(sum - e.value) < 0.005;
}

The comparison allows less than half a euro cent of floating-point difference. This is an example check for EUR, not a universal currency, discount or tax model. Free items with a genuine numeric price of 0 remain valid. Strings such as "64.95", malformed prices and zero quantities fail instead of silently becoming 0 or 1. Optional item fields remain available because the array is not rebuilt.

For the GA4 add_to_cart tag, a Custom Event trigger uses event name add_to_cart and the additional condition {{JS - ecommerce valid}} equals true. Event parameters map currency, value and items to the corresponding version-2 variables. The guard must be attached to the actual sending tag; merely returning undefined from a different variable does not stop a tag. Other triggers must not bypass the check.

Checking the selected event

In GTM preview, the same selected event provides three complementary views: the original message, the resulting data layer state and the resolved variables. Additional model fields can be intentional persistent state, not automatically a defect. Missing results can arise from timing, a clear, a wrong version, spelling or custom validation.

A useful test sequence covers two items, one item, changed order, a free item, a name-only item and malformed input. After a second shorter event, old products must not remain. The tag must fire once for valid input and remain blocked for invalid input. Tag Assistant and the outgoing GA4 request then verify the configured mapping; a unit test of the function alone cannot verify the container.

Questions and answers

What happens to a price with a decimal comma?

The validator rejects text, including "64,95". Numeric prices at the source are preferable. Any conversion of localized text requires a known locale and explicit validation; deleting dots indiscriminately would turn an English decimal price into a different amount. The expression Number(value) || 0 conceals invalid input and should not supply a financial default.

Should the custom variable push another message?

No. Variable evaluation can occur more than once. A pure function reads input and returns a result without modifying the queue or its items. A push inside a variable can create unexpected events or loops.

Lukas Wojcik

Lukas Wojcik

Systems architect and technology enthusiast specializing in scalable tracking solutions, GMP Stack (GA4 & GTM), and robust backend architectures. Advocate for clean code and privacy-first design.

Get in touch

A question or an idea for a web project? Tell me a little about it. This form uses reCAPTCHA and formconfirmed to protect against spam.

Write a comment

Differing figures from other accounts and questions about the setup are welcome here.

The email address is not published. Required fields are marked with an asterisk.

LUKASWOJCIK.COM · NEWSLETTER

Fresh ideas in your inbox

New articles, practical tools and updates from the projects.

Choose topics (optional)
No selection: all topics.

Subscription starts after confirmation by email. · Privacy

Privacy · Newsletter

Email address, language, selected topics and subscription/confirmation dates are stored to deliver the newsletter. Double opt-in is required. Every issue includes an immediate unsubscribe link. Formconfirmed checks the signup for spam. No email open or click tracking.

Read the full privacy policy ↗