GA4 Event Tracking for Magento | Blue Frog Docs

GA4 Event Tracking for Magento

Implement Google Analytics 4 event tracking for Magento 2, including product views, add to cart, checkout, and custom eCommerce events.

GA4 Event Tracking for Magento

Implement comprehensive Google Analytics 4 event tracking on your Magento 2 store to monitor user interactions, product engagement, and conversion funnel behavior. This guide covers standard eCommerce events and Magento-specific implementations.


GA4 Event Architecture

Event Types

1. Automatically Collected Events

  • page_view - Automatically tracked with basic GA4 setup
  • session_start - First event in a session
  • first_visit - User's first visit to site

2. Enhanced Measurement Events (Enable in GA4 Admin)

  • scroll - 90% page scroll
  • click - Outbound link clicks
  • file_download - File downloads
  • video_start, video_progress, video_complete - Video engagement

3. Recommended eCommerce Events (Implement manually)

  • view_item_list - Product listing views
  • select_item - Product click from list
  • view_item - Product detail page view
  • add_to_cart - Add product to cart
  • remove_from_cart - Remove from cart
  • view_cart - Cart page view
  • begin_checkout - Checkout initiated
  • add_shipping_info - Shipping info added
  • add_payment_info - Payment info added
  • purchase - Transaction completed

Product Events Implementation

View Item List Event

Track when users view product category pages.

Observer Method

File: app/code/YourCompany/Analytics/Observer/ViewItemList.php

<?php
namespace YourCompany\Analytics\Observer;

use Magento\Framework\Event\Observer;
use Magento\Framework\Event\ObserverInterface;
use Magento\Framework\Registry;

class ViewItemList implements ObserverInterface
{
    protected $registry;

    public function __construct(Registry $registry)
    {
        $this->registry = $registry;
    }

    public function execute(Observer $observer)
    {
        $category = $this->registry->registry('current_category');
        $productCollection = $observer->getCollection();

        if ($category && $productCollection) {
            $items = [];
            $index = 1;

            foreach ($productCollection as $product) {
                $items[] = [
                    'item_id' => $product->getSku(),
                    'item_name' => $product->getName(),
                    'item_category' => $category->getName(),
                    'price' => (float)$product->getFinalPrice(),
                    'index' => $index++
                ];
            }

            $this->registry->register('ga4_view_item_list', [
                'event' => 'view_item_list',
                'item_list_name' => $category->getName(),
                'items' => $items
            ]);
        }
    }
}

Register Observer:

File: app/code/YourCompany/Analytics/etc/frontend/events.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
    <event name="catalog_block_product_list_collection">
        <observer name="analytics_view_item_list"
                  instance="YourCompany\Analytics\Observer\ViewItemList"/>
    </event>
</config>

Template Implementation

File: app/code/YourCompany/Analytics/view/frontend/templates/category.phtml

<?php
/** @var \Magento\Framework\View\Element\Template $block */
$registry = $block->getRegistry();
$eventData = $registry->registry('ga4_view_item_list');

if ($eventData):
?>
<script>
    require(['jquery'], function($) {
        $(document).ready(function() {
            gtag('event', 'view_item_list', {
                'item_list_name': '<?= $block->escapeJs($eventData['item_list_name']) ?>',
                'items': <?= $block->escapeJs(json_encode($eventData['items'])) ?>
            });
        });
    });
</script>
<?php endif; ?>

View Item Event

Track product detail page views.

Block Class Method

File: app/code/YourCompany/Analytics/Block/ProductView.php

<?php
namespace YourCompany\Analytics\Block;

use Magento\Catalog\Block\Product\View as ProductView;
use Magento\Framework\Registry;
use Magento\Catalog\Model\Category;

class ProductViewAnalytics extends \Magento\Framework\View\Element\Template
{
    protected $registry;

    public function __construct(
        \Magento\Framework\View\Element\Template\Context $context,
        Registry $registry,
        array $data = []
    ) {
        $this->registry = $registry;
        parent::__construct($context, $data);
    }

    public function getProduct()
    {
        return $this->registry->registry('current_product');
    }

    public function getProductData()
    {
        $product = $this->getProduct();
        if (!$product) {
            return null;
        }

        $categories = $product->getCategoryIds();
        $categoryName = '';

        if (!empty($categories)) {
            $categoryId = reset($categories);
            $category = $this->_objectManager
                ->create(Category::class)
                ->load($categoryId);
            $categoryName = $category->getName();
        }

        return [
            'item_id' => $product->getSku(),
            'item_name' => $product->getName(),
            'item_category' => $categoryName,
            'price' => (float)$product->getFinalPrice(),
            'currency' => $this->_storeManager->getStore()->getCurrentCurrencyCode()
        ];
    }
}

