Meta Pixel Event Tracking on Drupal
Overview
This guide covers implementing Meta Pixel standard and custom events for Drupal-specific features including Webforms, Drupal Commerce, user actions, and content interactions. Learn how to track conversions, build audiences, and optimize Facebook/Instagram ad campaigns.
Standard Events Overview
Meta provides standard events for common actions:
| Event | Description | Use Case |
|---|---|---|
ViewContent |
Page/product view | Content engagement |
Search |
Search performed | Search behavior |
AddToCart |
Item added to cart | Shopping intent |
InitiateCheckout |
Checkout started | Purchase intent |
Purchase |
Purchase completed | Conversion |
Lead |
Form submitted | Lead generation |
CompleteRegistration |
User registered | Account creation |
Contact |
Contact initiated | Communication |
Webform Event Tracking
Track Form Submissions as Leads
File: modules/custom/meta_pixel_events/meta_pixel_events.module
<?php
use Drupal\webform\WebformSubmissionInterface;
/**
* Implements hook_webform_submission_insert().
*/
function meta_pixel_events_webform_submission_insert(WebformSubmissionInterface $submission) {
$webform = $submission->getWebform();
$webform_id = $webform->id();
$webform_title = $webform->label();
// Determine event type based on webform ID
$event_name = 'Lead'; // Default to Lead event
// Custom mapping for specific forms
$event_mapping = [
'contact' => 'Contact',
'newsletter' => 'Subscribe',
'quote_request' => 'Lead',
'demo_request' => 'Schedule',
];
if (isset($event_mapping[$webform_id])) {
$event_name = $event_mapping[$webform_id];
}
// Get submission data
$data = $submission->getData();
// Store event in session for JavaScript
$_SESSION['meta_pixel_events'][] = [
'event' => $event_name,
'parameters' => [
'content_name' => $webform_title,
'content_category' => 'webform',
'value' => 1.00, // Assign value for lead tracking
'currency' => 'USD',
],
];
}
/**
* Implements hook_page_attachments().
*/
function meta_pixel_events_page_attachments(array &$attachments) {
if (!empty($_SESSION['meta_pixel_events'])) {
$attachments['#attached']['drupalSettings']['metaPixelEvents'] = $_SESSION['meta_pixel_events'];
$attachments['#attached']['library'][] = 'meta_pixel_events/event_tracker';
unset($_SESSION['meta_pixel_events']);
}
}
File: modules/custom/meta_pixel_events/meta_pixel_events.libraries.yml
event_tracker:
version: 1.x
js:
js/event-tracker.js: {}
dependencies:
- core/drupal
- core/drupalSettings
File: modules/custom/meta_pixel_events/js/event-tracker.js
(function (Drupal, drupalSettings) {
'use strict';
Drupal.behaviors.metaPixelEventTracker = {
attach: function (context, settings) {
if (settings.metaPixelEvents && settings.metaPixelEvents.length > 0) {
settings.metaPixelEvents.forEach(function(eventData) {
if (typeof fbq !== 'undefined') {
fbq('track', eventData.event, eventData.parameters);
}
});
// Clear events after firing
delete drupalSettings.metaPixelEvents;
}
}
};
})(Drupal, drupalSettings);
Client-Side Form Tracking
File: themes/custom/mytheme/js/meta-pixel-forms.js
(function (Drupal, once) {
'use strict';
Drupal.behaviors.metaPixelFormTracking = {
attach: function (context, settings) {
// Track all webform submissions
once('meta-form-tracking', 'form.webform-submission-form', context).forEach(function(form) {
var formId = form.getAttribute('data-webform-id') || form.id;
var formTitle = form.querySelector('.webform-title')?.textContent || formId;
// Track form start on first interaction
var formStarted = false;
form.addEventListener('focusin', function() {
if (!formStarted && typeof fbq !== 'undefined') {
formStarted = true;
fbq('trackCustom', 'FormStart', {
content_name: formTitle,
form_id: formId
});
}
}, { once: true });
// Track successful submission
form.addEventListener('submit', function(event) {
if (typeof fbq !== 'undefined') {
fbq('track', 'Lead', {
content_name: formTitle,
content_category: 'webform',
value: 1.00,
currency: 'USD'
});
}
});
});
}
};
})(Drupal, once);
Drupal Commerce Event Tracking
View Product (ViewContent)
<?php
/**
* Implements hook_page_attachments().
*/
function meta_pixel_events_page_attachments(array &$attachments) {
$route_match = \Drupal::routeMatch();
// Track product views
if ($route_match->getRouteName() === 'entity.commerce_product.canonical') {
/** @var \Drupal\commerce_product\Entity\ProductInterface $product */
$product = $route_match->getParameter('commerce_product');
if ($product) {
$variation = $product->getDefaultVariation();
if ($variation) {
$price = $variation->getPrice();
$attachments['#attached']['drupalSettings']['metaPixelEvents'][] = [
'event' => 'ViewContent',
'parameters' => [
'content_type' => 'product',
'content_ids' => [$variation->getSku()],
'content_name' => $product->label(),
'content_category' => _meta_pixel_get_product_category($product),
'value' => (float) $price->getNumber(),
'currency' => $price->getCurrencyCode(),
],
];
$attachments['#attached']['library'][] = 'meta_pixel_events/event_tracker';
}
}
}
}
Add to Cart
<?php
use Drupal\commerce_cart\Event\CartEvents;
use Drupal\commerce_cart\Event\CartEntityAddEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Meta Pixel Commerce Event Subscriber.
*/
class MetaPixelCommerceSubscriber implements EventSubscriberInterface {
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
return [
CartEvents::CART_ENTITY_ADD => ['onAddToCart', -100],
];
}
/**
* Track Add to Cart event.
*/
public function onAddToCart(CartEntityAddEvent $event) {
$order_item = $event->getOrderItem();
$variation = $order_item->getPurchasedEntity();
$product = $variation->getProduct();
$quantity = (float) $order_item->getQuantity();
$_SESSION['meta_pixel_events'][] = [
'event' => 'AddToCart',
'parameters' => [
'content_type' => 'product',
'content_ids' => [$variation->getSku()],
'content_name' => $product->label(),
'content_category' => $this->getProductCategory($product),
'value' => (float) $variation->getPrice()->getNumber() * $quantity,
'currency' => $variation->getPrice()->getCurrencyCode(),
],
];
}
/**
* Get product category.
*/
protected function getProductCategory($product) {
if ($product->hasField('field_category') && !$product->get('field_category')->isEmpty()) {
return $product->get('field_category')->entity->label();
}
return '';
}
}
Register service:
File: meta_pixel_events.services.yml
services:
meta_pixel_events.commerce_subscriber:
class: Drupal\meta_pixel_events\EventSubscriber\MetaPixelCommerceSubscriber
tags:
- { name: event_subscriber }
Initiate Checkout
<?php
use Drupal\commerce_checkout\Event\CheckoutEvents;
/**
* Track checkout initiation.
*/
public function onCheckoutStart(CheckoutEvent $event) {
$order = $event->getOrder();
$content_ids = [];
$contents = [];
foreach ($order->getItems() as $order_item) {
$variation = $order_item->getPurchasedEntity();
$product = $variation->getProduct();
$content_ids[] = $variation->getSku();
$contents[] = [
'id' => $variation->getSku(),
'quantity' => (float) $order_item->getQuantity(),
'item_price' => (float) $variation->getPrice()->getNumber(),
];
}
$_SESSION['meta_pixel_events'][] = [
'event' => 'InitiateCheckout',
'parameters' => [
'content_type' => 'product',
'content_ids' => $content_ids,
'contents' => $contents,
'value' => (float) $order->getTotalPrice()->getNumber(),
'currency' => $order->getTotalPrice()->getCurrencyCode(),
'num_items' => count($order->getItems()),
],
];
}
Purchase
<?php
use Drupal\state_machine\Event\WorkflowTransitionEvent;
/**
* Track completed purchases.
*/
public function onOrderPlace(WorkflowTransitionEvent $event) {
/** @var \Drupal\commerce_order\Entity\OrderInterface $order */
$order = $event->getEntity();
// Prevent duplicate tracking
if ($order->getData('meta_pixel_tracked')) {
return;
}
$content_ids = [];
$contents = [];
foreach ($order->getItems() as $order_item) {
$variation = $order_item->getPurchasedEntity();
$content_ids[] = $variation->getSku();
$contents[] = [
'id' => $variation->getSku(),
'quantity' => (float) $order_item->getQuantity(),
'item_price' => (float) $variation->getPrice()->getNumber(),
];
}
$_SESSION['meta_pixel_events'][] = [
'event' => 'Purchase',
'parameters' => [
'content_type' => 'product',
'content_ids' => $content_ids,
'contents' => $contents,
'value' => (float) $order->getTotalPrice()->getNumber(),
'currency' => $order->getTotalPrice()->getCurrencyCode(),
'num_items' => count($order->getItems()),
],
];
// Mark as tracked
$order->setData('meta_pixel_tracked', TRUE);
$order->save();
}
User Behavior Tracking
User Registration
<?php
use Drupal\user\UserInterface;
/**
* Implements hook_user_insert().
*/
function meta_pixel_events_user_insert(UserInterface $account) {
$_SESSION['meta_pixel_events'][] = [
'event' => 'CompleteRegistration',
'parameters' => [
'content_name' => 'User Registration',
'status' => 'completed',
],
];
}
User Login
<?php
/**
* Implements hook_user_login().
*/
function meta_pixel_events_user_login(UserInterface $account) {
$_SESSION['meta_pixel_events'][] = [
'event' => 'CustomizeProduct', // Or use custom event
'parameters' => [
'content_name' => 'User Login',
'user_type' => implode(',', $account->getRoles(TRUE)),
],
];
}
Search Tracking
Site Search
<?php
/**
* Track search events.
*/
function meta_pixel_events_page_attachments(array &$attachments) {
$route_name = \Drupal::routeMatch()->getRouteName();
// Track Drupal core search
if ($route_name === 'search.view') {
$keys = \Drupal::request()->query->get('keys');
if ($keys) {
$attachments['#attached']['drupalSettings']['metaPixelEvents'][] = [
'event' => 'Search',
'parameters' => [
'search_string' => $keys,
'content_category' => 'site_search',
],
];
$attachments['#attached']['library'][] = 'meta_pixel_events/event_tracker';
}
}
}
Product Search (Views)
(function (Drupal, once) {
'use strict';
Drupal.behaviors.metaPixelProductSearch = {
attach: function (context, settings) {
once('meta-product-search', 'form.views-exposed-form[id*="commerce"]', context).forEach(function(form) {
form.addEventListener('submit', function() {
var searchInput = form.querySelector('input[type="search"], input[name*="keys"]');
var searchTerm = searchInput ? searchInput.value : '';
if (searchTerm && typeof fbq !== 'undefined') {
fbq('track', 'Search', {
search_string: searchTerm,
content_category: 'product_search'
});
}
});
});
}
};
})(Drupal, once);
Content Interaction Tracking
Video Engagement
(function (Drupal, once) {
'use strict';
Drupal.behaviors.metaPixelVideoTracking = {
attach: function (context, settings) {
once('meta-video-tracking', 'video', context).forEach(function(video) {
var videoTitle = video.getAttribute('title') || 'Video';
var tracked = false;
// Track when video plays
video.addEventListener('play', function() {
if (!tracked && typeof fbq !== 'undefined') {
tracked = true;
fbq('trackCustom', 'VideoPlay', {
content_name: videoTitle,
content_type: 'video'
});
}
});
// Track completion
video.addEventListener('ended', function() {
if (typeof fbq !== 'undefined') {
fbq('trackCustom', 'VideoComplete', {
content_name: videoTitle,
content_type: 'video'
});
}
});
});
}
};
})(Drupal, once);
Download Tracking
(function (Drupal, once) {
'use strict';
Drupal.behaviors.metaPixelDownloadTracking = {
attach: function (context, settings) {
var fileExtensions = /\.(pdf|docx?|xlsx?|zip|mp4|mp3)$/i;
document.addEventListener('click', function(event) {
var target = event.target.closest('a');
if (!target) return;
var href = target.href;
if (href && fileExtensions.test(href)) {
var fileName = href.split('/').pop().split('?')[0];
if (typeof fbq !== 'undefined') {
fbq('trackCustom', 'FileDownload', {
content_name: fileName,
content_type: 'download',
download_url: href
});
}
}
}, true);
}
};
})(Drupal, once);
Custom Events
Newsletter Subscription
<?php
/**
* Track newsletter signups.
*/
function meta_pixel_events_webform_submission_insert(WebformSubmissionInterface $submission) {
$webform_id = $submission->getWebform()->id();
if ($webform_id === 'newsletter') {
$_SESSION['meta_pixel_events'][] = [
'event' => 'Subscribe', // Custom event
'parameters' => [
'content_name' => 'Newsletter',
'content_category' => 'email_list',
'value' => 1.00,
'currency' => 'USD',
'predicted_ltv' => 50.00, // Estimated lifetime value
],
];
}
}
Article Reading Time
(function (Drupal, once) {
'use strict';
Drupal.behaviors.metaPixelReadingTime = {
attach: function (context, settings) {
if (!document.body.classList.contains('page-node-type-article')) {
return;
}
once('meta-reading-time', 'body', context).forEach(function() {
var startTime = Date.now();
var tracked = {
'30s': false,
'60s': false,
'120s': false
};
var articleTitle = document.querySelector('.page-title')?.textContent || 'Article';
setInterval(function() {
var elapsed = Math.floor((Date.now() - startTime) / 1000);
if (elapsed >= 30 && !tracked['30s'] && typeof fbq !== 'undefined') {
tracked['30s'] = true;
fbq('trackCustom', 'ArticleRead30s', {
content_name: articleTitle,
reading_time: 30
});
}
if (elapsed >= 60 && !tracked['60s'] && typeof fbq !== 'undefined') {
tracked['60s'] = true;
fbq('trackCustom', 'ArticleRead60s', {
content_name: articleTitle,
reading_time: 60
});
}
if (elapsed >= 120 && !tracked['120s'] && typeof fbq !== 'undefined') {
tracked['120s'] = true;
fbq('trackCustom', 'ArticleRead120s', {
content_name: articleTitle,
reading_time: 120
});
}
}, 5000); // Check every 5 seconds
});
}
};
})(Drupal, once);
Dynamic Ads Product Catalog
Automatic Product Feed
For Dynamic Ads, use product catalog integration:
Install Commerce Feeds module:
composer require drupal/commerce_feeds drush en commerce_feeds -yCreate product feed view:
Configure catalog in Meta Business Manager:
- Upload feed URL
- Map fields to Meta's schema
- Set update schedule
Server-Side Event Tracking
Send Events via Conversions API
<?php
use FacebookAds\Object\ServerSide\Event;
use FacebookAds\Object\ServerSide\EventRequest;
use FacebookAds\Object\ServerSide\UserData;
use FacebookAds\Object\ServerSide\Content;
use FacebookAds\Object\ServerSide\CustomData;
/**
* Send purchase event to Conversions API.
*/
public function sendPurchaseEvent($order) {
$pixel_id = '123456789012345';
$access_token = 'YOUR_ACCESS_TOKEN';
// Build user data
$user_data = (new UserData())
->setClientIpAddress($_SERVER['REMOTE_ADDR'])
->setClientUserAgent($_SERVER['HTTP_USER_AGENT'])
->setFbp($_COOKIE['_fbp'] ?? null);
// Add customer info
$customer = $order->getCustomer();
if ($customer && $customer->getEmail()) {
$user_data->setEmail(hash('sha256', strtolower(trim($customer->getEmail()))));
}
// Build contents array
$contents = [];
foreach ($order->getItems() as $order_item) {
$variation = $order_item->getPurchasedEntity();
$contents[] = (new Content())
->setProductId($variation->getSku())
->setQuantity((int) $order_item->getQuantity())
->setItemPrice((float) $variation->getPrice()->getNumber());
}
// Build custom data
$custom_data = (new CustomData())
->setContents($contents)
->setCurrency($order->getTotalPrice()->getCurrencyCode())
->setValue((float) $order->getTotalPrice()->getNumber());
// Build event
$event = (new Event())
->setEventName('Purchase')
->setEventTime(time())
->setEventSourceUrl(\Drupal::request()->getUri())
->setUserData($user_data)
->setCustomData($custom_data)
->setActionSource('website');
// Send to Meta
$request = (new EventRequest($pixel_id))
->setEvents([$event]);
try {
$response = $request->execute();
} catch (\Exception $e) {
\Drupal::logger('meta_pixel')->error('Error: @error', ['@error' => $e->getMessage()]);
}
}
Testing Events
1. Meta Pixel Helper
- Install Chrome extension
- Verify events fire correctly
- Check parameters are populated
- Identify duplicate events
2. Events Manager Test Events
- Open Meta Events Manager
- Click Test Events
- Browse your site
- Verify events appear with correct parameters
3. Debug in Console
// Enable debug mode
fbq('track', 'PageView', {}, {eventID: 'test123'});
// Log all pixel calls
var originalFbq = window.fbq;
window.fbq = function() {
console.log('Meta Pixel:', arguments);
originalFbq.apply(this, arguments);
};
Event Deduplication
When using both browser pixel and Conversions API:
<?php
// Generate event ID
$event_id = uniqid('event_', true);
// Store in session for browser
$_SESSION['meta_pixel_events'][] = [
'event' => 'Purchase',
'parameters' => [...],
'eventID' => $event_id,
];
// Send to Conversions API with same event ID
$event->setEventId($event_id);
if (eventData.eventID) {
fbq('track', eventData.event, eventData.parameters, {
eventID: eventData.eventID
});
} else {
fbq('track', eventData.event, eventData.parameters);
}