Meta Pixel Setup on Drupal
Overview
This guide covers implementing the Meta Pixel (formerly Facebook Pixel) on Drupal sites for tracking user behavior, conversions, and creating remarketing audiences. Learn module-based and custom implementation approaches that work with Drupal's caching system.
Method 1: Using the Meta Pixel Module
Installation
# Install Meta Pixel module (if available)
composer require drupal/facebook_pixel
# Enable the module
drush en facebook_pixel -y
# Clear cache
drush cr
Note: If the module isn't available in Drupal 9/10, use the manual implementation method below.
Configuration
Navigate to Configuration → System → Facebook Pixel (/admin/config/system/facebook-pixel)
Pixel ID:
123456789012345
Pages to Track:
- All pages (leave blank)
Pages to Exclude:
/admin*
/user/*/edit
/node/add*
/node/*/edit
Role Exclusions:
☐ Track Administrator
☑ Track Authenticated User
☑ Track Anonymous User
Method 2: Manual Theme Implementation (Recommended)
Step 1: Add Pixel Base Code to Theme
File: themes/custom/mytheme/mytheme.libraries.yml
meta-pixel:
version: 1.x
js:
js/meta-pixel.js: { weight: -10 }
dependencies:
- core/drupal
- core/drupalSettings
File: themes/custom/mytheme/js/meta-pixel.js
/**
* @file
* Meta Pixel implementation.
*/
(function (Drupal, drupalSettings) {
'use strict';
// Initialize Meta Pixel
!function(f,b,e,v,n,t,s)
{if(f.fbq)return;n=f.fbq=function(){n.callMethod?
n.callMethod.apply(n,arguments):n.queue.push(arguments)};
if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';
n.queue=[];t=b.createElement(e);t.async=!0;
t.src=v;s=b.getElementsByTagName(e)[0];
s.parentNode.insertBefore(t,s)}(window, document,'script',
'https://connect.facebook.net/en_US/fbevents.js');
// Get pixel ID from Drupal settings
var pixelId = drupalSettings.metaPixel?.pixelId || '123456789012345';
// Initialize pixel
fbq('init', pixelId);
// Track PageView
fbq('track', 'PageView');
// Add user data if available (for Advanced Matching)
if (drupalSettings.metaPixel?.userData) {
fbq('init', pixelId, drupalSettings.metaPixel.userData);
}
})(Drupal, drupalSettings);
Step 2: Attach Library via Theme
File: themes/custom/mytheme/mytheme.theme
<?php
/**
* Implements hook_page_attachments().
*/
function mytheme_page_attachments(array &$attachments) {
// Don't load on admin pages
if (\Drupal::service('router.admin_context')->isAdminRoute()) {
return;
}
// Don't track administrators
$current_user = \Drupal::currentUser();
if ($current_user->hasPermission('administer site configuration')) {
return;
}
// Attach Meta Pixel library
$attachments['#attached']['library'][] = 'mytheme/meta-pixel';
// Pass pixel configuration to JavaScript
$attachments['#attached']['drupalSettings']['metaPixel'] = [
'pixelId' => '123456789012345', // Replace with your Pixel ID
'advancedMatching' => TRUE,
];
// Add cache contexts
$attachments['#cache']['contexts'][] = 'user.roles';
$attachments['#cache']['contexts'][] = 'url.path';
}
Advanced Matching (Enhanced Conversions)
Hashing User Data
File: themes/custom/mytheme/mytheme.theme
<?php
/**
* Implements hook_page_attachments().
*/
function mytheme_page_attachments(array &$attachments) {
if (\Drupal::service('router.admin_context')->isAdminRoute()) {
return;
}
$current_user = \Drupal::currentUser();
// Prepare user data for Advanced Matching
$user_data = [];
if (!$current_user->isAnonymous()) {
/** @var \Drupal\user\UserInterface $user */
$user = \Drupal\user\Entity\User::load($current_user->id());
// Hash email (required for Advanced Matching)
if ($user->getEmail()) {
$user_data['em'] = hash('sha256', strtolower(trim($user->getEmail())));
}
// Hash phone (if available)
if ($user->hasField('field_phone') && !$user->get('field_phone')->isEmpty()) {
$phone = $user->get('field_phone')->value;
// Remove non-numeric characters
$phone = preg_replace('/[^0-9]/', '', $phone);
$user_data['ph'] = hash('sha256', $phone);
}
// Hash first name
if ($user->hasField('field_first_name') && !$user->get('field_first_name')->isEmpty()) {
$user_data['fn'] = hash('sha256', strtolower(trim($user->get('field_first_name')->value)));
}
// Hash last name
if ($user->hasField('field_last_name') && !$user->get('field_last_name')->isEmpty()) {
$user_data['ln'] = hash('sha256', strtolower(trim($user->get('field_last_name')->value)));
}
// External ID (user ID)
$user_data['external_id'] = $user->id();
}
$attachments['#attached']['library'][] = 'mytheme/meta-pixel';
$attachments['#attached']['drupalSettings']['metaPixel'] = [
'pixelId' => '123456789012345',
'userData' => $user_data,
];
}
Updated JavaScript:
// Initialize with Advanced Matching
if (drupalSettings.metaPixel?.userData) {
fbq('init', drupalSettings.metaPixel.pixelId, drupalSettings.metaPixel.userData);
} else {
fbq('init', drupalSettings.metaPixel.pixelId);
}
fbq('track', 'PageView');
Environment-Specific Configuration
File: settings.php
<?php
// Meta Pixel ID per environment
$config['mytheme.settings']['meta_pixel_id'] = '123456789012345'; // Default
// Production environment
if (getenv('PANTHEON_ENVIRONMENT') === 'live') {
$config['mytheme.settings']['meta_pixel_id'] = '111111111111111';
}
// Staging environment
elseif (getenv('PANTHEON_ENVIRONMENT') === 'test') {
$config['mytheme.settings']['meta_pixel_id'] = '222222222222222';
}
// Development (disable tracking)
else {
$config['mytheme.settings']['meta_pixel_id'] = NULL;
}
Update theme file:
<?php
function mytheme_page_attachments(array &$attachments) {
$pixel_id = \Drupal::config('mytheme.settings')->get('meta_pixel_id');
// Don't load if no pixel ID (dev environment)
if (!$pixel_id) {
return;
}
if (\Drupal::service('router.admin_context')->isAdminRoute()) {
return;
}
$attachments['#attached']['library'][] = 'mytheme/meta-pixel';
$attachments['#attached']['drupalSettings']['metaPixel'] = [
'pixelId' => $pixel_id,
];
}
Noscript Fallback
Add to html.html.twig
File: themes/custom/mytheme/templates/html.html.twig
<body{{ attributes }}>
{# Meta Pixel noscript fallback #}
{% if not is_admin and meta_pixel_id %}
<noscript>
<img height="1" width="1" style="display:none"
src="https://www.facebook.com/tr?id={{ meta_pixel_id }}&ev=PageView&noscript=1"/>
</noscript>
{% endif %}
<a href="#main-content" class="visually-hidden focusable skip-link">
{{ 'Skip to main content'|t }}
</a>
{{ page_top }}
{{ page }}
{{ page_bottom }}
<js-bottom-placeholder token="{{ placeholder_token }}">
</body>
Update mytheme.theme:
<?php
function mytheme_preprocess_html(&$variables) {
$variables['is_admin'] = \Drupal::service('router.admin_context')->isAdminRoute();
$variables['meta_pixel_id'] = \Drupal::config('mytheme.settings')->get('meta_pixel_id');
}
GDPR & Privacy Compliance
Cookie Consent Integration
File: js/meta-pixel.js (updated)
(function (Drupal, drupalSettings) {
'use strict';
/**
* Initialize Meta Pixel with consent check.
*/
Drupal.behaviors.metaPixelConsent = {
attach: function (context, settings) {
// Check if EU Cookie Compliance module is installed
if (typeof Drupal.eu_cookie_compliance !== 'undefined') {
// Wait for consent
document.addEventListener('eu_cookie_compliance.changeStatus', function(event) {
if (event.detail.status === 'allow' &&
(event.detail.categories?.marketing || event.detail.categories?.advertising)) {
initializeMetaPixel();
}
});
// Check if already consented
var consentService = Drupal.eu_cookie_compliance || {};
if (consentService.hasAgreed && consentService.hasAgreed()) {
initializeMetaPixel();
}
} else {
// No consent management - initialize immediately
initializeMetaPixel();
}
function initializeMetaPixel() {
// Prevent double initialization
if (window._fbq) return;
!function(f,b,e,v,n,t,s)
{if(f.fbq)return;n=f.fbq=function(){n.callMethod?
n.callMethod.apply(n,arguments):n.queue.push(arguments)};
if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';
n.queue=[];t=b.createElement(e);t.async=!0;
t.src=v;s=b.getElementsByTagName(e)[0];
s.parentNode.insertBefore(t,s)}(window, document,'script',
'https://connect.facebook.net/en_US/fbevents.js');
var pixelId = settings.metaPixel?.pixelId;
if (!pixelId) return;
if (settings.metaPixel?.userData) {
fbq('init', pixelId, settings.metaPixel.userData);
} else {
fbq('init', pixelId);
}
fbq('track', 'PageView');
}
}
};
})(Drupal, drupalSettings);
Limited Data Use (California Privacy)
// For California users (CCPA compliance)
if (drupalSettings.user?.region === 'CA') {
fbq('dataProcessingOptions', ['LDU'], 1, 1000); // California state code
}
// Or disable data processing entirely
fbq('dataProcessingOptions', []);
Server-Side Events API (Advanced)
Setup Conversions API
Install Facebook Business SDK:
composer require facebook/php-business-sdk
File: modules/custom/meta_pixel/src/MetaPixelService.php
<?php
namespace Drupal\meta_pixel;
use FacebookAds\Api;
use FacebookAds\Object\ServerSide\Event;
use FacebookAds\Object\ServerSide\EventRequest;
use FacebookAds\Object\ServerSide\UserData;
/**
* Meta Pixel Server-Side Events service.
*/
class MetaPixelService {
/**
* Send server-side event to Meta.
*/
public function sendEvent($event_name, $event_data = []) {
$access_token = 'YOUR_ACCESS_TOKEN';
$pixel_id = '123456789012345';
Api::init(null, null, $access_token);
// Build user data
$user_data = (new UserData())
->setClientIpAddress($_SERVER['REMOTE_ADDR'])
->setClientUserAgent($_SERVER['HTTP_USER_AGENT'])
->setFbp($this->getFbpCookie())
->setFbc($this->getFbcCookie());
// Add hashed user info if available
$current_user = \Drupal::currentUser();
if (!$current_user->isAnonymous()) {
$user = \Drupal\user\Entity\User::load($current_user->id());
if ($user->getEmail()) {
$user_data->setEmail(hash('sha256', strtolower(trim($user->getEmail()))));
}
}
// Build event
$event = (new Event())
->setEventName($event_name)
->setEventTime(time())
->setEventSourceUrl(\Drupal::request()->getUri())
->setUserData($user_data)
->setCustomData($event_data);
// Send event
$request = (new EventRequest($pixel_id))
->setEvents([$event]);
try {
$response = $request->execute();
\Drupal::logger('meta_pixel')->info('Event sent: @event', ['@event' => $event_name]);
} catch (\Exception $e) {
\Drupal::logger('meta_pixel')->error('Error sending event: @error', ['@error' => $e->getMessage()]);
}
}
/**
* Get _fbp cookie value.
*/
protected function getFbpCookie() {
return $_COOKIE['_fbp'] ?? null;
}
/**
* Get _fbc cookie value.
*/
protected function getFbcCookie() {
return $_COOKIE['_fbc'] ?? null;
}
}
Testing Your Implementation
1. Meta Pixel Helper Extension
Install Meta Pixel Helper Chrome extension:
- Verify pixel fires on page load
- Check for PageView event
- Verify pixel ID is correct
- Identify any errors or warnings
2. Events Manager
- Open Meta Business Suite → Events Manager
- Select your pixel
- View Test Events tab
- Browse your Drupal site
- Verify events appear in real-time
3. Browser Developer Tools
Network Tab:
Filter: facebook.com
Look for: /tr?id=YOUR_PIXEL_ID
Console:
// Check if fbq is defined
console.log(typeof fbq);
// View pixel queue
console.log(fbq.queue);
// Manually trigger test event
fbq('track', 'TestEvent');
Common Issues & Solutions
Pixel Not Loading
Clear Drupal cache:
drush crCheck JavaScript errors in console
Verify library is attached:
- View page source
- Search for "fbevents.js"
Check admin route exclusion:
var_dump(\Drupal::service('router.admin_context')->isAdminRoute());
Events Not Showing in Events Manager
- Check pixel ID is correct
- Verify user isn't using ad blocker
- Check browser privacy settings
- Wait 20 minutes for events to appear (not real-time)
Duplicate Events
Check for multiple pixel implementations:
- Module configuration
- Theme implementation
- GTM also loading Meta Pixel
Advanced Matching Not Working
- Verify email is hashed (SHA-256)
- Check data format:
- Email: lowercase, trimmed
- Phone: numbers only
- Name: lowercase, trimmed
- Test in Events Manager → Test Events
Performance Optimization
1. Async Loading
Pixel already loads asynchronously by default.
2. Preconnect to Facebook
<?php
function mytheme_page_attachments(array &$attachments) {
$attachments['#attached']['html_head_link'][] = [
[
'rel' => 'preconnect',
'href' => 'https://connect.facebook.net',
],
TRUE
];
$attachments['#attached']['html_head_link'][] = [
[
'rel' => 'dns-prefetch',
'href' => 'https://connect.facebook.net',
],
TRUE
];
}
3. Delayed Loading (Non-Critical)
// Load pixel after page interactive
if ('requestIdleCallback' in window) {
requestIdleCallback(initializeMetaPixel);
} else {
setTimeout(initializeMetaPixel, 2000);
}
BigPipe Compatibility
Meta Pixel works with BigPipe when loaded via hook_page_attachments() as shown above. The pixel initialization happens in the <head> before BigPipe streaming begins.
If experiencing issues:
<?php
function mytheme_page_attachments(array &$attachments) {
// Use html_head for early loading (before BigPipe)
$attachments['#attached']['html_head'][] = [
[
'#type' => 'html_tag',
'#tag' => 'script',
'#value' => file_get_contents(drupal_get_path('theme', 'mytheme') . '/js/meta-pixel-inline.js'),
'#weight' => -100,
],
'meta_pixel_inline'
];
}