PrestaShop GA4 Ecommerce Tracking | Blue Frog Docs

PrestaShop GA4 Ecommerce Tracking

Implement complete Google Analytics 4 ecommerce tracking on PrestaShop including product impressions, cart actions, checkout, and purchase events.

PrestaShop GA4 Ecommerce Tracking

Complete guide to implementing GA4 Enhanced Ecommerce tracking on PrestaShop. Track the entire customer journey from product discovery to purchase completion.

Ecommerce Events Overview

GA4 ecommerce tracking uses these key events:

Event Description PrestaShop Context
view_item_list Products displayed in list Category pages, search results, related products
select_item Product clicked in list Product link clicked
view_item Product details viewed Product page
add_to_cart Item added to cart Add to cart action
remove_from_cart Item removed from cart Cart removal
view_cart Shopping cart viewed Cart page
begin_checkout Checkout started First checkout step
add_shipping_info Shipping selected Shipping method chosen
add_payment_info Payment selected Payment method chosen
purchase Transaction completed Order confirmation

Product Data Structure

Standard GA4 Item Parameters

All ecommerce events use this product data structure:

{
  item_id: "12345",              // Required: Product ID
  item_name: "Product Name",     // Required: Product name
  affiliation: "PrestaShop Store", // Optional: Store name
  coupon: "SUMMER2024",          // Optional: Applied coupon
  discount: 5.00,                // Optional: Discount amount
  index: 0,                      // Optional: Position in list
  item_brand: "Brand Name",      // Optional: Manufacturer
  item_category: "Category",     // Optional: Main category
  item_category2: "Subcategory", // Optional: Sub-category
  item_category3: "",            // Optional: Further subcategory
  item_list_id: "related_products", // Optional: List identifier
  item_list_name: "Related Products", // Optional: List name
  item_variant: "Red-XL",        // Optional: Combination/variant
  location_id: "1",              // Optional: Shop/warehouse ID
  price: 29.99,                  // Optional: Product price
  quantity: 1                    // Optional: Quantity
}

PrestaShop-Specific Product Data Helper

Create a PHP helper function in your module:

<?php
// Format PrestaShop product for GA4
private function formatProductForGA4($product, $context = 'view')
{
    // Get product object if array passed
    if (is_array($product)) {
        $id_product = isset($product['id_product']) ? (int)$product['id_product'] : 0;
        $productObj = new Product($id_product, true, $this->context->language->id);
    } else {
        $productObj = $product;
        $id_product = $productObj->id;
    }

    // Get category path
    $category = new Category($productObj->id_category_default, $this->context->language->id);
    $categories = $this->getCategoryPath($category);

    // Get manufacturer/brand
    $brand = '';
    if ($productObj->id_manufacturer > 0) {
        $manufacturer = new Manufacturer($productObj->id_manufacturer, $this->context->language->id);
        $brand = $manufacturer->name;
    }

    // Get combination/variant if applicable
    $variant = '';
    if (isset($product['id_product_attribute']) && $product['id_product_attribute'] > 0) {
        $combination = new Combination($product['id_product_attribute']);
        $variant = $this->getCombinationName($combination);
    }

    // Get discount if applicable
    $discount = 0;
    $price = $productObj->getPrice(true, null, 2);
    $price_without_reduction = $productObj->getPriceWithoutReduct(true);
    if ($price_without_reduction > $price) {
        $discount = $price_without_reduction - $price;
    }

    return array(
        'item_id' => (string)$id_product,
        'item_name' => $productObj->name,
        'item_brand' => $brand,
        'item_category' => isset($categories[0]) ? $categories[0] : '',
        'item_category2' => isset($categories[1]) ? $categories[1] : '',
        'item_category3' => isset($categories[2]) ? $categories[2] : '',
        'item_variant' => $variant,
        'price' => $price,
        'discount' => $discount,
        'quantity' => isset($product['quantity']) ? (int)$product['quantity'] : 1,
        'affiliation' => $this->context->shop->name,
        'location_id' => (string)$this->context->shop->id
    );
}

