Meta Pixel Event Tracking on PrestaShop
Track customer actions on your PrestaShop store with Meta Pixel events to optimize Facebook and Instagram advertising campaigns, build custom audiences, and measure ROI.
Meta Pixel Standard Events
Event Overview
| Event Name | When to Fire | PrestaShop Context | Business Impact |
|---|---|---|---|
PageView |
Every page load | All pages | Audience building, retargeting |
ViewContent |
Product viewed | Product page | Product retargeting, dynamic ads |
AddToCart |
Item added to cart | Add to cart action | Cart abandonment campaigns |
InitiateCheckout |
Checkout started | Checkout page | Checkout abandonment |
AddPaymentInfo |
Payment info added | Payment step | Lower funnel optimization |
Purchase |
Order completed | Order confirmation | Conversion tracking, ROAS |
Search |
Site search performed | Search page | Search-based audiences |
AddToWishlist |
Item wishlisted | Wishlist action | Interest-based targeting |
Event Parameters
Standard Parameters:
{
content_name: "Product Name", // Product/page name
content_category: "Category", // Product category
content_ids: ["12345"], // Array of product IDs
content_type: "product", // product or product_group
value: 29.99, // Monetary value
currency: "USD", // ISO currency code
num_items: 1 // Quantity
}
Implementing Events with PrestaShop Hooks
ViewContent Event (Product Page)
Using Hook: displayFooterProduct
<?php
// In your Meta Pixel module
public function hookDisplayFooterProduct($params)
{
$product = $params['product'];
$product_obj = new Product($product->id, true, $this->context->language->id);
// Get category
$category = new Category($product_obj->id_category_default, $this->context->language->id);
// Get price
$price = $product_obj->getPrice(true);
// Get selected combination if any
$id_product_attribute = Tools::getValue('id_product_attribute', 0);
$event_data = array(
'content_name' => $product_obj->name,
'content_category' => $category->name,
'content_ids' => array((string)$product->id),
'content_type' => 'product',
'value' => (float)$price,
'currency' => $this->context->currency->iso_code
);
$this->context->smarty->assign(array(
'meta_event' => 'ViewContent',
'meta_event_data' => $event_data
));
return $this->display(__FILE__, 'views/templates/hook/meta-viewcontent.tpl');
}
{* views/templates/hook/meta-viewcontent.tpl *}
<script>
fbq('track', 'ViewContent', {
content_name: '{$meta_event_data.content_name|escape:'javascript':'UTF-8'}',
content_category: '{$meta_event_data.content_category|escape:'javascript':'UTF-8'}',
content_ids: ['{$meta_event_data.content_ids.0|escape:'javascript':'UTF-8'}'],
content_type: 'product',
value: {$meta_event_data.value|floatval},
currency: '{$meta_event_data.currency|escape:'javascript':'UTF-8'}'
});
</script>
AddToCart Event
Method 1: Server-Side Hook (actionCartSave)
<?php
// Hook: actionCartSave
public function hookActionCartSave($params)
{
if (!isset($params['cart'])) {
return;
}
$cart = $params['cart'];
$last_product = $cart->getLastProduct();
if (!$last_product) {
return;
}
// Get category
$category = new Category($last_product['id_category_default'], $this->context->language->id);
$event_data = array(
'event' => 'AddToCart',
'content_name' => $last_product['name'],
'content_category' => $category->name,
'content_ids' => array((string)$last_product['id_product']),
'content_type' => 'product',
'value' => (float)$last_product['price'],
'currency' => $this->context->currency->iso_code
);
// Store in cookie for JavaScript to read
$this->context->cookie->__set('meta_cart_event', json_encode($event_data));
}
JavaScript to Read and Fire:
// modules/custommeta/views/js/cart-tracking.js
document.addEventListener('DOMContentLoaded', function() {
// Check for cart event in cookie
var metaEvent = getCookie('meta_cart_event');
if (metaEvent && typeof fbq !== 'undefined') {
try {
var eventData = JSON.parse(metaEvent);
fbq('track', 'AddToCart', {
content_name: eventData.content_name,
content_category: eventData.content_category,
content_ids: eventData.content_ids,
content_type: eventData.content_type,
value: eventData.value,
currency: eventData.currency
});
// Clear cookie
document.cookie = 'meta_cart_event=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
} catch (e) {
console.error('Meta 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();
}
Method 2: Client-Side JavaScript (AJAX Cart)
// Track add to cart button clicks
document.addEventListener('DOMContentLoaded', function() {
// Listen for PrestaShop cart updates
prestashop.on('updateCart', function(event) {
if (event && event.reason && event.reason.linkAction === 'add-to-cart') {
var productData = getProductDataFromPage();
if (typeof fbq !== 'undefined') {
fbq('track', 'AddToCart', {
content_name: productData.name,
content_category: productData.category,
content_ids: [productData.id],
content_type: 'product',
value: parseFloat(productData.price),
currency: prestashop.currency.iso_code
});
}
}
});
// Direct button click tracking (backup)
var addToCartButtons = document.querySelectorAll('[data-button-action="add-to-cart"]');
addToCartButtons.forEach(function(button) {
button.addEventListener('click', function(e) {
var productData = getProductDataFromPage();
if (typeof fbq !== 'undefined') {
fbq('track', 'AddToCart', {
content_name: productData.name,
content_ids: [productData.id],
content_type: 'product',
value: parseFloat(productData.price),
currency: prestashop.currency.iso_code
});
}
});
});
});
function getProductDataFromPage() {
return {
id: prestashop.page.id_product || document.querySelector('[data-product-id]')?.dataset.productId,
name: document.querySelector('h1.product-title')?.textContent.trim(),
category: document.querySelector('.breadcrumb .category')?.textContent.trim() || '',
price: document.querySelector('[data-product-price]')?.dataset.productPrice ||
document.querySelector('.product-price .current-price-value')?.textContent.replace(/[^0-9.]/g, '')
};
}
InitiateCheckout Event
Hook: displayBeforeCheckout or displayShoppingCart
<?php
// Hook: displayBeforeCheckout
public function hookDisplayBeforeCheckout($params)
{
$cart = $this->context->cart;
$products = $cart->getProducts(true);
$content_ids = array();
$total_value = 0;
$num_items = 0;
foreach ($products as $product) {
$content_ids[] = (string)$product['id_product'];
$total_value += $product['price'] * $product['quantity'];
$num_items += $product['quantity'];
}
$event_data = array(
'content_ids' => $content_ids,
'content_type' => 'product',
'value' => (float)$cart->getOrderTotal(true),
'currency' => $this->context->currency->iso_code,
'num_items' => $num_items
);
$this->context->smarty->assign(array(
'meta_event' => 'InitiateCheckout',
'meta_event_data' => $event_data
));
return $this->display(__FILE__, 'views/templates/hook/meta-checkout.tpl');
}
Template:
{* views/templates/hook/meta-checkout.tpl *}
<script>
fbq('track', 'InitiateCheckout', {
content_ids: {$meta_event_data.content_ids|@json_encode nofilter},
content_type: 'product',
value: {$meta_event_data.value|floatval},
currency: '{$meta_event_data.currency|escape:'javascript':'UTF-8'}',
num_items: {$meta_event_data.num_items|intval}
});
</script>
AddPaymentInfo Event
JavaScript Implementation:
// 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 cartTotal = document.querySelector('.cart-total .value')?.textContent.replace(/[^0-9.]/g, '') || '0';
if (typeof fbq !== 'undefined') {
fbq('track', 'AddPaymentInfo', {
content_category: 'checkout',
value: parseFloat(cartTotal),
currency: prestashop.currency.iso_code
});
}
}
});
Purchase Event (Conversion)
Most critical event for campaign optimization:
<?php
// Hook: displayOrderConfirmation
public function hookDisplayOrderConfirmation($params)
{
$order = $params['order'];
// Get order products
$order_detail = $order->getOrderDetailList();
$content_ids = array();
$contents = array();
foreach ($order_detail as $product) {
$content_ids[] = (string)$product['product_id'];
$contents[] = array(
'id' => (string)$product['product_id'],
'quantity' => (int)$product['product_quantity'],
'item_price' => (float)$product['unit_price_tax_incl']
);
}
$event_data = array(
'content_ids' => $content_ids,
'contents' => $contents,
'content_type' => 'product',
'value' => (float)$order->total_paid_tax_incl,
'currency' => $this->context->currency->iso_code,
'num_items' => count($order_detail)
);
// Prevent duplicate on refresh
$this->context->smarty->assign(array(
'meta_event' => 'Purchase',
'meta_event_data' => $event_data,
'order_reference' => $order->reference
));
return $this->display(__FILE__, 'views/templates/hook/meta-purchase.tpl');
}
Template with Deduplication:
{* views/templates/hook/meta-purchase.tpl *}
<script>
// Prevent duplicate purchase tracking on page refresh
var orderRef = '{$order_reference|escape:'javascript':'UTF-8'}';
var cookieName = 'meta_purchase_' + orderRef;
function getCookie(name) {
var value = "; " + document.cookie;
var parts = value.split("; " + name + "=");
if (parts.length == 2) return parts.pop().split(";").shift();
}
if (!getCookie(cookieName) && typeof fbq !== 'undefined') {
fbq('track', 'Purchase', {
content_ids: {$meta_event_data.content_ids|@json_encode nofilter},
contents: {$meta_event_data.contents|@json_encode nofilter},
content_type: 'product',
value: {$meta_event_data.value|floatval},
currency: '{$meta_event_data.currency|escape:'javascript':'UTF-8'}',
num_items: {$meta_event_data.num_items|intval}
});
// Set cookie to prevent duplicate (expires in 24 hours)
document.cookie = cookieName + '=1; path=/; max-age=86400';
}
</script>
Search Event
Hook: actionSearch
<?php
// Hook: actionSearch
public function hookActionSearch($params)
{
$search_query = isset($params['search_query']) ? $params['search_query'] : Tools::getValue('s');
$event_data = array(
'search_string' => $search_query
);
$this->context->smarty->assign(array(
'meta_event' => 'Search',
'meta_event_data' => $event_data
));
return $this->display(__FILE__, 'views/templates/hook/meta-search.tpl');
}
Template:
<script>
fbq('track', 'Search', {
search_string: '{$meta_event_data.search_string|escape:'javascript':'UTF-8'}'
});
</script>
AddToWishlist Event
JavaScript Implementation:
// Track wishlist additions
document.addEventListener('click', function(e) {
if (e.target.matches('.wishlist-button-add, [data-action="add-to-wishlist"]')) {
var productId = e.target.dataset.productId ||
e.target.closest('[data-id-product]').dataset.idProduct;
var productName = e.target.dataset.productName ||
document.querySelector('.product-title').textContent.trim();
var productPrice = e.target.dataset.productPrice ||
document.querySelector('.product-price .price')?.textContent.replace(/[^0-9.]/g, '');
if (typeof fbq !== 'undefined') {
fbq('track', 'AddToWishlist', {
content_name: productName,
content_ids: [productId],
content_type: 'product',
value: parseFloat(productPrice) || 0,
currency: prestashop.currency.iso_code
});
}
}
});
Custom Events
CompleteRegistration Event
Hook: actionCustomerAccountAdd
<?php
// Hook: actionCustomerAccountAdd
public function hookActionCustomerAccountAdd($params)
{
$customer = $params['newCustomer'];
$event_data = array(
'content_name' => 'Account Registration',
'status' => 'completed'
);
// Store in cookie for template to read
$this->context->cookie->__set('meta_registration_event', json_encode($event_data));
}
JavaScript:
// Fire registration event
var regEvent = getCookie('meta_registration_event');
if (regEvent && typeof fbq !== 'undefined') {
try {
var eventData = JSON.parse(regEvent);
fbq('track', 'CompleteRegistration', {
content_name: eventData.content_name,
status: eventData.status
});
// Clear cookie
document.cookie = 'meta_registration_event=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
} catch (e) {
console.error('Meta registration event error:', e);
}
}
Lead Event (Newsletter Signup)
// Track newsletter subscriptions
var newsletterForm = document.querySelector('.block_newsletter form, #newsletter-form');
if (newsletterForm) {
newsletterForm.addEventListener('submit', function(e) {
if (typeof fbq !== 'undefined') {
fbq('track', 'Lead', {
content_name: 'Newsletter Subscription',
content_category: 'newsletter'
});
}
});
}
Contact Event (Contact Form)
// Track contact form submissions
var contactForm = document.querySelector('.contact-form, #contact-form');
if (contactForm) {
contactForm.addEventListener('submit', function(e) {
if (typeof fbq !== 'undefined') {
fbq('track', 'Contact', {
content_name: 'Contact Form Submission'
});
}
});
}
Using GTM for Meta Pixel Events
Recommended approach for flexibility:
Create Event Tags in GTM
Example: ViewContent Tag
Tag Type: Custom HTML
Tag Name: Meta Pixel - ViewContent
HTML:
<script>
fbq('track', 'ViewContent', {
content_name: {{Product Name}},
content_ids: [{{Product ID}}],
content_type: 'product',
value: {{Product Price}},
currency: {{Currency}}
});
</script>
Triggering: Page Type equals "product"
Use Data Layer Variables:
Create GTM variables that read from PrestaShop data layer:
- Product Name:
\{\{ecommerce.items.0.item_name\}\} - Product ID:
\{\{ecommerce.items.0.item_id\}\} - Product Price:
\{\{ecommerce.items.0.price\}\} - Currency:
\{\{ecommerce.currency\}\}
See GTM Data Layer guide for implementation.
Meta Conversions API (Server-Side Tracking)
Why Use Conversions API?
- Overcome iOS 14+ tracking limitations
- Improve event match quality
- Reduce impact of ad blockers
- Server-side redundancy for critical events
Implementation Overview
Requirements:
- Access Token from Facebook Business Settings
- Server-side capability (PHP)
- PrestaShop hooks for server-side events
Basic CAPI Implementation:
<?php
// Send purchase event to Conversions API
public function sendPurchaseToConversionsAPI($order)
{
$access_token = Configuration::get('META_CAPI_ACCESS_TOKEN');
$pixel_id = Configuration::get('CUSTOMMETA_PIXEL_ID');
if (empty($access_token) || empty($pixel_id)) {
return;
}
$customer = new Customer($order->id_customer);
$address = new Address($order->id_address_delivery);
// Get order products
$order_detail = $order->getOrderDetailList();
$content_ids = array();
$contents = array();
foreach ($order_detail as $product) {
$content_ids[] = (string)$product['product_id'];
$contents[] = array(
'id' => (string)$product['product_id'],
'quantity' => (int)$product['product_quantity'],
'item_price' => (float)$product['unit_price_tax_incl']
);
}
// Build event data
$event_data = array(
'event_name' => 'Purchase',
'event_time' => time(),
'event_source_url' => $this->context->link->getPageLink('order-confirmation', true),
'action_source' => 'website',
'user_data' => array(
'em' => array(hash('sha256', strtolower(trim($customer->email)))),
'fn' => array(hash('sha256', strtolower(trim($customer->firstname)))),
'ln' => array(hash('sha256', strtolower(trim($customer->lastname)))),
'ph' => array(hash('sha256', preg_replace('/[^0-9]/', '', $address->phone))),
'ct' => array(hash('sha256', strtolower(trim($address->city)))),
'zp' => array(hash('sha256', trim($address->postcode))),
'country' => array(hash('sha256', strtolower(trim($this->context->country->iso_code)))),
'client_ip_address' => $_SERVER['REMOTE_ADDR'],
'client_user_agent' => $_SERVER['HTTP_USER_AGENT']
),
'custom_data' => array(
'content_ids' => $content_ids,
'contents' => $contents,
'content_type' => 'product',
'value' => (float)$order->total_paid_tax_incl,
'currency' => $this->context->currency->iso_code,
'num_items' => count($order_detail)
)
);
$data = array(
'data' => array($event_data)
);
// Send to Conversions API
$url = "https://graph.facebook.com/v18.0/{$pixel_id}/events?access_token={$access_token}";
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json'));
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
// Log response for debugging
if ($http_code !== 200) {
PrestaShopLogger::addLog(
'Meta CAPI Error: ' . $response,
3,
null,
'Order',
$order->id
);
}
return $response;
}
Call in Order Confirmation Hook:
public function hookDisplayOrderConfirmation($params)
{
$order = $params['order'];
// Send to Conversions API
$this->sendPurchaseToConversionsAPI($order);
// Also fire browser pixel (for deduplication)
// ... browser pixel code ...
}
Event Deduplication
Prevent counting same event twice (browser + server):
// Browser-side: Include event_id
var eventId = 'order_' + orderReference + '_' + Date.now();
fbq('track', 'Purchase', {
// ... event data ...
}, {
eventID: eventId
});
// Server-side: Use same event_id
$event_data = array(
'event_name' => 'Purchase',
'event_id' => 'order_' . $order->reference . '_' . time(),
// ... rest of data ...
);
Testing Meta Pixel Events
Test Events Tool
In Facebook Events Manager:
- Navigate to Events Manager > Test Events
- Enter your store URL or use browser extension
- Browse store and perform actions
- Verify events appear in real-time
- Check event parameters are correct
Verify:
- ✅ Event name correct
- ✅ Parameters populated
- ✅ Values are numeric (not strings)
- ✅ Currency is 3-letter code
- ✅ content_ids are strings
Meta Pixel Helper
Chrome Extension checks:
- Events firing at correct times
- No pixel errors
- Multiple pixels detected if configured
- Parameter warnings
Browser Console Debugging
// Enable debug mode
fbq('set', 'autoConfig', false, 'YOUR_PIXEL_ID');
// Log all pixel events
var originalTrack = fbq.track;
fbq.track = function() {
console.log('Meta Pixel Track:', arguments);
return originalTrack.apply(this, arguments);
};
// Check event queue
fbq.queue
Common Issues and Solutions
Events Firing Multiple Times
Cause: Multiple implementations active
Fix:
- Check for duplicate pixel base code
- Verify only one module/implementation active
- Remove manual code if using module
Incorrect Event Values
Cause: Price formatting issues
Fix:
// Ensure numeric values
value: parseFloat(productPrice.replace(/[^0-9.]/g, ''))
// Not:
value: productPrice // May be string "$29.99"
Purchase Event Not Firing
Cause: Page caching, JavaScript errors, missing pixel
Fix:
- Clear PrestaShop cache
- Check browser console for errors
- Verify pixel base code loads before event
- Test order confirmation page directly
Low Event Match Quality
Cause: Missing customer data, not using advanced matching
Fix:
- Enable advanced matching with hashed customer data
- Implement Conversions API
- Include more user_data parameters
Performance Optimization
Batch Related Events:
// Instead of multiple fbq calls
fbq('track', 'AddToCart', data1);
fbq('track', 'CustomEvent', data2);
// Use single call with custom event
fbq('trackSingle', 'PIXEL_ID', 'AddToCart', data1);
Lazy Load Non-Critical Events:
// Load pixel after user interaction
var pixelLoaded = false;
function ensurePixelLoaded() {
if (!pixelLoaded && typeof fbq === 'undefined') {
loadMetaPixel();
pixelLoaded = true;
}
}
// Call before firing events
ensurePixelLoaded();
fbq('track', 'ViewContent', data);
Next Steps
- Meta Pixel Setup - Initial pixel installation
- GTM Data Layer - Use data layer for events
- Events Not Firing - Debug tracking issues