Template

File: app/code/YourCompany/Analytics/view/frontend/templates/product-view.phtml

<?php
/** @var \YourCompany\Analytics\Block\ProductViewAnalytics $block */
$productData = $block->getProductData();
if ($productData):
?>
<script>
    require(['jquery'], function($) {
        $(document).ready(function() {
            gtag('event', 'view_item', {
                'currency': '<?= $block->escapeJs($productData['currency']) ?>',
                'value': <?= $productData['price'] ?>,
                'items': [{
                    'item_id': '<?= $block->escapeJs($productData['item_id']) ?>',
                    'item_name': '<?= $block->escapeJs($productData['item_name']) ?>',
                    'item_category': '<?= $block->escapeJs($productData['item_category']) ?>',
                    'price': <?= $productData['price'] ?>
                }]
            });
        });
    });
</script>
<?php endif; ?>

Add to Layout:

File: app/code/YourCompany/Analytics/view/frontend/layout/catalog_product_view.xml

<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceContainer name="content">
            <block class="YourCompany\Analytics\Block\ProductViewAnalytics"
                   name="analytics.product.view"
                   template="YourCompany_Analytics::product-view.phtml"
                   after="-"/>
        </referenceContainer>
    </body>
</page>

Cart Events Implementation

Add to Cart Event

Track when products are added to cart.

JavaScript Interceptor Method

File: app/code/YourCompany/Analytics/view/frontend/web/js/add-to-cart-tracker.js

define([
    'jquery',
    'Magento_Customer/js/customer-data'
], function($, customerData) {
    'use strict';

    return function(config, element) {
        $(element).on('click', function(event) {
            var form = $(this).closest('form');
            var productData = form.data('product-info');

            if (productData) {
                gtag('event', 'add_to_cart', {
                    'currency': productData.currency || 'USD',
                    'value': parseFloat(productData.price) || 0,
                    'items': [{
                        'item_id': productData.sku,
                        'item_name': productData.name,
                        'price': parseFloat(productData.price),
                        'quantity': parseInt($('[name="qty"]').val()) || 1
                    }]
                });
            }
        });
    };
});

Apply to Add to Cart Button

File: app/code/YourCompany/Analytics/view/frontend/templates/product-addtocart.phtml

<script type="text/x-magento-init">
{
    "#product-addtocart-button": {
        "YourCompany_Analytics/js/add-to-cart-tracker": {
            "productSku": "<?= $block->escapeJs($product->getSku()) ?>",
            "productName": "<?= $block->escapeJs($product->getName()) ?>",
            "productPrice": <?= $product->getFinalPrice() ?>
        }
    }
}
</script>

Alternative: AJAX Listener Method

For AJAX add to cart, use RequireJS mixins:

File: app/code/YourCompany/Analytics/view/frontend/requirejs-config.js

var config = {
    config: {
        mixins: {
            'Magento_Catalog/js/catalog-add-to-cart': {
                'YourCompany_Analytics/js/catalog-add-to-cart-mixin': true
            }
        }
    }
};

File: app/code/YourCompany/Analytics/view/frontend/web/js/catalog-add-to-cart-mixin.js

define([
    'jquery',
    'mage/utils/wrapper'
], function($, wrapper) {
    'use strict';

    return function(targetModule) {
        var submitForm = targetModule.prototype.submitForm;

        targetModule.prototype.submitForm = wrapper.wrap(submitForm, function(originalAction, form) {
            var formData = $(form).serializeArray();
            var qty = 1;
            var productSku = '';

            formData.forEach(function(item) {
                if (item.name === 'qty') {
                    qty = parseInt(item.value);
                }
                if (item.name === 'product') {
                    productSku = item.value;
                }
            });

            // Track add to cart event
            if (typeof gtag !== 'undefined') {
                gtag('event', 'add_to_cart', {
                    'items': [{
                        'item_id': productSku,
                        'quantity': qty
                    }]
                });
            }

            return originalAction(form);
        });

        return targetModule;
    };
});

Remove from Cart Event

File: app/code/YourCompany/Analytics/view/frontend/web/js/cart-remove-tracker.js