// Get category hierarchy
private function getCategoryPath($category)
{
    $categories = array();
    $current = $category;

    while ($current->id > 2) { // Stop at root (id 2)
        $categories[] = $current->name;
        $current = new Category($current->id_parent, $this->context->language->id);
    }

    return array_reverse($categories);
}

// Get combination/variant name
private function getCombinationName($combination)
{
    $attributes = $combination->getAttributesName($this->context->language->id);
    $variant_parts = array();

    foreach ($attributes as $attribute) {
        $variant_parts[] = $attribute['name'];
    }

    return implode('-', $variant_parts);
}

Implementing Ecommerce Events

1. Product Impressions (view_item_list)

Category Page Implementation:

// Hook: actionProductSearchAfter or displayFooterCategory
public function hookDisplayFooterCategory($params)
{
    $category = $params['category'];
    $products = $this->getDisplayedProducts($category);

    $items = array();
    $index = 0;

    foreach ($products as $product) {
        $item = $this->formatProductForGA4($product);
        $item['index'] = $index;
        $item['item_list_id'] = 'category_' . $category->id;
        $item['item_list_name'] = $category->name;

        $items[] = $item;
        $index++;
    }

    $this->context->smarty->assign(array(
        'ga4_items' => $items,
        'ga4_list_name' => $category->name,
        'ga4_list_id' => 'category_' . $category->id
    ));

    return $this->display(__FILE__, 'views/templates/hook/view-item-list.tpl');
}

Template:

{* views/templates/hook/view-item-list.tpl *}
<script>
  // Fire view_item_list event
  gtag('event', 'view_item_list', {
    item_list_id: '{$ga4_list_id|escape:'javascript':'UTF-8'}',
    item_list_name: '{$ga4_list_name|escape:'javascript':'UTF-8'}',
    items: [
      {foreach from=$ga4_items item=item}
      {
        item_id: '{$item.item_id|escape:'javascript':'UTF-8'}',
        item_name: '{$item.item_name|escape:'javascript':'UTF-8'}',
        item_brand: '{$item.item_brand|escape:'javascript':'UTF-8'}',
        item_category: '{$item.item_category|escape:'javascript':'UTF-8'}',
        {if $item.item_category2}item_category2: '{$item.item_category2|escape:'javascript':'UTF-8'}',{/if}
        price: {$item.price|floatval},
        quantity: {$item.quantity|intval},
        index: {$item.index|intval}
      }{if !$item@last},{/if}
      {/foreach}
    ]
  });
</script>

2. Product Click (select_item)

JavaScript Implementation:

// Track product clicks in category/search listings
document.addEventListener('click', function(e) {
  var productLink = e.target.closest('a.product-thumbnail, a.product-title, .product-miniature a');

  if (productLink) {
    var miniature = productLink.closest('.product-miniature');
    if (!miniature) return;

    var productId = miniature.dataset.idProduct;
    var productName = miniature.querySelector('.product-title')?.textContent.trim();
    var productPrice = miniature.querySelector('.product-price .price')?.textContent.replace(/[^0-9.]/g, '');
    var listName = document.querySelector('.page-category-heading')?.textContent.trim() || 'product_list';
    var index = Array.from(miniature.parentElement.children).indexOf(miniature);

    if (typeof gtag !== 'undefined') {
      gtag('event', 'select_item', {
        item_list_name: listName,
        items: [{
          item_id: productId,
          item_name: productName,
          price: parseFloat(productPrice) || 0,
          index: index
        }]
      });
    }
  }
}, true); // Use capture phase to ensure tracking before navigation

3. Product View (view_item)

Product Page Implementation:

// Hook: displayFooterProduct
public function hookDisplayFooterProduct($params)
{
    $product = $params['product'];

    // Get selected combination if any
    $id_product_attribute = Tools::getValue('id_product_attribute', 0);

    $product_data = array(
        'id_product' => $product->id,
        'id_product_attribute' => $id_product_attribute
    );

    $item = $this->formatProductForGA4($product_data);

    $this->context->smarty->assign(array(
        'ga4_item' => $item,
        'ga4_currency' => $this->context->currency->iso_code,
        'ga4_value' => $item['price']
    ));

    return $this->display(__FILE__, 'views/templates/hook/view-item.tpl');
}

