Structurer le dataLayer PrestaShop pour Google Analytics 4

Structurer le dataLayer PrestaShop pour Google Analytics 4
Un dataLayer mal structuré dans PrestaShop, c'est des rapports GA4 faux et des décisions prises sur de mauvaises données. Schéma des items, hooks PHP, push AJAX, protection anti-doublon : le guide complet pour implémenter un tracking e-commerce fiable.

Depuis le passage à GA4, le dataLayer n'est plus une option : c'est le contrat entre votre PrestaShop et votre stack analytics. Un push mal structuré, un événement manquant ou un paramètre nommé différemment selon la page — et vos rapports e-commerce sont faux. Voici comment structurer un dataLayer robuste, conforme au schéma GA4, et maintenable dans un module PrestaShop.

GA4 vs Universal Analytics : un modèle d'événements fondamentalement différent

Avec Universal Analytics, les hits e-commerce s'appuyaient sur un schéma figé (ec:addToCart, ec:purchase…). GA4 adopte un modèle d'événements plat et flexible, mais avec des noms et paramètres standardisés que Google Ads et les rapports e-commerce attendent précisément.

Conséquence directe : si vous poussez add_to_basket au lieu de add_to_cart, GTM accepte l'événement mais GA4 ne le comptabilise pas dans ses rapports e-commerce automatiques. La rigueur de nommage est non-négociable.

Les événements e-commerce GA4 à implémenter par ordre de priorité :

  • view_item_list — affichage d'une liste de produits (catégorie, résultats de recherche)
  • view_item — affichage d'une fiche produit
  • add_to_cart — ajout au panier
  • remove_from_cart — suppression du panier
  • view_cart — affichage du panier
  • begin_checkout — début du tunnel d'achat
  • add_payment_info — saisie des infos de paiement
  • add_shipping_info — sélection de la livraison
  • purchase — confirmation de commande

La structure d'un item GA4 dans PrestaShop

Chaque produit poussé dans le dataLayer doit respecter ce schéma. C'est la brique de base qui se répète dans tous les événements :

// Structure d'un item GA4 — à partir d'un objet Product PrestaShop
{
    item_id: "12",                    // $product->id
    item_name: "T-shirt coton bio",   // $product->name
    affiliation: "Boutique principale",
    currency: "EUR",
    discount: 5.00,                   // prix barré - prix soldé
    index: 0,                         // position dans la liste
    item_brand: "Ma Marque",          // $manufacturer->name
    item_category: "Vêtements",       // $category->name
    item_category2: "Homme",          // catégorie parent si applicable
    item_variant: "Rouge / L",        // attributs combinaison
    price: 29.99,                     // prix TTC unitaire
    quantity: 1
}

Le champ item_id peut utiliser l'ID PrestaShop ou votre référence interne — l'essentiel est la cohérence entre tous les événements d'une même session.

Implémenter le dataLayer avec les hooks PrestaShop

L'approche la plus propre est un module dédié qui écoute les hooks de rendu pour injecter les pushes dataLayer au bon moment.

Hook actionProductListOverride — vue liste

public function hookDisplayHeader(array $params): string
{
    $controller = $this->context->controller;

    if ($controller instanceof CategoryController) {
        return $this->generateViewItemListPush($controller);
    }

    if ($controller instanceof ProductController) {
        return $this->generateViewItemPush($controller);
    }

    return '';
}

private function generateViewItemListPush(CategoryController $controller): string
{
    $products = $controller->getTemplateVarProducts();
    $category = $this->context->controller->getCategory();

    $items = array_map(
        fn($product, $index) => $this->buildItem($product, $index),
        $products['products'],
        array_keys($products['products'])
    );

    $push = [
        'event' => 'view_item_list',
        'ecommerce' => [
            'item_list_id' => 'category_' . $category->id,
            'item_list_name' => $category->name,
            'items' => $items,
        ],
    ];

    return $this->renderPush($push);
}

Hook displayOrderConfirmation — événement purchase

C'est l'événement le plus critique : un doublon de purchase fausse tout votre ROI. Il faut stocker l'ID commande en session pour éviter le re-push au rechargement de page.

public function hookDisplayOrderConfirmation(array $params): string
{
    $order = $params['order'];

    // Éviter le double-comptage sur rechargement
    $sessionKey = 'ga4_purchase_' . $order->id;
    if ($this->context->cookie->$sessionKey) {
        return '';
    }
    $this->context->cookie->$sessionKey = true;

    $products = $order->getProducts();
    $items = array_values(array_map(
        fn($product) => $this->buildItemFromOrderDetail($product),
        $products
    ));

    $push = [
        'event' => 'purchase',
        'ecommerce' => [
            'transaction_id' => (string) $order->id,
            'affiliation' => Configuration::get('PS_SHOP_NAME'),
            'value' => (float) $order->total_paid_tax_incl,
            'tax' => (float) ($order->total_paid_tax_incl - $order->total_paid_tax_excl),
            'shipping' => (float) $order->total_shipping_tax_incl,
            'currency' => Currency::getIsoCodeById((int) $order->id_currency),
            'coupon' => $this->getOrderCoupon($order),
            'items' => $items,
        ],
    ];

    return $this->renderPush($push);
}

Hook actionCartSave — add_to_cart en AJAX

L'ajout au panier en PrestaShop est souvent asynchrone. Il faut pousser le dataLayer depuis le JavaScript au retour de l'appel AJAX, pas depuis le hook PHP :

