GA4 dataLayer Auto-Tracking
If your shop already sends GA4 ecommerce events, it already writes every product view, add to cart and order into a list called the dataLayer. AxiTrace can read that same list and turn it into AxiTrace events, so you do not have to add tracking calls by hand. This page explains exactly what is read, how the automatic shop check decides whether to switch it on, and how double counting is prevented.
1. What a dataLayer is
A dataLayer is a plain JavaScript list, usually window.dataLayer, where your shop writes what
a visitor does: a product was viewed, something was added to the cart, an order was placed. Google Tag
Manager and GA4 read from it. Each entry is called a push.
If GA4 ecommerce is implemented properly on your shop, you already have one. A GA4 purchase push looks like this:
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({ ecommerce: null }); // clears the previous ecommerce object
window.dataLayer.push({
event: 'purchase',
ecommerce: {
transaction_id: 'ORD-1041',
value: 129.90,
tax: 24.29,
shipping: 9.90,
currency: 'EUR',
coupon: 'SPRING10',
items: [
{ item_id: 'SKU-17', item_name: 'Oak Side Table', price: 59.95, quantity: 2 },
{ item_id: 'SKU-42', item_name: 'Linen Cushion', price: 10.00, quantity: 1 }
]
}
});
Not sure whether your shop has one? The dataLayer check on the Google Tag Manager page prints it in your browser console in 30 seconds, and the shop check below looks for it automatically.
2. How AxiTrace reads it
When dataLayer auto-tracking is on, the AxiTrace SDK on your pages observes the dataLayer. It first reads every entry that is already in the list, then watches every new push. A push made before the SDK loaded is therefore not lost. These eleven GA4 events are translated:
| GA4 dataLayer event | AxiTrace event | What is taken from the push |
|---|---|---|
view_item_list | view_item_list | item_list_id, item_list_name, items |
select_item | select_item | item_list_id, item_list_name, the selected item |
view_item | product.view | The first item: id, name, category, brand, price, quantity, currency |
add_to_cart | product.addToCart | The first item, as for view_item |
remove_from_cart | remove_from_cart | Items and value (derived from the items when missing) |
begin_checkout | begin_checkout | Items, value, currency, coupon |
add_shipping_info | add_shipping_info | Items, shipping_tier, coupon |
add_payment_info | add_payment_info | Items, payment_type, coupon |
purchase | purchase | See Purchases |
search | search | search_term |
generate_lead | form.submit | Value, currency, and the items as the lead's products (see Leads) |
Every event made this way carries the marker source: "ga4_datalayer", so AxiTrace can always
tell an event read from your dataLayer apart from one your shop sent in another way.
Shapes that are understood
- Google Tag Manager pushes:
dataLayer.push({ event: 'add_to_cart', ecommerce: {...} }). - gtag() calls:
gtag('event', 'add_to_cart', {...}), with the fields at the top level. - Split pushes: an
ecommerceobject pushed on its own, followed within 2 seconds by an event push without one. The stored object is used for that event.{ ecommerce: null }clears it. - Universal Analytics enhanced ecommerce: the older
ecommerce.purchase(actionField+products),ecommerce.checkout,ecommerce.addandecommerce.detailblocks are translated topurchase,begin_checkout,add_to_cartandview_item. - Universal Analytics classic ecommerce: the flat
transactionId,transactionTotal,transactionTax,transactionShippingandtransactionProductskeys Google Tag Manager's original Google Analytics tag read on the order confirmation page, with or without anevent. They are read as apurchase; see Classic ecommerce transactions. - Products without an id: when an item has no
item_id, itsitem_name(oritem_variant) is used as the SKU, so the product event is not rejected.
Customer details from user_data
Ad platforms match a conversion to a person more often when it carries the buyer's email, phone and
address. If your pushes include GA4 user_data, AxiTrace reads it from three places: the top
level of the push, inside the ecommerce object, and a
gtag('set', 'user_data', {...}) call, which is remembered and applied to every later event on
the page.
window.dataLayer.push({
event: 'purchase',
user_data: {
email: 'buyer@example.com',
phone_number: '+48600100200',
address: {
first_name: 'Anna',
last_name: 'Nowak',
city: 'Krakow',
region: 'Lesser Poland',
postal_code: '30-001',
country: 'PL'
}
},
ecommerce: { transaction_id: 'ORD-1041', value: 129.90, currency: 'EUR', items: [ /* ... */ ] }
});
GA4 user_data key |
Used by AxiTrace as |
|---|---|
email or email_address | Email, and the visitor is identified by it when no user id is known yet |
phone_number | Phone |
address.first_name, address.last_name | First name, last name |
address.city, address.region | City, state or region |
address.postal_code, address.country | Postcode, country |
address may be an object or a list; for a list the first entry is used. AxiTrace needs the
plain values and hashes them itself before anything is sent to an ad platform. That is why an email or
phone that is already a 64-character hash is dropped, and every sha256_* key
(sha256_email_address, sha256_phone_number and so on) is ignored: hashing a hash
would make it match no one.
What is not read
view_cartandrefund. They are ignored in silence, because AxiTrace has no endpoint for them yet.- Any other event name, including GTM's own entries (
gtm.js,gtm.dom,gtm.load) and Universal Analytics actions other than the four above (impressions,click,remove,refund,promoView). - A gtag event whose
send_tonames only Google Ads (AW-) or Floodlight (DC-) targets. That is a conversion ping for another Google product, not a GA4 event. Whensend_toalso names a GA4 property (G-), it is read. sha256_*keys and already hashed email or phone values, as explained above.- A dataLayer that is not a list. If
window.dataLayeris some other kind of object, the SDK leaves it untouched, logs one warning in the console and auto-tracking stays off on that page. - A dataLayer replaced late. If the page throws away its dataLayer and creates a new list, the SDK follows the new list for 20 seconds after it started. A list created by a script that runs later than that is never seen.
- Pages the SDK is not on. A hosted checkout on another domain (a payment provider, a marketplace checkout) and content inside iframes are invisible to it.
If the dataLayer does not exist yet when the SDK starts, the SDK creates an empty one, exactly as the Google Tag Manager snippet does, so pushes made afterwards are still read.
3. Purchases
The purchase is the event that matters most, so it has the most rules.
| Field | How it is used |
|---|---|
transaction_id |
Becomes the AxiTrace order id and the key for deduplication. Always send your real order number. |
value |
The order value. GA4 has no separate revenue field, so value is also stored as the
order revenue. An explicit revenue (Universal Analytics carries one) wins. |
currency |
Stored in upper case, for example EUR. When a push carries a value but no currency,
the default currency of your workspace (Workspace settings) is used, never an assumed US dollar. |
tax, shipping, coupon |
Stored as sent. |
items |
The products of the order. The product count is the number of units: two of one product and three of another count as five, and an item without a quantity counts as one. |
A reloaded confirmation page is not a second sale
Purchases are deduplicated on transaction_id, per workspace, within 30 days.
A buyer who reloads the order confirmation page, opens it again from the browser history a week later, or
a tag that fires twice, produces one order in AxiTrace, not two. Two different shops that both happen to
have an order number 1041 never affect each other.
Missing and placeholder order ids
A purchase without a transaction_id is still recorded, because it is still revenue, but it
cannot be deduplicated: a reload counts it twice. The SDK says so in the browser console. The same applies
when the id is a value a tag manager writes when a variable did not resolve. These are treated exactly
like a missing id (compared without case and surrounding spaces):
undefined, null, (not set), nan, false,
-, and an empty value.
Treating them as real ids would be worse than having none: every order of the shop would share the same id and all of them after the first would be dropped as repeats.
Classic ecommerce transactions
Many custom shops set up Google Tag Manager years ago with the original Google Analytics "standard ecommerce" format and still point their GA4, Google Ads, Meta and Microsoft purchase tags at it. The order is a flat object on the confirmation page, usually placed above the Google Tag Manager snippet:
window.dataLayer = [{
transactionId: '100234',
transactionAffiliation: 'My Shop',
transactionTotal: '1,234.56',
transactionTax: '205.76',
transactionShipping: '0.00',
transactionProducts: [
{ sku: 'FOX-10', name: 'Battery system', category: 'Batteries', price: '1,228.56', quantity: '1' },
{ sku: 'CR-1', name: 'Ceiling rose', category: 'Accessories', price: '3.00', quantity: '2' }
]
}];
AxiTrace reads it as it is, with no Google Tag Manager variables to create:
| Classic key | Read as |
|---|---|
transactionId | The order id (transaction_id), with the same deduplication as a GA4 purchase |
transactionTotal | The order value and revenue. When it is missing, the sum of price times quantity of the products |
transactionTax, transactionShipping | Tax and shipping |
transactionProducts | The items: sku as the item id, name, category, price, quantity |
transactionAffiliation | Not used |
- Currency. The classic format has no currency field, so the default currency of your
workspace is used. Make sure it is set correctly in Workspace settings. If your object does carry one as
transactionCurrency,currencyCodeorcurrency, that one wins. - Numbers written as text are understood:
"38.26","1,234.56","£1,234.50"and"1.234,56"are all read correctly. A value that cannot be read is left out rather than guessed. - An entry is a transaction when it has a
transactionIdor atransactionTotal, and a total or at least one product. AtransactionIdon its own is ignored. - Later Google Tag Manager events (
gtm.dom,gtm.load, a consent banner event) do not repeat the purchase: each dataLayer entry is read once.
One order per confirmation page
A shop that added GA4 later sometimes describes the same order twice on one page: once in the classic
keys and once in a GA4 or Universal Analytics enhanced purchase push, sometimes even with
different ids. AxiTrace counts the first one it reads and skips the other on that page. When a single push
carries both an ecommerce purchase block and the classic keys, the block is read. A reload of
the page is still deduplicated on the order id, because both formats use the same key.
4. The shop check
On the Manual connection page of the AxiTrace admin panel, an AI shop check looks for a GA4 ecommerce dataLayer on your shop and sets auto-tracking for you. It starts by itself the first time you open that page, when your workspace already knows your shop address. Otherwise you enter the address and click Check my shop. It usually takes under a minute, and you can leave the page while it runs.
What is fetched
- Your home page.
- Your
/sitemap.xml, to find product pages. - Up to two product pages and one cart or checkout page, chosen from links on your home page and your sitemap.
- Your public Google Tag Manager container (the same
gtm.jsfile every visitor's browser downloads), when your pages load one.
Only pages on your shop's own domain and its subdomains are read. The check reads the HTML of those pages and the dataLayer
pushes written in them; it does not run your scripts and does not download other script files. It sends
an honest user agent, AxiTraceShopCheck/1.0, with a link back to this page.
It never logs in, never fills in forms, never places an order and never clicks through a cookie banner. If bot protection stops it, it reports that and stops. It does not try to get around it.
How the result is decided
A program first extracts evidence from what was fetched: dataLayer pushes and gtag calls with GA4
ecommerce event names, GA4 event tags in your tag container, the shop platform, a cookie banner that holds
back Google Tag Manager, and any existing AxiTrace tracking. An AI model then judges that evidence (it
receives the extracted evidence, never your raw pages), and a fixed set of rules checks the answer. The
rules can only make the answer more careful: auto-tracking is switched on only when there is hard
evidence, meaning a GA4 ecommerce push or gtag call in your pages, a GA4 ecommerce event tag in your
tag container, or classic ecommerce transaction keys: in your page code, or as Data Layer Variables
(transactionId with transactionTotal or transactionProducts) in your
tag container. The container is what finds a classic setup, because the order itself only appears on the
confirmation page, which the check never reaches.
Results
| Result | What it means | Auto-tracking |
|---|---|---|
| GA4 ecommerce dataLayer found | Your shop publishes GA4 ecommerce events. The card lists the events found, the product page used and the reasons. | Switched on, with the dataLayer name found |
| No GA4 ecommerce dataLayer found | No hard evidence was found. Send events with AxiTrace tracking calls (see the JavaScript SDK), or switch auto-tracking on yourself if you know the dataLayer is there. | Switched off |
| Already tracked | Your pages already send events to AxiTrace (AxiTrace tracking calls or an AxiTrace plugin), so reading the dataLayer as well would count everything twice. On a Shopify store the result points you to the AxiTrace Shopify app instead, because scripts in a Shopify theme do not run on Shopify's checkout. | Switched off |
| Blocked | Your shop's bot protection stopped the check. Tracking itself is not affected. | Unchanged |
| Address problems | The address could not be resolved, points to a private network, did not answer within 10 seconds, did not return a web page, or redirects to a different domain. | Unchanged |
| Failed on our side | Something went wrong at AxiTrace. We are notified automatically, and the attempt does not count against your limit. | Unchanged |
When a cookie banner holds back your Google Tag Manager until consent, the check still reads your pages and says so in the reasons.
Limit
The shop check is free on every plan. Each workspace can run it 3 times in any 24 hours; the card shows when the next check is available.
5. The Workspace setting
Auto-tracking is controlled by the GA4 dataLayer auto-tracking card in Workspace settings, in the Data collection section. It has a switch, an optional dataLayer name, and a line that says who set it last and when: the shop check, or you. The SDK receives the setting together with the rest of your workspace settings when it starts, so there is nothing to copy into your pages and no extra request. A change reaches your visitors within about 5 minutes, the time a browser may keep the settings it already loaded.
- The shop check writes the setting each time it finishes with a result. You can change it afterwards; your change stands until you run a new check.
- Turning it on asks you to confirm, because a site that also sends events with AxiTrace tracking calls or runs an AxiTrace plugin would count purchases and leads twice.
- The installation snippet on the Manual connection page shows the current state as a
comment line, for example
// dataLayer auto-tracking: ON (dataLayer) - managed in Workspace settings. The snippet never switches it on or off itself.
The page option
The SDK also accepts autoTrackDataLayer and dataLayerName as options in
Axitrace.init(). They combine with the Workspace setting like this:
| Page code | Workspace setting | Result |
|---|---|---|
No option, or autoTrackDataLayer: false | On | On |
No option, or autoTrackDataLayer: false | Off | Off |
autoTrackDataLayer: true | On or off | On |
A page can switch auto-tracking on, but never off. If your page code passes
autoTrackDataLayer: true, auto-tracking stays on even when the Workspace switch is off. To
manage it from the admin panel only, remove that option from your page.
A custom dataLayer name
Google Tag Manager can be installed with a renamed dataLayer (the fourth argument of the GTM snippet). Put
that name into the Workspace card, or leave it empty for the default dataLayer. A name set in
the page code wins over the Workspace name, unless the page names the default dataLayer. A
name must be a plain JavaScript identifier of at most 64 characters: letters, digits, _ and
$, not starting with a digit.
When the settings cannot be loaded
The SDK starts only after it has received your workspace settings, because those settings also decide consent and Do Not Track behaviour. It waits up to 2 seconds per attempt and retries three times, after 5, 15 and 45 seconds. Pushes made in the meantime stay in the dataLayer and are read as soon as the settings arrive. If all attempts fail, the SDK stays off for that page view: nothing is tracked, dataLayer or not.
6. Rules against double counting
Auto-tracking adds a second way for the same action to reach AxiTrace. These rules keep one order one order.
Use one source per event
Never switch on auto-tracking on a site that also sends the same events with AxiTrace tracking calls
(Axitrace._instance.track(...)) or runs an AxiTrace plugin for WooCommerce, Shopify, Magento
or Shopware. The plugins already send those events, and more reliably, because they capture the order on
the server. The shop check detects both and answers "Already tracked".
One overlap is handled for you: when the SDK's own form tracking records a form submission and the
dataLayer pushes generate_lead for the same submission, a second lead within 3 seconds of the
first is skipped.
GA4 connections
If you forward events to GA4 through AxiTrace, remember that your shop's own GA4 tag already sends the events AxiTrace just read from the same dataLayer, and GA4 does not deduplicate. Each GA4 connection therefore has a switch, Forward browser-bridged dataLayer events. Turn it on only if your own GA4 tag does not already send these events; otherwise GA4 counts them twice. It is off for GA4 connections created from now on, and on for connections that already existed, so their behaviour does not change.
Every dataLayer event that is forwarded to GA4 carries the event parameter
axitrace_source with the value datalayer_bridge, so you can find or exclude these
events in your GA4 reports.
One order, one conversion on every ad platform
The same order can reach AxiTrace from more than one place: the dataLayer on the confirmation page, a
plugin, a webhook, your backend. AxiTrace keeps every copy for reporting, but it forwards only the
first purchase with a given order id to your ad platforms. Any other purchase event with
the same order id in the same workspace within 30 days is not forwarded, whatever its source. Order ids
are compared without case, surrounding spaces or a leading #, so #1041 and
1041 are the same order.
Meta, TikTok, Pinterest and Microsoft Ads also pair the browser and server copy of each event by a shared event id. GA4 and Google Ads do not, which is why the order rule above matters.
7. Troubleshooting
The shop check found nothing, but I know my shop has a dataLayer
The check reads the pushes written into your pages and the tags in your public tag container. Pushes made by a separate script file, by a theme app, or only after a click are invisible to it. If the console check shows GA4 ecommerce pushes, switch auto-tracking on yourself in Workspace settings → Data collection. The check never overrides your change until you run it again.
Auto-tracking is on, but no ecommerce events arrive
Open a product page with the browser console open. A warning that the dataLayer "is not an array" means the page stores something else under that name. Otherwise check that the dataLayer name in Workspace settings matches the fourth argument of your GTM snippet, and that the pushes use the GA4 event names in the table above. If your workspace requires cookie consent, nothing is sent until the visitor grants it.
I switched auto-tracking off, but events still arrive from the dataLayer
Your page code probably passes autoTrackDataLayer: true to Axitrace.init().
A page can switch auto-tracking on but never off. Remove the option from the page. Also allow a few
minutes: a browser may keep the workspace settings it already loaded for up to 5 minutes.
Orders appear twice in AxiTrace
Look at the source of the two rows in the Orders list (Orders & journeys). Two sources for one order (the dataLayer
and a plugin, or the dataLayer and AxiTrace tracking calls) means auto-tracking should be off, see
rules against double counting. Your ad platforms still receive the
order once. Two rows from the dataLayer usually means the purchase push has no
transaction_id, or a placeholder such as (not set); the browser console
says which.
Purchases arrive without the buyer's email or address
The shop check says it was blocked
Your shop's bot protection stopped the check, and the check does not try to get around it. Tracking itself is not affected, because the SDK runs in your visitors' browsers. If your shop has a GA4 ecommerce dataLayer, switch auto-tracking on in Workspace settings → Data collection.
GA4 shows every ecommerce event twice
Turn off Forward browser-bridged dataLayer events on your GA4 connection. Your own
GA4 tag already sends these events. The duplicates in GA4 carry
axitrace_source = datalayer_bridge.
Need Help?
If you have any questions or need assistance:
- Email us at info@axitrace.com - we typically reply within one business day.
- See Install with Google Tag Manager to load AxiTrace through GTM.
- See the JavaScript SDK reference for every option the SDK accepts.