Template:

{* views/templates/hook/view-item.tpl *}
<script>
  gtag('event', 'view_item', {
    currency: '{$ga4_currency|escape:'javascript':'UTF-8'}',
    value: {$ga4_value|floatval},
    items: [{
      item_id: '{$ga4_item.item_id|escape:'javascript':'UTF-8'}',
      item_name: '{$ga4_item.item_name|escape:'javascript':'UTF-8'}',
      item_brand: '{$ga4_item.item_brand|escape:'javascript':'UTF-8'}',
      item_category: '{$ga4_item.item_category|escape:'javascript':'UTF-8'}',
      {if $ga4_item.item_category2}item_category2: '{$ga4_item.item_category2|escape:'javascript':'UTF-8'}',{/if}
      {if $ga4_item.item_variant}item_variant: '{$ga4_item.item_variant|escape:'javascript':'UTF-8'}',{/if}
      price: {$ga4_item.price|floatval},
      quantity: 1
    }]
  });
</script>

4. Add to Cart (add_to_cart)

AJAX Cart Addition:

// Hook: actionCartSave
public function hookActionCartSave($params)
{
    if (!isset($params['cart'])) {
        return;
    }

    $cart = $params['cart'];
    $last_product = $cart->getLastProduct();

    if (!$last_product) {
        return;
    }

    $item = $this->formatProductForGA4($last_product);

    // Store in cookie for JavaScript retrieval
    $event_data = array(
        'event' => 'add_to_cart',
        'currency' => $this->context->currency->iso_code,
        'value' => $item['price'] * $item['quantity'],
        'items' => array($item)
    );

    // Use PrestaShop cookie to pass data to frontend
    $this->context->cookie->__set('ga4_cart_event', json_encode($event_data));
}

JavaScript to Read Cookie and Fire Event:

// Check for cart event on page load
document.addEventListener('DOMContentLoaded', function() {
  var ga4Event = getCookie('ga4_cart_event');

  if (ga4Event && typeof gtag !== 'undefined') {
    try {
      var eventData = JSON.parse(ga4Event);

      gtag('event', eventData.event, {
        currency: eventData.currency,
        value: eventData.value,
        items: eventData.items
      });

      // Clear cookie after firing
      document.cookie = 'ga4_cart_event=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
    } catch (e) {
      console.error('GA4 cart event error:', e);
    }
  }
});

function getCookie(name) {
  var value = "; " + document.cookie;
  var parts = value.split("; " + name + "=");
  if (parts.length == 2) return parts.pop().split(";").shift();
}

5. Remove from Cart (remove_from_cart)

JavaScript Implementation:

// Track cart item removal
document.addEventListener('click', function(e) {
  if (e.target.matches('.cart-item-remove, .remove-from-cart, [data-link-action="delete-from-cart"]')) {
    var cartItem = e.target.closest('.cart-item, .product-line-grid');
    if (!cartItem) return;

    var productId = cartItem.dataset.idProduct;
    var productName = cartItem.querySelector('.product-name, .label')?.textContent.trim();
    var price = cartItem.querySelector('.product-price, .price')?.textContent.replace(/[^0-9.]/g, '');
    var quantity = cartItem.querySelector('.qty input, .product-quantity')?.value || 1;

    if (typeof gtag !== 'undefined') {
      gtag('event', 'remove_from_cart', {
        currency: prestashop.currency.iso_code,
        value: parseFloat(price) * parseInt(quantity),
        items: [{
          item_id: productId,
          item_name: productName,
          price: parseFloat(price),
          quantity: parseInt(quantity)
        }]
      });
    }
  }
});

6. Begin Checkout (begin_checkout)

Checkout Page Hook:

// Hook: displayShoppingCart or custom checkout controller override
public function hookDisplayBeforeCheckout($params)
{
    $cart = $this->context->cart;
    $products = $cart->getProducts(true);

    $items = array();
    foreach ($products as $product) {
        $items[] = $this->formatProductForGA4($product);
    }

    $cart_total = $cart->getOrderTotal(true);

    $this->context->smarty->assign(array(
        'ga4_items' => $items,
        'ga4_cart_value' => $cart_total,
        'ga4_currency' => $this->context->currency->iso_code
    ));

    return $this->display(__FILE__, 'views/templates/hook/begin-checkout.tpl');
}

