Meta Pixel Event Tracking on Drupal | Blue Frog Docs

Meta Pixel Event Tracking on Drupal

Implement custom Meta Pixel events for Drupal forms, commerce, and user interactions

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

<?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:

  1. Install Commerce Feeds module:

    composer require drupal/commerce_feeds
    drush en commerce_feeds -y
    
  2. Create product feed view:

    • Format: CSV or XML
    • Fields: id, title, description, price, image_url, url
    • Path: /product-feed.csv
  3. 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

  1. Open Meta Events Manager
  2. Click Test Events
  3. Browse your site
  4. 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);

JavaScript:

if (eventData.eventID) {
  fbq('track', eventData.event, eventData.parameters, {
    eventID: eventData.eventID
  });
} else {
  fbq('track', eventData.event, eventData.parameters);
}

Resources


Next Steps

// SYS.FOOTER