define([
    'jquery',
    'Magento_Checkout/js/action/get-totals',
    'Magento_Customer/js/customer-data'
], function($, getTotalsAction, customerData) {
    'use strict';

    return function() {
        $(document).on('click', '.action-delete', function() {
            var $row = $(this).closest('tr');
            var itemData = {
                item_id: $row.data('product-sku'),
                item_name: $row.find('.product-item-name').text().trim(),
                price: parseFloat($row.find('.price').data('price-amount')) || 0,
                quantity: parseInt($row.find('.qty').val()) || 1
            };

            gtag('event', 'remove_from_cart', {
                'currency': 'USD',
                'value': itemData.price * itemData.quantity,
                'items': [itemData]
            });
        });
    };
});

Initialize on Cart Page:

File: app/code/YourCompany/Analytics/view/frontend/layout/checkout_cart_index.xml

<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceContainer name="content">
            <block class="Magento\Framework\View\Element\Template"
                   name="analytics.cart.tracker"
                   template="YourCompany_Analytics::cart-tracker.phtml"/>
        </referenceContainer>
    </body>
</page>

Checkout Events Implementation

Begin Checkout Event

File: app/code/YourCompany/Analytics/Observer/BeginCheckout.php

<?php
namespace YourCompany\Analytics\Observer;

use Magento\Framework\Event\Observer;
use Magento\Framework\Event\ObserverInterface;
use Magento\Checkout\Model\Session as CheckoutSession;

class BeginCheckout implements ObserverInterface
{
    protected $checkoutSession;

    public function __construct(CheckoutSession $checkoutSession)
    {
        $this->checkoutSession = $checkoutSession;
    }

    public function execute(Observer $observer)
    {
        $quote = $this->checkoutSession->getQuote();
        $items = [];

        foreach ($quote->getAllVisibleItems() as $item) {
            $items[] = [
                'item_id' => $item->getProduct()->getSku(),
                'item_name' => $item->getName(),
                'price' => (float)$item->getPrice(),
                'quantity' => (int)$item->getQty()
            ];
        }

        $this->checkoutSession->setGa4BeginCheckout([
            'currency' => $quote->getQuoteCurrencyCode(),
            'value' => (float)$quote->getGrandTotal(),
            'items' => $items
        ]);
    }
}

Register Event:

File: app/code/YourCompany/Analytics/etc/frontend/events.xml

<event name="checkout_onepage_controller_success_action">
    <observer name="analytics_begin_checkout"
              instance="YourCompany\Analytics\Observer\BeginCheckout"/>
</event>

Template:

File: view/frontend/templates/checkout-begin.phtml

<?php
$checkoutData = $block->getCheckoutSession()->getGa4BeginCheckout();
if ($checkoutData):
?>
<script>
    require(['jquery'], function($) {
        gtag('event', 'begin_checkout', {
            'currency': '<?= $block->escapeJs($checkoutData['currency']) ?>',
            'value': <?= $checkoutData['value'] ?>,
            'items': <?= $block->escapeJs(json_encode($checkoutData['items'])) ?>
        });
    });
</script>
<?php
    $block->getCheckoutSession()->unsGa4BeginCheckout();
endif;
?>

Purchase Event

Track completed transactions.

File: app/code/YourCompany/Analytics/Observer/TrackPurchase.php

<?php
namespace YourCompany\Analytics\Observer;

use Magento\Framework\Event\Observer;
use Magento\Framework\Event\ObserverInterface;
use Magento\Sales\Model\Order;

class TrackPurchase implements ObserverInterface
{
    protected $checkoutSession;

    public function __construct(\Magento\Checkout\Model\Session $checkoutSession)
    {
        $this->checkoutSession = $checkoutSession;
    }

    public function execute(Observer $observer)
    {
        /** @var Order $order */
        $order = $observer->getEvent()->getOrder();

        if (!$order) {
            return;
        }

        $items = [];
        foreach ($order->getAllVisibleItems() as $item) {
            $product = $item->getProduct();
            $items[] = [
                'item_id' => $item->getSku(),
                'item_name' => $item->getName(),
                'item_category' => $this->getProductCategory($product),
                'price' => (float)$item->getPrice(),
                'quantity' => (int)$item->getQtyOrdered(),
                'discount' => (float)$item->getDiscountAmount()
            ];
        }

        $purchaseData = [
            'transaction_id' => $order->getIncrementId(),
            'value' => (float)$order->getGrandTotal(),
            'currency' => $order->getOrderCurrencyCode(),
            'tax' => (float)$order->getTaxAmount(),
            'shipping' => (float)$order->getShippingAmount(),
            'items' => $items
        ];

        // Store in session for success page rendering
        $this->checkoutSession->setGa4PurchaseData($purchaseData);
    }