Template:

{* views/templates/hook/begin-checkout.tpl *}
<script>
  gtag('event', 'begin_checkout', {
    currency: '{$ga4_currency|escape:'javascript':'UTF-8'}',
    value: {$ga4_cart_value|floatval},
    items: [
      {foreach from=$ga4_items item=item}
      {
        item_id: '{$item.item_id|escape:'javascript':'UTF-8'}',
        item_name: '{$item.item_name|escape:'javascript':'UTF-8'}',
        item_brand: '{$item.item_brand|escape:'javascript':'UTF-8'}',
        price: {$item.price|floatval},
        quantity: {$item.quantity|intval}
      }{if !$item@last},{/if}
      {/foreach}
    ]
  });
</script>

7. Add Shipping Info (add_shipping_info)

Checkout Step Tracking:

// Track shipping method selection
document.addEventListener('change', function(e) {
  if (e.target.matches('input[name="delivery_option"], .delivery-option')) {
    var shippingMethod = e.target.value;
    var shippingCost = document.querySelector('.shipping-cost .value')?.textContent.replace(/[^0-9.]/g, '') || '0';

    var cartItems = getCartItemsFromCheckout();
    var cartValue = getCheckoutTotal();

    if (typeof gtag !== 'undefined') {
      gtag('event', 'add_shipping_info', {
        currency: prestashop.currency.iso_code,
        value: parseFloat(cartValue),
        shipping_tier: shippingMethod,
        items: cartItems
      });
    }
  }
});

8. Add Payment Info (add_payment_info)

Payment Selection Tracking:

// Track payment method selection
document.addEventListener('change', function(e) {
  if (e.target.matches('input[name="payment-option"], .payment-option')) {
    var paymentMethod = e.target.dataset.moduleName || e.target.value;

    var cartItems = getCartItemsFromCheckout();
    var cartValue = getCheckoutTotal();

    if (typeof gtag !== 'undefined') {
      gtag('event', 'add_payment_info', {
        currency: prestashop.currency.iso_code,
        value: parseFloat(cartValue),
        payment_type: paymentMethod,
        items: cartItems
      });
    }
  }
});

9. Purchase (purchase)

Order Confirmation Page - Most Critical Event:

// Hook: displayOrderConfirmation
public function hookDisplayOrderConfirmation($params)
{
    $order = $params['order'];

    // Get order products
    $order_detail = $order->getOrderDetailList();
    $items = array();

    foreach ($order_detail as $product) {
        $item = $this->formatProductForGA4($product);
        $items[] = $item;
    }

    // Get applied cart rules (discounts/coupons)
    $cart_rules = $order->getCartRules();
    $coupon = '';
    if (!empty($cart_rules)) {
        $coupon_names = array();
        foreach ($cart_rules as $rule) {
            $coupon_names[] = $rule['name'];
        }
        $coupon = implode(',', $coupon_names);
    }

    // Get shipping and tax
    $shipping = $order->total_shipping_tax_incl;
    $tax = $order->total_paid_tax_incl - $order->total_paid_tax_excl;

    $this->context->smarty->assign(array(
        'ga4_transaction_id' => $order->reference,
        'ga4_value' => $order->total_paid_tax_incl,
        'ga4_tax' => $tax,
        'ga4_shipping' => $shipping,
        'ga4_currency' => $this->context->currency->iso_code,
        'ga4_coupon' => $coupon,
        'ga4_items' => $items,
        'ga4_affiliation' => $this->context->shop->name
    ));

    return $this->display(__FILE__, 'views/templates/hook/purchase.tpl');
}

Purchase Event Template:

{* views/templates/hook/purchase.tpl *}
<script>
  gtag('event', 'purchase', {
    transaction_id: '{$ga4_transaction_id|escape:'javascript':'UTF-8'}',
    value: {$ga4_value|floatval},
    tax: {$ga4_tax|floatval},
    shipping: {$ga4_shipping|floatval},
    currency: '{$ga4_currency|escape:'javascript':'UTF-8'}',
    {if $ga4_coupon}coupon: '{$ga4_coupon|escape:'javascript':'UTF-8'}',{/if}
    affiliation: '{$ga4_affiliation|escape:'javascript':'UTF-8'}',
    items: [
      {foreach from=$ga4_items item=item}
      {
        item_id: '{$item.item_id|escape:'javascript':'UTF-8'}',
        item_name: '{$item.item_name|escape:'javascript':'UTF-8'}',
        item_brand: '{$item.item_brand|escape:'javascript':'UTF-8'}',
        item_category: '{$item.item_category|escape:'javascript':'UTF-8'}',
        {if $item.item_variant}item_variant: '{$item.item_variant|escape:'javascript':'UTF-8'}',{/if}
        price: {$item.price|floatval},
        discount: {$item.discount|floatval},
        quantity: {$item.quantity|intval}
      }{if !$item@last},{/if}
      {/foreach}
    ]
  });
</script>

Refund Tracking

Server-Side Refund Event:

For refunds, you need to send data to GA4's Measurement Protocol API:

// When processing refund
public function sendRefundToGA4($order)
{
    $measurement_id = Configuration::get('GA4_MEASUREMENT_ID');
    $api_secret = Configuration::get('GA4_API_SECRET'); // From GA4 Admin > Data Streams

    $client_id = $this->getClientIdForOrder($order);

    $refund_data = array(
        'client_id' => $client_id,
        'events' => array(
            array(
                'name' => 'refund',
                'params' => array(
                    'transaction_id' => $order->reference,
                    'value' => $order->total_paid_tax_incl,
                    'currency' => Currency::getCurrencyInstance($order->id_currency)->iso_code
                )
            )
        )
    );

    $url = "https://www.google-analytics.com/mp/collect?measurement_id={$measurement_id}&api_secret={$api_secret}";

    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($refund_data));
    curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json'));

    $response = curl_exec($ch);
    curl_close($ch);

    return $response;
}

Testing Ecommerce Tracking

Using GA4 DebugView

Enable Debug Mode:

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

Test Complete Funnel:

  1. Browse category (view_item_list)
  2. Click product (select_item)
  3. View product page (view_item)
  4. Add to cart (add_to_cart)
  5. View cart (view_cart)
  6. Start checkout (begin_checkout)
  7. Select shipping (add_shipping_info)
  8. Select payment (add_payment_info)
  9. Complete test order (purchase)

Verify each event appears in DebugView with correct parameters.

Data Validation Checklist

  • All required parameters present (item_id, item_name, etc.)
  • Currency is correct 3-letter ISO code
  • Prices are numeric (not strings)
  • Transaction ID is unique for each purchase
  • Items array contains all purchased products
  • Tax and shipping values are accurate
  • Coupon codes are captured when applied
  • No duplicate purchase events on page refresh

Multi-Store Ecommerce Tracking

Add store context to transactions:

// In formatProductForGA4 or purchase event
$item['affiliation'] = $this->context->shop->name;
$item['location_id'] = (string)$this->context->shop->id;

Segment by store in GA4:

Create custom dimension for shop_id to analyze performance per store in multi-store setups.

Common Issues and Solutions

Duplicate Purchase Events

Problem: Purchase event fires multiple times on order confirmation refresh.

Solution: Set cookie to prevent duplicate firing:

// Check if purchase already tracked
if (!getCookie('order_tracked_' + transactionId)) {
  gtag('event', 'purchase', purchaseData);

  // Set cookie to prevent re-firing
  document.cookie = 'order_tracked_' + transactionId + '=1; path=/; max-age=86400';
}

Missing Product Data

Problem: Product category or brand is empty.

Solution: Ensure all products have categories and manufacturers assigned in PrestaShop.

Currency Mismatch

Problem: Wrong currency in GA4 reports.

Solution: Verify $this->context->currency->iso_code returns correct 3-letter code (USD, EUR, GBP).

Next Steps

// SYS.FOOTER