Install with Google Tag Manager
If your shop already runs Google Tag Manager and already pushes a GA4 ecommerce dataLayer, you can go
live with AxiTrace without touching a single line of shop code. One Custom HTML tag loads the AxiTrace
loader, one switch in Workspace settings tells it to read your dataLayer, and your existing
view_item, add_to_cart, begin_checkout and purchase
pushes become AxiTrace events automatically. The full reference for reading the dataLayer is
GA4 dataLayer Auto-Tracking.
- Who this page is for
- The one prerequisite: a GA4 ecommerce dataLayer
- Path 1 (recommended): load AxiTrace and let it read the dataLayer
- What gets mapped, and what does not
- Path 2: no dataLayer, fire events from GTM tags
- What you still do not get from a browser-only install
- Verify the install
- Troubleshooting
- A note on server-side GTM
Who this page is for
You run a custom shop, a headless storefront, or any site that has no AxiTrace plugin. Your two documented options so far were to hand-write JavaScript (JavaScript SDK) or to send events from your own backend (HTTP API). This page is the third option: install through Google Tag Manager, which is what most shops in this position already have.
If you run WooCommerce, Shopify, Magento, Shopware or BigCommerce, do not use this page. Install the plugin for your platform instead: it captures the order server-side, which is strictly better than anything a browser can do. See the platform integration guides.
The one prerequisite: a GA4 ecommerce dataLayer
Everything on this page depends on a single question: does your site already push GA4 ecommerce events, or the classic Google Analytics ecommerce transaction, into a dataLayer? If it does, the install below takes about ten minutes. If it does not, someone still has to write those pushes into your shop's templates, and Google Tag Manager does not remove that work, it moves it. Be clear about which of the two situations you are in before you start.
Check your own dataLayer in 30 seconds
Open a product page on your site, open the browser devtools console, and paste this. The first part prints what the page has pushed already; the second part prints every new push live, so you can click Add to cart and watch what happens. Then do the same on the order confirmation page after a test order: on many shops that is the only page where the order data appears.
// 1. What this page has already pushed.
(function () {
var dl = window.dataLayer;
if (!Array.isArray(dl)) {
console.warn('No dataLayer array on this page. Nothing to map.');
return;
}
console.log('dataLayer entries:', dl.length);
dl.forEach(function (entry, i) {
if (entry && entry.transactionId) {
console.log(i, 'classic ecommerce transaction', entry);
} else if (entry && entry.event) {
console.log(i, entry.event, entry.ecommerce || '(no ecommerce object)');
}
});
})();
// 2. Print every future push. Now click "Add to cart" and watch the console.
(function () {
var dl = window.dataLayer;
if (!Array.isArray(dl)) { return; }
var originalPush = dl.push;
dl.push = function () {
try { console.log('PUSH', arguments[0]); } catch (e) {}
return originalPush.apply(dl, arguments);
};
console.log('Watching dataLayer pushes. Reload the page to stop.');
})();
Reading the result
| What you see | What it means |
|---|---|
Entries with event: "view_item", "add_to_cart",
"begin_checkout", "purchase", each with an
ecommerce object |
You have a GA4 ecommerce dataLayer. Go to Path 1 and you are done in ten minutes. |
A dataLayer exists, but the only entries are gtm.js,
gtm.dom, gtm.load and similar |
That is Google Tag Manager talking to itself. There is no ecommerce data in it. Check the order confirmation page too (the classic ecommerce row). If there is none there either, add the pushes to your shop's templates, or use Path 2. |
Entries with an ecommerce object but names like productClick or
eec.purchase |
That is the older Universal Analytics enhanced ecommerce shape. AxiTrace translates its
purchase, checkout, add and detail blocks to
the GA4 events; other Universal Analytics actions are not read. See
what is read. |
On the order confirmation page, a classic ecommerce transaction entry with
transactionId, transactionTotal and transactionProducts
(often with no event, so it only shows up in this check) |
That is the classic Google Analytics ecommerce format. AxiTrace reads it as a purchase as it is, with no variables to map, so Path 1 works and the orders arrive. Set your workspace's default currency in Workspace settings, because this format carries none. See classic ecommerce transactions. |
No dataLayer array on this page |
Nothing to map. Someone has to write the pushes, or use Path 2. |
A tag manager is a place to put tags. It is not a source of data. If your product pages do not publish the SKU, the price and the currency somewhere a script can read them, no configuration inside GTM will invent them. The work of emitting that data is the same work whether it ends up in a dataLayer, in a plugin, or in a hand-written SDK call - the only thing that changes is who does it and where it lives.
Create the Custom HTML tag
In your web Google Tag Manager container, go to Tags → New, choose
Custom HTML, and paste the snippet below. Replace axi.yourdomain.com with
your own tracking domain and pk_live_... with your workspace Public Key (both are shown in
the AxiTrace admin panel).
<script src="https://axi.yourdomain.com/axitrack.js"></script>
<script>
Axitrace.init({
publicKey: 'pk_live_...',
apiUrl: 'https://axi.yourdomain.com'
});
</script>
The options, and what the Workspace setting does instead
| Option | Required | Default | What it does |
|---|---|---|---|
publicKey |
Yes | none | Your workspace Public Key. Starts with pk_live_. It is a public value and is meant
to sit in page source. |
apiUrl |
Yes | none | Your first-party tracking domain, the one you connected and verified in AxiTrace. See Domain Configuration. |
autoTrackDataLayer |
No | false |
Leave it out. Reading the dataLayer is switched on in Workspace settings (see below). A page that
passes true keeps it on regardless of the Workspace switch, so the switch can no
longer turn it off. With auto-tracking off, the SDK tracks page views, form submissions and
engagement; ecommerce events then need either the dataLayer auto-tracking or explicit tracking
calls. |
dataLayerName |
No | 'dataLayer' |
Only needed if your GTM container was installed with a renamed dataLayer, that is if your GTM
snippet ends with something other than 'dataLayer' as its fourth argument. The
Workspace setting has a field for the name too; a non-default name set here wins. |
The JavaScript SDK page documents many more initialization options (consent gating, cookie domain, session length, pixel bridging). They all work here too. The two required above are the only ones this install method needs.
Switch on dataLayer auto-tracking
In the AxiTrace admin panel, open Workspace settings, the Data collection section, and turn on GA4 dataLayer auto-tracking. You may find it already on: the AI shop check on the Manual connection page looks for a GA4 ecommerce dataLayer on your shop and sets this switch for you. The SDK picks the setting up by itself, so the tag above never has to change. See the Workspace setting for the details.
Fire it on Initialization, All Pages
Give the tag the built-in Initialization - All Pages trigger, not All
Pages. Initialization triggers fire before every other tag on the page, which is what you want for
a loader: the SDK has to be listening before your shop pushes view_item, or the first event of
the page is the one you lose.
Leave Tag firing options at Once per page and leave Support document.write unchecked. Name the tag something like AxiTrace Loader and save.
The SDK reads the entries already sitting in the dataLayer when it starts, and then watches for new
ones. A shop that prints its view_item push high in the <head>, before
GTM has finished loading, is still tracked correctly.
Publish and check the order of your pixels
Submit the container. If your Meta Pixel and TikTok Pixel are also managed by this GTM container, give
them the same Initialization - All Pages trigger so they are present as early as
possible. AxiTrace calls fbq() and ttq() with its own event ID so the browser
and server copies of each event deduplicate, and since SDK 0.7.6 those calls are queued and replayed for
up to 30 seconds if the pixel is not loaded yet. This pairing on a shared event ID is done by Meta,
TikTok, Pinterest and Microsoft Ads. GA4 does not deduplicate at all: if your own GA4 tag already sends
these events, keep Forward browser-bridged dataLayer events off on your AxiTrace GA4
connection, or GA4 counts them twice. A pixel that arrives late is still bridged - loading it
early just makes the browser event fire sooner.
Install your Meta and TikTok pixels init-only: no fbq('track', 'PageView')
and no ttq.page(). AxiTrace fires those itself, with the shared event ID that makes
deduplication work. Leaving the manual calls in place double-counts every page view. See
Pixel Deduplication.
What gets mapped, and what does not
With dataLayer auto-tracking on, these GA4 event names become AxiTrace events. Nothing else on the page
has to change: the SDK reads the ecommerce object of each push and fills in the AxiTrace
event properties from it.
| GA4 dataLayer event | AxiTrace event |
|---|---|
view_item | product.view |
view_item_list | view_item_list |
select_item | select_item |
add_to_cart | product.addToCart |
remove_from_cart | remove_from_cart |
begin_checkout | begin_checkout |
add_shipping_info | add_shipping_info |
add_payment_info | add_payment_info |
purchase | purchase |
search | search |
generate_lead | form.submit |
purchase is AxiTrace's accepted alias for transaction.charge, which is the name
you will see in the Events Reference and in your error log. They
are the same event.
view_item_list, select_item, remove_from_cart and
add_shipping_info were added to this list on 19 September 2026. Before that they
were ignored, which meant a shop with a complete GA4 ecommerce dataLayer got seven of the
eleven events it was already publishing. If you built a workaround for that - a backend call,
or a custom event - you can remove it: keep only one source per event, or the same action is
counted twice.
Not covered yet
These GA4 events are not mapped. They are ignored, silently and harmlessly - no error, no partial event:
view_cartrefund
Do not try to send these from a GTM tag by hand. The browser endpoint has no handler for
them, so the call is answered 400 unsupported event and the attempt is recorded
in your workspace's error log. Unlike the four above, these two have no server-side endpoint
either, so there is nothing a backend integration can do about them yet.
purchase is deduplicated on transaction_id, per workspace, within 30 days:
two pushes carrying the same transaction_id are one order, and the repeat is dropped. A
buyer who reloads the order confirmation page, or a tag that fires twice, does not double-count revenue
in AxiTrace. Your ad platforms receive only the first purchase with a given order id, whatever source it
came from.
This depends on transaction_id actually being present in the push. A
purchase event without one cannot be recognised as a repeat of anything. Check for it in
the console output from the dataLayer check above before you trust this.
AxiTrace remembers a purchase for 30 days, so this holds for a confirmation page re-opened from
browser history days later, not only for an immediate reload. Ordinary browser events such as
product.view keep a much shorter window on purpose: a visitor who opens the same product
page again tomorrow really is a new view.
Path 2: no dataLayer, fire events from GTM tags
If the check above found no ecommerce data, you can still build events inside GTM out of triggers and
variables. Keep the loader tag from Path 1 without autoTrackDataLayer, keep
GA4 dataLayer auto-tracking off in Workspace settings (the shop check leaves it off when
it finds no GA4 ecommerce dataLayer), and add one Custom HTML tag per event. Never combine the two: events
sent by hand and the same events read from the dataLayer are counted twice.
A DOM Element variable is a CSS selector pointed at your own markup. The day a theme update renames a
class, moves the price into a different element, or changes 49.99 USD to
USD 49.99, the variable returns the wrong value or nothing at all - and nothing warns
you. Your tag keeps firing, your conversions keep arriving, and the amounts are quietly wrong.
Treat Path 2 as a bridge, not a destination. A real dataLayer, a plugin, or a server-side integration all survive a redesign; a selector does not.
Create the variables
Under Variables → User-Defined Variables, create one DOM Element variable per value you need. For an add-to-cart event that is typically the SKU, the name and the price:
| Variable name | Type | Selection method | Example selector |
|---|---|---|---|
| DOM - Product SKU | DOM Element | CSS Selector | [data-product-sku], attribute data-product-sku |
| DOM - Product Name | DOM Element | CSS Selector | h1.product-title |
| DOM - Product Price | DOM Element | CSS Selector | [data-price], attribute data-price |
Prefer a data- attribute over element text wherever your templates offer one. An attribute
holds a clean machine value (49.99); element text holds whatever the page displays
($49.99, or 49,99 EUR), and you would have to clean it up in JavaScript before
sending it.
Create the trigger
Under Triggers → New, choose Click - All Elements, set it to fire on Some Clicks, and add the condition Click Classes contains add-to-cart (substitute whatever class or ID your own button carries). Name it Click - Add to Cart.
Create the tag
Add a Custom HTML tag with the snippet below and give it the trigger from Sub-step 2. The double-brace names are GTM variable references: GTM substitutes the value at fire time.
<script>
(function () {
// The loader tag fires on Initialization, so the SDK is normally ready by
// the time a visitor clicks. Guard anyway: a click in the first few hundred
// milliseconds of a slow page should be skipped, not throw.
if (!window.Axitrace || !window.Axitrace._instance) { return; }
var price = parseFloat(String({{DOM - Product Price}}).replace(',', '.'));
window.Axitrace._instance.track('product.addToCart', {
content_id: {{DOM - Product SKU}},
content_name: {{DOM - Product Name}},
content_type: 'product',
sku: {{DOM - Product SKU}},
name: {{DOM - Product Name}},
price: price,
quantity: 1,
currency: 'EUR',
value: price
});
})();
</script>
Repeat the pattern for the other events you need, using the names from the mapping table and the property shapes documented under E-commerce Tracking and in the Events Reference. Hard-code the currency only if your shop sells in one; otherwise it needs its own variable.
What you still do not get from a browser-only install
This is worth reading before you decide the install is finished. A GTM install is a browser install, and a browser install has three limits that no amount of GTM configuration removes.
1. The purchase exists only if the browser reaches the confirmation page
A purchase event is a tag firing on a page. If that page never loads, the event never
happens and the order is simply absent from AxiTrace and from every ad platform you forward to. That
covers more real orders than most people expect:
- the payment gateway takes the buyer to its own domain and the return redirect fails, times out, or lands on an error page;
- the buyer closes the tab on the bank's 3-D Secure screen after the money has already moved;
- the order is placed by phone, by email, in a physical shop, or entered by your staff in the back office;
- the buyer's connection drops between the payment confirmation and your thank-you page.
Each of those is a paid conversion your ad platforms will never learn about, and they optimise on what they learn about.
2. Match keys are only as good as what the page exposes
Meta and TikTok score every conversion on how many buyer identifiers arrive with it: email, phone, first
name, last name, city, postcode and country. That score is Event Match Quality, and it directly affects
how well their algorithms can attribute and optimise. A typical GA4 purchase push carries the
order value, the currency, the transaction id and the items - and nothing at all about the buyer, because
GA4 never needed it.
What the page does not print, the browser cannot send. Your Event Match Quality is capped at exactly what your confirmation page happens to expose, which is usually far below what your order record already contains.
You can raise that cap by adding GA4 user_data to your pushes: plain-text
email, phone_number and an address with first name, last name,
city, region, postcode and country. AxiTrace reads it and hashes it itself before sending. Already hashed
values and sha256_* keys cannot be used. See
customer details from user_data.
3. There is no retry
The browser gets one attempt. If the request is blocked by an extension, cut off by a closing tab, or lost to a dropped connection, that conversion is gone and nothing notices. A server that owns the order can retry until the event is accepted, and AxiTrace's server-side path does exactly that.
Keep this GTM install for the upper funnel. view_item, add_to_cart and
begin_checkout are high-volume, they need the visitor's cookies and ad click IDs, and the
browser is genuinely the best place to capture them.
Then send the purchase from your backend, from the same code path that already marks the order as paid, using the HTTP API. That copy is authoritative: it exists even when no browser ever reached a confirmation page, it carries the email, phone and address you already hold, and it is retried until it is accepted.
When both paths cover the same order, give the two copies the same event ID: set the
HTTP API's eventSalt to a value derived from your order ID, and keep it stable across
retries. AxiTrace's 30-day purchase window covers both paths, so the second copy is recognised as the
same order rather than counted as a second sale. This is exactly how the platform plugins run a
browser pixel and a server-side webhook side by side.
Verify the install
GTM Preview
Click Preview in GTM and browse your own shop: a product page, an add to cart, and a test order if you can place one. In the Tag Assistant panel, confirm that your AxiTrace Loader tag appears under Tags Fired on the very first Initialization event of every page, and that it fires once, not repeatedly.
The network tab
With devtools open, filter network requests by your tracking domain. You should see
axitrack.js load once, then one request per event. Each should return
200.
AxiTrace answers 200 to requests it deliberately drops, so that bot detection never leaks back to the client. A headless browser, an automated test runner, or a known crawler gets a clean 200 and no stored event. Always confirm in the admin panel, never in the network tab alone. See Testing with curl (Always 200).
The AxiTrace admin panel
Open your workspace and check, in this order:
-
The dashboard. Page views appear within seconds. Conversion events
(
product.addToCart,begin_checkout,transaction.charge) are held in the queue for 10 seconds on purpose, so the visitor's profile can pick upfbpandfbcfirst. Wait 15 seconds before deciding an event is missing. - Event feed → Errors. This is where a rejected event explains itself. A validation failure, a wrong public key, an origin that is not allowed - each one is recorded here with a reason. An empty Errors tab and an empty Overview together mean the request never arrived at all.
-
The Orders list (Orders & journeys). A test order should appear with the right amount, currency and
transaction id. This is also where you see whether your
purchasepush carried the buyer details, or only the order total.
Troubleshooting
The loader tag fires, but no events arrive
Check that GA4 dataLayer auto-tracking is on in Workspace settings, in the Data collection section. With it off, the SDK tracks page views, form submissions and engagement, and maps nothing from the dataLayer. Then re-run the dataLayer check on a live page: if the pushes use event names that are not in the mapping table, nothing matches and nothing is sent.
Page views work, ecommerce events do not
That is the signature of a loader that is installed correctly and a dataLayer the SDK cannot read.
The most common causes are a renamed dataLayer (enter the name from your GTM snippet's fourth
argument in the dataLayer name field of the Workspace setting), and pushes whose ecommerce object is missing or empty.
Watch a real push with part 2 of the console snippet and compare it
against the GA4 shape.
Every event is counted twice
Three things cause this. The loader tag has All Pages as well as
Initialization - All Pages, so it runs twice. Or your Meta and TikTok pixels still
make their own fbq('track', 'PageView') and ttq.page() calls, which
AxiTrace also fires with a shared event ID - remove the manual ones. Or your shop pushes the same
GA4 event twice, which the console watcher will show you directly.
Events are missing for visitors who came from Facebook or Instagram
The Facebook in-app browser closes aggressively, and a tag that runs late can be cut off before its
request leaves. GTM always loads asynchronously, so this install method is a little more exposed to
it than a synchronous SDK tag in the <head>. If a meaningful share of your paid
traffic comes from Meta placements, read
Facebook In-App Browser and
consider loading the SDK directly in your template instead of through GTM.
Nothing is sent at all, and the console mentions consent
If your workspace has "Respect cookie consent" enabled, or your GTM container blocks the tag until a consent state is granted, AxiTrace stays silent until marketing consent arrives - no cookie, no request. That is working as intended. See Cookie Consent for how to wire your consent tool to it.
A note on server-side GTM
Nothing on this page needs server-side GTM. Your AxiTrace tracking domain is already a first-party, server-side endpoint: events from the browser go to your own domain and are forwarded to Meta, TikTok, Google Ads and GA4 from AxiTrace's servers, not from the visitor's browser. That is the benefit people usually buy sGTM for, and you have it the moment the loader above is live.
Separately, AxiTrace can host a managed Google server-side GTM container for you on Business and Custom plans, on that same domain, with no Cloud Run bill of your own. It is useful if you want your Google tags to run server-side too, or want one place to inspect and reshape outbound tag traffic. It is not a prerequisite for anything here, and it does not improve AxiTrace's own tracking. See Server-Side GTM Hosting if that is what you are after.
Need Help?
If you have any questions or need assistance:
- Email us at info@axitrace.com - we typically reply within one business day.
- See the JavaScript SDK reference for every option the loader accepts.
- See the HTTP API reference to add the server-side purchase described above.