• Google Tag Manager
  • Data Layer
  • GA4
  • Tracking

The GTM Data Layer Explained, With Code Examples

By Olam Sule · Published 17 Sept 2026

TL;DR

A data layer is a JavaScript array, window.dataLayer, that your website fills with facts about the page and what the visitor just did. Google Tag Manager reads each push, stores the values and fires triggers on pushes that carry an event key, so tracking stops depending on the page's HTML. We build and repair data layers for clients most weeks.

Olamide Sule, founder of Dolphin Analytics: a digital analytics expert based in London, setting up and fixing tracking for agency and in-house clients.

We set up Google Tag Manager containers for clients most weeks, and the data layer is where we spend the most time. When tracking is flaky, the tags are rarely the root cause. The values feeding them are. Below is how we explain the data layer to a client’s developers: what it is, how GTM reads it, the code to push, and how to check it works.

What is a data layer?

A data layer is a JavaScript array, named window.dataLayer in Google Tag Manager, that your website fills with structured facts: the page type, the logged-in status, the form a visitor submitted, the value of an order. GTM reads every object pushed into that array and makes the values available to your tags and triggers.

Think of it as a contract between the website and your tracking. Developers agree to publish named values in a fixed shape. The marketing team agrees to read only those names. Neither side has to understand the other’s code.

Without a data layer, GTM has to scrape values from the page itself: a CSS selector for the price, the text of a button, a URL pattern for the thank-you page. Those break silently the next time someone redesigns the page, and nobody notices until the conversions stop. A value pushed into the data layer survives a redesign, because it doesn’t depend on how the page looks.

The term isn’t unique to GTM. Adobe’s tag manager reads adobeDataLayer and Tealium reads utag_data, and we compare those set-ups in GTM vs Adobe Launch. The rest of this post is about GTM, which is where most of the searches for this term land.

How does the GTM data layer work?

The GTM container snippet creates the array if it doesn’t exist, then watches it. Every time the site calls dataLayer.push(), GTM takes the object, merges its keys into an internal store (Google calls it the data model), and checks whether any trigger should fire.

Three rules explain almost every behaviour you’ll see:

  1. Values persist. Once page_type is pushed, it stays available on that page until a later push overwrites it. GTM keeps the latest value for each key.
  2. The event key is what triggers listen for. A push containing event: "generate_lead" gives a Custom Event trigger something to fire on. A push without an event key updates the stored values but gives no Custom Event trigger a moment to fire.
  3. Order matters. A tag reads variable values at the moment its trigger fires. If the order value is pushed after the purchase event, the tag has already gone with the old value.

You’ll also see GTM’s own events in the array: gtm.js when the container loads, gtm.dom when the page’s HTML is ready, and gtm.load when everything has finished loading. Those drive the Page View, DOM Ready and Window Loaded triggers.

One detail catches people out: the Google tag (gtag.js) uses the same array. Its gtag() function is a small wrapper that pushes into dataLayer, so a site running both hard-coded gtag.js and GTM shares one data layer between them. That’s often where duplicate GA4 events start.

How do you push data to the data layer?

Your developers add a push wherever something worth tracking happens. Here is the pattern we give them, in two parts.

Page-level values, placed above the GTM container snippet in the <head>, so they exist before any tag runs:

<script>
  window.dataLayer = window.dataLayer || [];
  window.dataLayer.push({
    page_type: "product",
    user: {
      status: "logged_in",
      customer_type: "returning"
    }
  });
</script>
<!-- Google Tag Manager snippet goes here -->

Interaction events, pushed at the moment they happen, for example when a lead form submits successfully (after the server confirms, not on the button click):

window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
  event: "generate_lead",
  form_id: "contact_main",
  form_location: "footer"
});

Four habits keep a data layer clean:

  • Always push, never reassign. dataLayer = [{ ... }] placed after the GTM snippet replaces the array GTM is watching, and every later push goes nowhere.
  • Spell it dataLayer. The name is case sensitive, and so are the keys. formId and form_id are two different values to GTM.
  • Use snake_case event names that match GA4. Pushing generate_lead rather than Form Submit - Footer means the GTM event name and the GA4 event name can be the same thing.
  • Keep personal data out. Emails, names and phone numbers don’t belong in a data layer whose values get sent to GA4. Google’s own guidance on avoiding personally identifiable information bans sending it to Analytics.

Ecommerce pushes follow a stricter structure (an ecommerce object with an items array, cleared before each push). We cover that in full in the GA4 ecommerce data layer guide, so we won’t repeat it here.

On single-page apps (React, Vue, Next.js), the page doesn’t reload when the route changes, so GTM’s page triggers only fire once. Push an event on each route change, such as event: "virtual_page_view" with the new page_path, and fire your GA4 page tag on that instead.

How do you use data layer values in GTM?

