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_inclet nonpricequi peut être HT selon la config PrestaShop. - item_id en entier vs chaîne : GA4 est case-sensitive sur les types. Castez toujours en
stringpour é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_iddé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 !