Meta Pixel Setup on Drupal | Blue Frog Docs

Meta Pixel Setup on Drupal

Complete guide to implementing Facebook/Meta Pixel on Drupal CMS

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

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

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

  1. Open Meta Business Suite → Events Manager
  2. Select your pixel
  3. View Test Events tab
  4. Browse your Drupal site
  5. 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

  1. Clear Drupal cache:

    drush cr
    
  2. Check JavaScript errors in console

  3. Verify library is attached:

    • View page source
    • Search for "fbevents.js"
  4. Check admin route exclusion:

    var_dump(\Drupal::service('router.admin_context')->isAdminRoute());
    

Events Not Showing in Events Manager

  1. Check pixel ID is correct
  2. Verify user isn't using ad blocker
  3. Check browser privacy settings
  4. 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

  1. Verify email is hashed (SHA-256)
  2. Check data format:
    • Email: lowercase, trimmed
    • Phone: numbers only
    • Name: lowercase, trimmed
  3. 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'
  ];
}

Resources


Next Steps

// SYS.FOOTER