Pushing values does nothing on its own. GTM needs a variable to read each value, a trigger to fire on the event, and a tag to send it. Using the lead form push above:

  1. Create the variable. In GTM, go to Variables > User-Defined Variables > New > Variable Configuration > Data Layer Variable. Set Data Layer Variable Name to form_id and leave Data Layer Version on Version 2. Name the variable DLV - form_id and save. For a nested key, use dot notation: user.status.
  2. Create the trigger. Go to Triggers > New > Trigger Configuration > Custom Event. Set Event name to generate_lead (it must match the pushed value exactly) and leave it on All Custom Events. Save it as CE - generate_lead.
  3. Create the tag. Go to Tags > New > Tag Configuration > Google Analytics: GA4 Event. Enter your Measurement ID, set Event Name to generate_lead, then under Event Parameters add form_id with the value {{DLV - form_id}} and form_location with {{DLV - form_location}} (create that second variable the same way). Set the firing trigger to CE - generate_lead.
  4. Register the parameters in GA4 if you want them in standard reports: Admin > Data display > Custom definitions > Create custom dimension, with Scope set to Event and Event parameter set to form_id.

If you’re still setting up the container itself, start with our GTM setup guide and come back to this step.

How do you check your data layer is working?

Check it in two places before you publish anything.

In the browser console. Open the page, open developer tools (F12, or Cmd+Option+I on a Mac), go to the Console tab and type dataLayer, then press Enter. You’ll see every object pushed so far, in order. Submit the form and type it again; the generate_lead object should be the newest entry, with the values you expect.

In GTM Preview. Click Preview in your GTM workspace, enter the page URL and connect. In the Tag Assistant window:

  1. Find generate_lead in the event timeline on the left and click it.
  2. Open the Data Layer tab. It shows the exact message pushed, and the stored data model at that moment.
  3. Open the Variables tab and confirm DLV - form_id shows contact_main, not undefined.
  4. Open the Tags tab and confirm your GA4 event tag sits under Tags Fired.

A variable reading undefined almost always means a name mismatch, a nested key without dot notation, or a value pushed after the event. If the tag still won’t fire, our GTM debugging walkthrough goes through the other causes in order.

Where do data layers go wrong?

When we audit a container, the data layer faults we find most often are the dull ones, and they cost the most:

  • Events pushed on the click, not the outcome. A generate_lead pushed when the submit button is pressed counts failed validations as leads.
  • Duplicate pushes. A purchase event pushed again when the confirmation page reloads, or pushed once by the site and again by a plugin. Every duplicate counts as another sale in GA4 and the ad platforms.
  • Values as the wrong type. A price pushed as text with a currency symbol in it, instead of a plain number, breaks revenue sums in GA4.
  • No specification. Developers invent key names page by page, so the same value arrives as formID, form_id and formName on three templates.
  • Reassigning the array after GTM loads, which silently disconnects every push that follows.

The fix for the last three is the same: a written data layer specification (every event, every key, its type and an example value) that developers build from and QA checks against.

What we do when the data layer is the problem

Most tracking faults we’re asked about turn out to be data layer faults underneath. We write the data layer specification, work alongside the developers who implement it, then connect the values to GA4, Google Ads and Meta in GTM and test each event in Preview before anything goes live. For Q Hotels, working through the agency Connective3, we found duplicate purchase events inflating bookings across 16 hotels and gave the group its first accurate property-level view of direct bookings. That’s the work behind our Google Tag Manager consultant service.

If you want a quick look at what your site loads today, Sonar is our free external scan: one click, no account, and it reports the tags, pixels and consent setup it can see from outside. If the data layer itself is the worry, tell us what’s broken, or book a call.

Frequently asked

What is a data layer in Google Tag Manager?

The data layer is a JavaScript array called window.dataLayer that sits between your website and Google Tag Manager. The site pushes objects into it (a page type, a form ID, an order value), and GTM reads each push, stores the values in its own data model, and hands them to tags through Data Layer Variables. Tracking then reads named values instead of scraping the page's HTML.

Do I need a data layer if I use GTM?

GTM creates an empty data layer for you when its snippet loads, so every container has one. You need your developers to push values into it once you track anything the page's HTML does not reliably show: logged-in status, order values, form outcomes, or events in a single-page app.

What is the difference between dataLayer.push and dataLayer = []?

dataLayer.push adds an object to the existing array, and GTM processes it. Writing dataLayer = [ ... ] replaces the whole array. Above the GTM snippet that is harmless, but after GTM has loaded it cuts GTM off from the array, so later pushes are never seen. Use window.dataLayer = window.dataLayer || [] and then push, everywhere.

Why is my data layer variable undefined in GTM?

The usual causes are a name mismatch (keys are case sensitive, so formId and form_id are different), the value being pushed after the event your tag fires on, or the key sitting inside a nested object that needs dot notation such as user.status. GTM Preview shows the data layer at each event, so you can see which one it is.

Who can set up a data layer for us?

Your developers write the pushes, and someone who knows GTM should write the specification they build from and wire up the variables, triggers and tags afterwards. We do both halves for agencies and in-house teams: we write the data layer spec, work with the developers who implement it, and connect it to GA4 and ad platforms in GTM.

Talk to us

Where does your data stop making sense?

Tell us what's broken, or grab a time. Either way you hear from a person, not a sales script.

Send a message

We reply within one working day.

Add a few details (optional) The more we know up front, the faster we can tell you what's wrong and how to fix it.

Protected by an invisible spam check. Prefer email? olam@dolphinanalytics.co.uk

Calendly · 30 min

Book a call

Thirty minutes on Google Meet with the founder.

The booking lands on the same record as your message.