Loading tutorials…
Loading tutorials…
Out-of-the-box Magento ships Google Analytics with the legacy Universal Analytics integration — which Google killed in 2023. If your store is reporting traffic but no purchases (or purchases with no items array), you've inherited the broken setup. Here's the correct GA4 + GTM + Enhanced Ecommerce stack.
Who this is forMagento operators who need real e-commerce analytics — accurate revenue, product-level performance, funnel drop-off, and refund tracking. Especially relevant if you are running paid ads and need GA4 conversion data feeding back into Google Ads.
What you'll need
Step 1
Stores → Configuration → Sales → Google API → Google Analytics. The built-in module fires UA events. Disable it before installing GA4 — running both creates duplicate counts.
Open Stores → Configuration → Sales → Google API → Google Analytics. Set 'Enable' = No. Save.
Run `bin/magento module:disable Magento_GoogleAnalytics` then `bin/magento setup:upgrade` then `bin/magento cache:flush` to fully remove the legacy module. It will not actively send events while disabled in admin, but disabling the module saves a few KB of JS per pageview.
If you have a Google Tag Manager module installed (Stores → Configuration → Sales → Google API → Google Tag Manager), DO use that — but verify it loads only ONE GTM container. Multiple GTM containers double-count everything.
Step 2
Three valid paths: paid extension (Weltpixel, Mageplaza Premium), Google Tag Manager + custom dataLayer, or direct gtag.js in custom theme code. GTM + dataLayer is the recommended path for most stores.
Option A — paid extension (Weltpixel GA4, $99-249 one-time). Fastest path. Installs via Composer, adds an admin config panel, fires every Enhanced Ecommerce event automatically. Best if your team is non-technical.
Option B — Google Tag Manager with custom dataLayer (recommended for most). You install GTM in the head/body via a small custom module or theme tweak, push dataLayer events at each catalog action (view_item, add_to_cart, purchase), and configure GA4 tags in GTM to consume them. Most flexible. ~6-8 hours of work.
Option C — direct gtag.js in theme files. Avoid. Hardcoded gtag is brittle, breaks on theme updates, and cannot be A/B tested. Only justified for sites that explicitly cannot use GTM (some EU consent-mode setups).
Recommendation: Option B (GTM + dataLayer). This tutorial walks through that path. Path A is mostly Composer install + admin config and not worth tutorial space.
Step 3
GTM needs its snippet in the <head> (and a noscript in <body>). For Magento, the cleanest path is a small custom module that injects both via layout XML.
Easiest path for non-developers: edit `app/design/frontend/[Vendor]/[theme]/Magento_Theme/templates/html/header.phtml` to add the GTM head snippet at the top. Replace `GTM-XXXXXX` with your container ID.
Cleaner path: create a small custom module `Vendor_GTM` with `etc/frontend/di.xml` injecting the snippet via the head layout. This survives theme changes.
Even cleaner if budget allows: install `magefan/module-google-tag-manager` (free) or `weltpixel/module-googletagmanager`. Both expose admin config for GTM container ID.
Verify GTM is loading: open the storefront in Chrome → DevTools → Network → filter 'gtm.js'. You should see one request to `googletagmanager.com/gtm.js?id=GTM-XXXXXX` returning 200.
Install the Tag Assistant Chrome extension. Navigate to your storefront → click the extension → confirm GTM container is detected and "Connected."
Step 4
GTM cannot send events to GA4 if your theme does not push dataLayer.push({event: 'view_item', ecommerce: {...}}) at the right moments. This is the bulk of the work.
Map every required e-commerce event to a Magento page/action: `view_item` (product page), `view_item_list` (category page), `add_to_cart` (when add-to-cart succeeds), `remove_from_cart`, `begin_checkout` (first checkout step), `add_payment_info`, `add_shipping_info`, `purchase` (order success page).
For `view_item` on product page: edit `app/design/frontend/[Vendor]/[theme]/Magento_Catalog/templates/product/view.phtml` and inject `<script>dataLayer.push({event:'view_item', ecommerce:{currency:'<?= $currentCurrency ?>', value: <?= $product->getFinalPrice() ?>, items:[{item_id:'<?= $product->getSku() ?>', item_name:'<?= $product->getName() ?>', price: <?= $product->getFinalPrice() ?>}]}});</script>`.
For `add_to_cart`: subscribe to the JS event Magento dispatches when add-to-cart fires. The cleanest path is a small RequireJS mixin on `Magento_Catalog/js/catalog-add-to-cart` that pushes to dataLayer on success.
For `purchase`: the order success page is `app/design/frontend/[Vendor]/[theme]/Magento_Checkout/templates/success.phtml`. Inject the purchase dataLayer push with order ID, total, tax, shipping, and items array. Use `$block->getOrder()` to access the order object.
Test each event in real time: open the storefront → DevTools Console → type `dataLayer` → walk through the funnel and confirm each event lands with the right ecommerce object.
Step 5
In GTM: create one GA4 Configuration tag + one GA4 Event tag per e-commerce event. Use dataLayer variables to pass the ecommerce object cleanly.
GTM → Variables → New → Data Layer Variable → name = `ecommerce`, data layer variable name = `ecommerce`, version = Version 2. Repeat for any other dataLayer keys you push.
Tags → New → GA4 Configuration tag. Measurement ID = G-XXXXXXX (from GA4 Admin → Data Streams → Web). Trigger = All Pages.
Tags → New → GA4 Event tag for `view_item`: Configuration Tag = your GA4 config, Event Name = `view_item`, More Settings → Ecommerce → Send Ecommerce data = Yes, Data Source = Data Layer. Trigger = Custom Event with name `view_item`.
Repeat the same pattern for `view_item_list`, `add_to_cart`, `begin_checkout`, `add_shipping_info`, `add_payment_info`, `purchase`. Eight tags total, eight triggers.
GTM → Preview → connect to your storefront URL. Walk the funnel. Confirm each tag fires in the right order with the right ecommerce data.
Submit + Publish the GTM container. Wait 5-10 minutes for the new container version to propagate.
Step 6
Run a real test purchase. Verify every event arrives in GA4 with correct product, value, and currency.
Open GA4 → Reports → Realtime → Events. This shows events arriving in real time.
Open an incognito window → land on your storefront → browse a category → click a product → add to cart → checkout with a test card → complete purchase.
In GA4 Realtime, you should see (in this order): `page_view`, `view_item_list`, `view_item`, `add_to_cart`, `begin_checkout`, `add_payment_info`, `add_shipping_info`, `purchase`. Each event row should show 1 event for your user.
Click `purchase` → inspect parameters. Confirm: `transaction_id` = your order number, `value` = order total, `currency` = correct code, `items` = array with sku, name, price for each line item.
Check GA4 → Reports → Monetization → Ecommerce purchases over 24-48 hours. Revenue should match Magento's Sales report within 1-3% (small variance from refunds / abandoned-after-add events is normal).
If revenue mismatches by more than 5%, the most common cause is missing `purchase` events on guest checkout (some themes break checkout success.phtml differently for guest vs logged-in).
Step 7
GA4 supports `refund` events that subtract from revenue reports. Use them or your revenue numbers drift permanently positive.
For every refund issued in Magento (Admin → Sales → Refunds), trigger a server-side `refund` event with the transaction_id and refund value. The cleanest path is a Magento observer on the `sales_order_creditmemo_save_after` event that calls the GA4 Measurement Protocol with the refund details.
GA4 Measurement Protocol setup: GA4 Admin → Data Streams → your stream → Measurement Protocol API secrets → Create. Save the secret. The endpoint is `https://www.google-analytics.com/mp/collect?measurement_id=G-XXX&api_secret=YYY`.
POST a JSON body with `{client_id: '<original session client_id>', events: [{name: 'refund', params: {transaction_id: 'M12345', currency: 'USD', value: 49.99, items: [...]}}]}`.
Link GA4 to Google Ads: GA4 Admin → Product Links → Google Ads links → Link. Enable conversion import + audience sharing. Then in Google Ads → Goals → Conversions → New → Import → GA4 → select `purchase` as a conversion action.
Wait 24-48 hours and verify Google Ads shows Purchase conversions from GA4 (status = Recording, recent count > 0).
Common mistakes
Leaving the legacy Magento Google Analytics module enabled
What goes wrong: The old module fires Universal Analytics events that Google ignores (UA was sunset July 2023). Worse, if you also installed GA4 via GTM, the legacy module still injects gtag.js for UA, doubling page-view events in some configurations.
How to avoid: Stores → Configuration → Sales → Google API → Google Analytics → Enable = No. Optionally disable the entire module via `bin/magento module:disable Magento_GoogleAnalytics`.
Missing `currency` in ecommerce dataLayer pushes
What goes wrong: GA4 silently rejects e-commerce events without currency. You see `view_item` in Realtime but no Monetization data 24 hours later. Common pattern: developer hardcodes currency only on the purchase event and forgets the funnel events.
How to avoid: Include `currency` in every ecommerce object. Use the store currency code dynamically, not a hardcoded "USD."
Purchase event firing twice on success page reloads
What goes wrong: Customers refresh the success page, fire `purchase` twice, doubled revenue lands in GA4. Bid strategies optimize toward inflated targets. Conversion value reports look great until reality (Magento's Sales report) reveals the gap.
How to avoid: Either fire `purchase` only on first load (check via sessionStorage flag) or use the order ID as a deduplication key in GA4 (`transaction_id` is automatically deduplicated by GA4 within the same property if the event arrives twice with the same transaction_id).
No refund tracking
What goes wrong: Magento processes refunds; GA4 has no idea. Revenue reports drift permanently optimistic. ROAS calculations are inflated. When CFO compares GA4 to Magento Sales reports, the gap widens monthly.
How to avoid: Set up the Measurement Protocol observer on `sales_order_creditmemo_save_after` to fire `refund` events. Test with a small refund and confirm GA4 Monetization → Ecommerce purchases shows the negative entry.
GTM container ID hardcoded in production theme code
What goes wrong: The dev hardcoded GTM-XXXX in header.phtml. When you stage a test container or deploy to staging, it still sends to production GA4. Test events pollute live data. Audiences and conversion bidding learn from junk.
How to avoid: Use a Magento system config field for the GTM container ID. Store production container ID in production env, staging container in staging env. Read via `Magento\Framework\App\Config\ScopeConfigInterface`.
Skipping GTM Preview mode validation
What goes wrong: Tags appear correctly configured in GTM. They fire in production. But the dataLayer object structure is slightly wrong — `items` is missing or item_id is mapped to product entity_id instead of SKU. GA4 receives events but Monetization reports show $0 revenue.
How to avoid: Always use GTM Preview mode + GA4 DebugView before publishing. Walk a real funnel. Inspect every event payload field by field.
Recap
Done — what's next
How to install Meta Pixel on Magento 2 with CAPI
Read the next tutorial
Hand it off
GA4 on Magento is one of the most common 'it's installed but the data is wrong' problems. Even technical operators ship it half-broken. A vetted Magento specialist with GA4 experience can install, validate, and link to Google Ads in 1-2 weeks at $14-16/hr — typically $600-1,200 total. The downstream value (clean bid optimization, accurate revenue, working audiences) usually justifies the cost within 30 days.
See specialist rates
Yes — for standard stores, a paid extension handles 90% of what's in this tutorial. Budget $99-249 one-time. The downside: customization is limited to whatever the extension supports. If you have custom checkout flows, post-purchase upsells, or non-standard product types, you'll still need custom dataLayer work on top.
Almost always one of three: (1) `currency` missing from the purchase event, (2) `items` array malformed (missing item_id or price), (3) `value` is a string instead of a number. Use GA4 DebugView to inspect the raw event payload.
Client-side via GTM covers 95% of e-commerce tracking needs. Server-side becomes mandatory for refund tracking (covered above) and useful for high-iOS / Safari-ITP traffic where browser pixels are blocked. For most Magento stores under $5M revenue, client-side + refund-via-Measurement-Protocol is the right stack.
GA4 uses client_id (anonymous, cookie-based) for both. If you set a user_id via dataLayer push when a customer logs in, GA4 can stitch sessions across devices. For guest checkout, you only get the client_id — no cross-device stitching, but conversion tracking works the same.
GA4 tracks whatever value you push. For B2B with customer-group-specific prices, push the actual purchase price for that customer (not list price). The simplest implementation reads the order total from `$order->getGrandTotal()` and the per-item price from each `$item->getPrice()` — both already reflect negotiated rates.
Magento's one-page checkout makes step events tricky — `add_shipping_info` and `add_payment_info` fire at the same DOM point. Subscribe to the Knockout.js step-change events (`stepData.subscribe`) and push dataLayer events at each transition. The default `begin_checkout` event on success of the checkout page load is usually fine.
Adobe Commerce
iOS 14.5+ ate roughly 30-50% of browser-pixel Meta attribution. Without server-side Conversions API, your Meta Ads campaigns are optimizing against half-blind data. Here's the full Meta Pixel + CAPI installation on Magento 2 — with the deduplication piece most tutorials skip.
Adobe Commerce
Magento is the most powerful e-commerce platform you can run — and the easiest to misconfigure on day one. Most DIY installs skip Elasticsearch, run on shared hosting, and ship with /admin still publicly addressable. Here's the install path that won't haunt you in month three.
Google Analytics 4
Ecommerce tracking in GA4 isn't optional — it's the foundation of every revenue report, ROAS calculation, and ad-platform optimization decision. Done right it pays back forever. Done wrong it produces numbers that everyone in the org argues about for months.
Google Analytics 4
Linking these two correctly is what makes Google's stack actually function as a stack. Done wrong, you'll have two systems that disagree about reality for years. Here's the right setup, in order.