The GA4 data layer is the invisible foundation of your entire tracking setup: it is the JavaScript object where your site drops the information (page view, add to cart, purchase) that Google Tag Manager then reads to feed GA4. When it is clean, your reports are reliable and your campaigns run on real signal. When it drifts, the damage is sneaky: revenue inflated two to four times over, empty product reports, a missing transaction_id that double-counts your sales. According to measurements gathered in 2026, close to 70% of e-commerce implementations ship at least one critical data layer error. This guide is grounded in practice: what a clean data layer actually is, the 7 most common mistakes with the right code to push, and a validation checklist so nothing breaks again when the site evolves.
What a clean data layer is
Before the mistakes, let us set the frame. The data layer is a plain JavaScript array, declared on the page before GTM loads:
window.dataLayer = window.dataLayer || [];
You never rewrite that array, you push objects into it as interactions happen:
window.dataLayer.push({
event: "add_to_cart",
ecommerce: {
currency: "EUR",
value: 29.90,
items: [{
item_id: "SKU_12345",
item_name: "Organic cotton t-shirt",
price: 29.90,
quantity: 1
}]
}
});
Three principles hold the whole structure together. First, the variable name is case-sensitive: it is dataLayer, never datalayer or DataLayer. One letter out of place and GTM reads nothing. Second, you push, you do not overwrite: dataLayer.push() appends a message, whereas a direct assignment (window.dataLayer = [...]) destroys the history and breaks the triggers already armed. Third, every push follows a stable event schema: an event key that names the action, and normalized parameters (types, case, units) that GTM will always find in the same place.
Think of the data layer as a data contract between the site and the tag manager. The site commits to providing fields that are consistently named, typed, and formatted; GTM commits to reading them without guessing. The moment one side breaks the contract (a price as text, a lowercase currency, a renamed key), the chain fails silently.
If your GA4 property is new or shaky, fix the foundations first with the guide Set Up GA4 From Scratch, then come back to secure the data layer that feeds it.
The 7 mistakes that wreck your data
1. An empty or malformed items[] array
This is the most visible mistake. Your monetization reports show revenue, yet the “Items purchased” report stays stubbornly empty. The cause: the purchase event (or view_item, add_to_cart) fires with an empty items array, or one filled with objects that do not match the GA4 schema.
GA4 expects a precise structure: items must be an array of objects, each carrying at least item_id or item_name. A singular items object, a string instead of an array, or a numeric item_id instead of a string is enough to empty your product reports.
// Correct
window.dataLayer.push({
event: "purchase",
ecommerce: {
transaction_id: "T_20260617_0042",
value: 89.70,
currency: "EUR",
items: [
{ item_id: "SKU_12345", item_name: "Organic cotton t-shirt", price: 29.90, quantity: 2 },
{ item_id: "SKU_67890", item_name: "Logo cap", price: 29.90, quantity: 1 }
]
}
});
2. A missing or non-unique transaction_id
Without a transaction_id, GA4 cannot deduplicate purchases. As a result, a customer who refreshes the confirmation page triggers a second purchase, and your revenue doubles on the affected orders. Conversely, a hard-coded transaction_id (the same value for every order) overwrites transactions against each other.
The rule is simple: every order must carry a genuinely unique identifier, ideally the order ID from your back office, pushed once on the confirmation page. GA4 uses this field to ignore duplicates received within a given time window.
3. A lowercase or missing currency
The currency field must follow the uppercase ISO 4217 format: EUR, USD, GBP. Push eur in lowercase, or omit the field, and GA4 either rejects the monetary value or applies the property’s default currency. On a multi-currency site, that guarantees wrong revenue: dollars counted as euros, or amounts ignored entirely.
// Wrong: "eur" in lowercase, value as text
ecommerce: { currency: "eur", value: "89.70", items: [/* ... */] }
// Correct: uppercase ISO currency, numeric value
ecommerce: { currency: "EUR", value: 89.70, items: [/* ... */] }
4. Prices and amounts sent as text
GA4 expects numbers for value, price, and quantity, not strings. A price pushed as text ("29.90", or worse "29,90 EUR" with a comma and a symbol) breaks aggregation: GA4 may read the value as zero, or refuse to sum it. The classic symptom is undervalued or zero revenue even though the transaction count looks right.
Before the push, always convert your amounts to decimal numbers using a dot, with no symbol and no thousands separator: parseFloat(price) on the JavaScript side, or clean formatting on the server side. The decimal separator is the dot, never the comma.
5. Forgetting ecommerce: null between events
This is the most treacherous mistake, because it does not break anything right away. When you push several e-commerce events in a single session (a product view, then an add to cart), the ecommerce object from the first push stays in memory in the data layer. If you do not reset it, the items from the first event bleed into the second, and you end up with phantom products attributed to the wrong events.
The fix is a single cleanup line before each new e-commerce push:
// Reset before pushing the new event
window.dataLayer.push({ ecommerce: null });
window.dataLayer.push({
event: "add_to_cart",
ecommerce: { currency: "EUR", value: 29.90, items: [/* ... */] }
});
That ecommerce: null clears the previous object and guarantees each event fires with its own data only.
6. Overly broad triggers that inflate revenue
On the GTM side, a poorly scoped trigger is as destructive as a coding error. The typical case: a purchase tag that fires on “all pages” whose URL contains /confirmation, when that same URL is also the order-tracking page viewed several times after the purchase. Every visit re-counts the sale. You also see tags armed on a custom event that is too generic and fires on every click.
Always scope your triggers to the precise data layer event (event equals purchase), never to a URL pattern alone, and combine it with a condition on the presence of transaction_id. It is this imprecision, more than the code itself, that most often inflates revenue two to four times over.
7. Inconsistent key casing and naming
The last trap is diffuse but common: a site that pushes item_name here, itemName there, or that mixes value, revenue, and total across pages. GTM reads exactly what it was told to read; any variation in the key name returns an undefined value. The symptom is a parameter that shows up on some pages and not others, with no apparent logic.
The solution is not technical but organizational: a data layer specification, written once, that locks the name, type, and format of every key, and that developers and marketers alike respect. This is the heart of the data layer governance that is becoming a standard in 2026.
Mistake, impact, fix: the reference table
| Mistake | Impact on your data | Fix |
|---|---|---|
Empty or malformed items[] | Empty product reports | Array of objects with item_id/item_name |
Missing or fixed transaction_id | Double-counted or overwritten sales | Unique order ID, pushed once |
| Lowercase or missing currency | Wrong or ignored amounts | Uppercase ISO 4217 code (EUR) |
| Amounts as text | Zero or undervalued revenue | Decimal numbers with a dot, no symbol |
Forgetting ecommerce: null | Phantom products between events | Push { ecommerce: null } before each event |
| Overly broad triggers | Revenue inflated 2 to 4 times | Trigger on the exact event + transaction_id |
| Inconsistent casing and naming | Intermittently undefined parameters | A locked data layer specification |
The data layer validation checklist
Fixing is not enough: you have to validate, then revalidate every time the site changes. Here is the sequence to run before any production release.
First, GTM Preview Mode. Open the preview, reproduce the journey (product view, add to cart, purchase), and inspect the Data Layer tab at each step. Check that every expected event appears, exactly once, with its complete ecommerce object.
Next, the schema check. For each event, confirm that items is indeed an array, that currency is uppercase, that value, price, and quantity are numbers, and that transaction_id is present and unique on the purchase.
Also verify consent. Make sure your e-commerce pushes respect the Consent Mode state and do not fire before the user has made a choice. The full mechanics are covered in the guide Consent Mode v2 in GA4.
Test the reset between events: on a page that chains several e-commerce events, confirm that ecommerce: null is pushed and that the items do not blend together. Single page applications (SPAs) deserve special attention, because navigation does not reload the page and the data layer is not reset automatically.
Finally, harden collection on the server side. Routing your events through a server container adds a layer of control and resilience against browser blocking. The full walkthrough is in the guide GTM Server-Side: Why and How to Migrate.
To audit quality over time, nothing beats the raw export. If you have wired up the GA4 BigQuery export, you can query event-level parameters directly and spot transactions with no transaction_id, non-standard currencies, or null values, without relying on interface sampling.
In summary
A reliable data layer is not a matter of luck, it is a data contract honored at every push. The seven mistakes covered here (empty items, missing transaction_id, lowercase currency, amounts as text, forgetting ecommerce: null, overly broad triggers, inconsistent naming) account for nearly every case of inflated revenue and empty reports. The cure comes down to three reflexes:
- Lock the schema: a data layer specification that defines the name, type, and format of every key, shared between developers and marketers.
- Push cleanly: always
dataLayer.push(), alwaysecommerce: nullbefore each new event, numbers and currencies in the right format. - Validate every time: Preview Mode, schema check, consent, SPA reset, then ongoing BigQuery audits.
The data layer is the building block every other tool assumes. Securing it once means GA4, server-side, and BigQuery finally work on numbers you can trust. To go further on the overall architecture of your measurement, see how to choose your analytics stack in 2026.