    protected function getProductCategory($product)
    {
        $categoryIds = $product->getCategoryIds();
        if (empty($categoryIds)) {
            return '';
        }

        $categoryId = reset($categoryIds);
        $category = $this->categoryRepository->get($categoryId);
        return $category->getName();
    }
}

Register Observer:

<event name="checkout_onepage_controller_success_action">
    <observer name="analytics_track_purchase"
              instance="YourCompany\Analytics\Observer\TrackPurchase"/>
</event>

Success Page Template:

File: view/frontend/templates/success.phtml

<?php
$purchaseData = $block->getCheckoutSession()->getGa4PurchaseData();
if ($purchaseData):
?>
<script>
    require(['jquery'], function($) {
        gtag('event', 'purchase', {
            'transaction_id': '<?= $block->escapeJs($purchaseData['transaction_id']) ?>',
            'value': <?= $purchaseData['value'] ?>,
            'currency': '<?= $block->escapeJs($purchaseData['currency']) ?>',
            'tax': <?= $purchaseData['tax'] ?>,
            'shipping': <?= $purchaseData['shipping'] ?>,
            'items': <?= $block->escapeJs(json_encode($purchaseData['items'])) ?>
        });
    });
</script>
<?php
    $block->getCheckoutSession()->unsGa4PurchaseData();
endif;
?>

Custom Events

Search Event

File: view/frontend/templates/search-tracking.phtml

<script>
    require(['jquery'], function($) {
        $('form#search_mini_form').on('submit', function() {
            var searchTerm = $(this).find('input[name="q"]').val();

            gtag('event', 'search', {
                'search_term': searchTerm
            });
        });
    });
</script>

Newsletter Signup

File: app/code/YourCompany/Analytics/Observer/NewsletterSubscribe.php

<?php
namespace YourCompany\Analytics\Observer;

use Magento\Framework\Event\Observer;
use Magento\Framework\Event\ObserverInterface;

class NewsletterSubscribe implements ObserverInterface
{
    public function execute(Observer $observer)
    {
        $subscriber = $observer->getEvent()->getSubscriber();

        // Store event data for frontend tracking
        $subscriber->setGa4Event([
            'event' => 'newsletter_signup',
            'method' => 'footer_form'
        ]);
    }
}

Testing & Validation

GA4 DebugView

Enable debug mode for testing:

gtag('config', 'G-XXXXXXXXXX', {
    'debug_mode': true
});

Access DebugView in GA4:

Admin > Configure > DebugView

Browser Console Logging

Add logging for development:

window.dataLayer = window.dataLayer || [];
function gtag(){
    dataLayer.push(arguments);
    console.log('GA4 Event:', arguments); // Development only
}

Network Tab Validation

  1. Open Chrome DevTools
  2. Navigate to Network tab
  3. Filter by collect or analytics
  4. Verify event parameters in request payload

Performance Considerations

Event Batching

Batch multiple events to reduce requests:

var eventQueue = [];

function queueEvent(eventName, eventParams) {
    eventQueue.push({name: eventName, params: eventParams});

    if (eventQueue.length >= 5) {
        flushEventQueue();
    }
}

function flushEventQueue() {
    eventQueue.forEach(function(event) {
        gtag('event', event.name, event.params);
    });
    eventQueue = [];
}

// Flush on page unload
window.addEventListener('beforeunload', flushEventQueue);

Lazy Loading

Defer non-critical event tracking:

require(['jquery'], function($) {
    $(window).on('load', function() {
        // Track events after page load
        trackDeferredEvents();
    });
});

Troubleshooting

Events Not Appearing in GA4

Solutions:

  1. Check DebugView for real-time validation
  2. Verify Measurement ID is correct
  3. Ensure events use correct parameter names
  4. Check browser console for JavaScript errors

Duplicate Purchase Events

Solutions:

  1. Clear session data after tracking: unsGa4PurchaseData()
  2. Check for multiple success page loads
  3. Implement transaction deduplication in GA4

Missing Event Parameters

Solutions:

  1. Validate data availability before tracking
  2. Add null checks in PHP observers
  3. Use default values for optional parameters

Next Steps


Additional Resources

// SYS.FOOTER