// Dans votre module JS — à brancher sur l'événement jQuery natif PrestaShop
$(document).on('click', '.add-to-cart', function () {
    const $btn = $(this);
    const productData = {
        item_id: $btn.data('product-id').toString(),
        item_name: $btn.data('product-name'),
        currency: prestashop.currency.iso_code,
        price: parseFloat($btn.data('product-price')),
        quantity: parseInt($('#quantity_wanted').val(), 10) || 1,
    };

    // Attendre la réponse AJAX PrestaShop avant de pusher
    $(document).on('updateCart', function (event, resp) {
        if (!resp || resp.hasError) {
            return;
        }

        window.dataLayer = window.dataLayer || [];
        window.dataLayer.push({ ecommerce: null }); // Clear obligatoire GA4
        window.dataLayer.push({
            event: 'add_to_cart',
            ecommerce: {
                currency: productData.currency,
                value: productData.price * productData.quantity,
                items: [productData],
            },
        });
    });
});

Le push { ecommerce: null } avant chaque événement e-commerce est obligatoire selon la documentation Google. Sans lui, les données de l'événement précédent contaminent le suivant.

Structurer le module : DataLayerBuilder

Centraliser la construction des items dans un service évite la duplication et garantit la cohérence entre les événements :

<?php

declare(strict_types=1);

namespace PrestaShop\Module\Ga4DataLayer\Builder;

use Currency;
use Manufacturer;
use Product;

class DataLayerBuilder
{
    /**
     * Construit un item GA4 depuis un tableau produit PrestaShop.
     *
     * @param array<string, mixed> $product
     * @param int $index Position dans la liste (0-based)
     *
     * @return array<string, mixed>
     */
    public function buildItem(array $product, int $index = 0): array
    {
        $price = (float) ($product['price_amount'] ?? $product['price'] ?? 0);
        $originalPrice = (float) ($product['price_without_reduction'] ?? $price);
        $discount = round($originalPrice - $price, 2);

        return array_filter([
            'item_id' => (string) $product['id_product'],
            'item_name' => $product['name'],
            'currency' => $this->getCurrencyCode(),
            'discount' => $discount > 0 ? $discount : null,
            'index' => $index,
            'item_brand' => $this->getManufacturerName((int) ($product['id_manufacturer'] ?? 0)),
            'item_category' => $product['category'] ?? null,
            'item_variant' => $this->buildVariant($product),
            'price' => $price,
            'quantity' => (int) ($product['quantity'] ?? 1),
        ], fn($value) => $value !== null);
    }

    private function buildVariant(array $product): ?string
    {
        if (empty($product['attributes_small'])) {
            return null;
        }

        return $product['attributes_small'];
    }

    private function getManufacturerName(int $manufacturerId): ?string
    {
        if ($manufacturerId <= 0) {
            return null;
        }

        $manufacturer = new Manufacturer($manufacturerId);

        return $manufacturer->name ?: null;
    }

    private function getCurrencyCode(): string
    {
        $currency = \Context::getContext()->currency;

        return $currency->iso_code ?? 'EUR';
    }
}

Valider votre dataLayer en 3 étapes

Avant de passer en production, vérifiez systématiquement ces trois points :

1. Google Tag Assistant

L'extension Chrome Tag Assistant (legacy ou companion) affiche en temps réel les pushes dataLayer et détecte les erreurs de nommage. Vérifiez que chaque événement a bien le champ ecommerce et que l'item contient au minimum item_id, item_name et price.

2. Console navigateur

// Surveiller les pushes en temps réel
const originalPush = Array.prototype.push;
window.dataLayer = window.dataLayer || [];
window.dataLayer.push = function(...args) {
    console.log('[DataLayer]', JSON.stringify(args, null, 2));
    return originalPush.apply(this, args);
};

3. Rapport de débogage GA4

Dans l'interface GA4, activez le mode débogage (paramètre debug_mode: true dans votre configuration GTM) et consultez Configurer › DebugView. Vous visualisez chaque événement reçu en temps réel avec ses paramètres.

Les erreurs les plus fréquentes

  • Prix TTC vs HT : GA4 attend le prix affiché au client (TTC). Vérifiez que vous utilisez price_tax_incl et non price qui peut être HT selon la config PrestaShop.
  • item_id en entier vs chaîne : GA4 est case-sensitive sur les types. Castez toujours en string pour éviter les incohérences entre les pushes PHP (entier) et JS (chaîne).
  • Double push sur la page confirmation : sans protection par cookie ou session, chaque rechargement crée une nouvelle transaction dans GA4. Le champ transaction_id déduplique côté GA4, mais uniquement sur une fenêtre de 90 jours.
  • Oublier le clear ecommerce : sans dataLayer.push({ ecommerce: null }), les données d'un événement add_to_cart peuvent se retrouver dans le purchase suivant.

Conclusion

Un dataLayer GA4 bien structuré dans PrestaShop, c'est la différence entre des rapports e-commerce fiables et des décisions prises sur des données corrompues. La clé : centraliser la construction des items dans un service dédié, systématiser le clear ecommerce avant chaque push, et protéger la page de confirmation contre les doubles comptages.

L'investissement initial est rentabilisé dès le premier audit de campagne : vous saurez précisément quels canaux génèrent du revenu, et vous pourrez en prouver la valeur à votre client.

Besoin d'aide pour implémenter ou auditer votre dataLayer PrestaShop ? Contactez-moi pour en discuter !

Jonathan Le-Peru

Écrit par Jonathan Le-Peru

Développeur backend avec plus de 7 ans d'expérience, spécialisé dans la création de solutions e-commerce robustes avec Prestashop. Passionné par l'optimisation des performances et les bonnes pratiques de développement.