Meta Pixel Event Tracking on Craft CMS | OpsBlu Docs

Meta Pixel Event Tracking on Craft CMS

Implement Meta Pixel event tracking in Craft CMS for lead generation, conversions, custom events, and Craft Commerce integration.

Learn how to track standard and custom Meta Pixel events in Craft CMS using Twig templates, including lead generation, content engagement, and e-commerce events.

Overview

Meta Pixel event tracking allows you to measure specific user actions on your Craft CMS website, optimize ad campaigns, and build custom audiences. This guide covers implementing both standard and custom events.

Standard Events

Lead Event

Track form submissions and lead generation:

{# templates/_forms/contact.twig #}

<form method="post" id="contact-form">
  {{ csrfInput() }}
  {{ actionInput('contact-form/send') }}

  <input type="text" name="fromName" placeholder="Name" required>
  <input type="email" name="fromEmail" placeholder="Email" required>
  <textarea name="message[body]" placeholder="Message" required></textarea>

  <button type="submit">Submit</button>
</form>

<script>
  document.getElementById('contact-form').addEventListener('submit', function(e) {
    fbq('track', 'Lead', {
      content_name: 'Contact Form',
      content_category: 'contact',
      value: 1.00,
      currency: 'USD'
    });
  });
</script>

Complete Registration Event

Track user registrations:

{# templates/account/register.twig #}

<form method="post" id="registration-form">
  {{ csrfInput() }}
  {{ actionInput('users/save-user') }}
  {{ redirectInput('account/welcome') }}

  <input type="text" name="username" required>
  <input type="email" name="email" required>
  <input type="password" name="password" required>

  <button type="submit">Register</button>
</form>

<script>
  document.getElementById('registration-form').addEventListener('submit', function(e) {
    fbq('track', 'CompleteRegistration', {
      content_name: 'User Registration',
      status: 'completed',
      value: 5.00,
      currency: 'USD'
    });
  });
</script>

ViewContent Event

Track when users view specific content:

{# templates/blog/_entry.twig #}

{% if entry is defined %}
<script>
  fbq('track', 'ViewContent', {
    content_ids: ['{{ entry.id }}'],
    content_type: '{{ entry.type.handle }}',
    content_name: '{{ entry.title|e('js') }}',
    content_category: '{{ entry.category.first().title ?? 'uncategorized' }}',
    {% if entry.author %}
    content_author: '{{ entry.author.fullName|e('js') }}',
    {% endif %}
    value: 1.00,
    currency: 'USD'
  });
</script>
{% endif %}

Search Event

Track search queries:

{# templates/search/index.twig #}

{% set searchQuery = craft.app.request.getParam('q') %}
{% set searchResults = craft.entries()
  .search(searchQuery)
  .all() %}

<script>
  fbq('track', 'Search', {
    search_string: '{{ searchQuery|e('js') }}',
    content_category: 'site_search',
    content_ids: [
      {% for result in searchResults|slice(0, 10) %}
        '{{ result.id }}'{{ not loop.last ? ',' : '' }}
      {% endfor %}
    ],
    value: {{ searchResults|length }},
    currency: 'USD'
  });
</script>

E-commerce Events (Craft Commerce)

ViewContent Event for Products

Track product page views:

{# templates/shop/product.twig #}

{% set variant = product.defaultVariant %}

<script>
  fbq('track', 'ViewContent', {
    content_ids: ['{{ variant.sku ?? variant.id }}'],
    content_name: '{{ product.title|e('js') }}',
    content_type: 'product',
    content_category: '{{ product.productType.name }}',
    value: {{ variant.price|number_format(2, '.', '') }},
    currency: '{{ craft.commerce.carts.cart.currency }}'
  });
</script>

AddToCart Event

Track when products are added to cart:

{# templates/shop/product.twig #}

<form method="post" id="add-to-cart-form">
  {{ csrfInput() }}
  {{ actionInput('commerce/cart/update-cart') }}

  <input type="hidden" name="purchasableId" value="{{ variant.id }}">
  <input type="number" name="qty" value="1" min="1" id="quantity">

  <button type="submit">Add to Cart</button>
</form>

<script>
  document.getElementById('add-to-cart-form').addEventListener('submit', function(e) {
    var quantity = parseInt(document.getElementById('quantity').value);

    fbq('track', 'AddToCart', {
      content_ids: ['{{ variant.sku ?? variant.id }}'],
      content_name: '{{ product.title|e('js') }}',
      content_type: 'product',
      content_category: '{{ product.productType.name }}',
      value: {{ variant.price|number_format(2, '.', '') }} * quantity,
      currency: '{{ cart.currency }}',
      num_items: quantity
    });
  });
</script>

InitiateCheckout Event

Track when users begin checkout:

{# templates/shop/checkout/index.twig #}

{% set cart = craft.commerce.carts.cart %}

<script>
  fbq('track', 'InitiateCheckout', {
    content_ids: [
      {% for item in cart.lineItems %}
        '{{ item.purchasable.sku ?? item.purchasable.id }}'{{ not loop.last ? ',' : '' }}
      {% endfor %}
    ],
    content_type: 'product',
    contents: [
      {% for item in cart.lineItems %}
        {
          id: '{{ item.purchasable.sku ?? item.purchasable.id }}',
          quantity: {{ item.qty }},
          item_price: {{ item.price|number_format(2, '.', '') }}
        }{{ not loop.last ? ',' : '' }}
      {% endfor %}
    ],
    num_items: {{ cart.totalQty }},
    value: {{ cart.total|number_format(2, '.', '') }},
    currency: '{{ cart.currency }}'
  });
</script>

AddPaymentInfo Event

Track payment information step:

{# templates/shop/checkout/payment.twig #}

<form method="post" id="payment-form">
  {{ csrfInput() }}

  <select name="gatewayId"
    {% for gateway in craft.commerce.gateways.allCustomerEnabledGateways %}
      <option value="{{ gateway.id }}">{{ gateway.name }}</option>
    {% endfor %}
  </select>

  <button type="submit">Complete Order</button>
</form>

<script>
  function trackPaymentInfo() {
    fbq('track', 'AddPaymentInfo', {
      content_category: 'checkout',
      value: {{ cart.total|number_format(2, '.', '') }},
      currency: '{{ cart.currency }}',
      num_items: {{ cart.totalQty }}
    });
  }
</script>

Purchase Event

Track completed purchases:

{# templates/shop/checkout/complete.twig #}

{% set order = craft.commerce.orders.number(craft.app.request.getParam('number')).one() %}

{% if order %}
<script>
  fbq('track', 'Purchase', {
    content_ids: [
      {% for item in order.lineItems %}
        '{{ item.purchasable.sku ?? item.purchasable.id }}'{{ not loop.last ? ',' : '' }}
      {% endfor %}
    ],
    content_type: 'product',
    contents: [
      {% for item in order.lineItems %}
        {
          id: '{{ item.purchasable.sku ?? item.purchasable.id }}',
          quantity: {{ item.qty }},
          item_price: {{ item.price|number_format(2, '.', '') }}
        }{{ not loop.last ? ',' : '' }}
      {% endfor %}
    ],
    num_items: {{ order.totalQty }},
    value: {{ order.total|number_format(2, '.', '') }},
    currency: '{{ order.currency }}'
  });
</script>

{# Prevent duplicate tracking #}
{% set orderTracked = craft.app.session.get('fb_order_' ~ order.number ~ '_tracked') %}
{% if not orderTracked %}
  {% do craft.app.session.set('fb_order_' ~ order.number ~ '_tracked', true) %}
{% endif %}
{% endif %}

Content Engagement Events

Download Tracking

Track asset downloads:

{# Track downloadable assets #}

{% for asset in entry.downloads.all() %}
  <a href="{{ asset.url }}"
     download 'Download', {
       content_name: '{{ asset.title|e('js') }}',
       content_category: 'downloads',
       content_type: '{{ asset.extension }}',
       value: 1.00,
       currency: 'USD'
     });">
    Download {{ asset.title }}
  </a>
{% endfor %}

Video Play Tracking

Track video interactions in Matrix blocks:

{# templates/_components/matrix-blocks.twig #}

{% for block in entry.contentBlocks.all() %}
  {% if block.type.handle == 'videoEmbed' %}
    <div class="video-block" id="video-{{ block.id }}">
      {{ block.videoUrl }}
    </div>

    <script>
      document.getElementById('video-{{ block.id }}').addEventListener('click', function() {
        fbq('trackCustom', 'VideoPlay', {
          content_name: '{{ block.videoTitle ?? 'Video'|e('js') }}',
          content_category: 'video',
          content_id: '{{ block.id }}',
          entry_id: '{{ entry.id }}'
        });
      });
    </script>
  {% endif %}
{% endfor %}

Track outbound links:

{# Track external links #}

<script>
  document.addEventListener('DOMContentLoaded', function() {
    // Track all external links
    var externalLinks = document.querySelectorAll('a[href^="http"]:not([href*="' + window.location.hostname + '"])');

    externalLinks.forEach(function(link) {
      link.addEventListener('click', function(e) {
        fbq('trackCustom', 'ExternalLinkClick', {
          content_name: this.textContent.trim(),
          link_url: this.href,
          page_location: window.location.href
        });
      });
    });
  });
</script>

Custom Events

Newsletter Subscription

Track newsletter signups:

{# templates/_forms/newsletter.twig #}

<form method="post" id="newsletter-form">
  {{ csrfInput() }}
  {{ actionInput('campaign/forms/submit') }}

  <input type="email" name="email" placeholder="Email" required>
  <button type="submit">Subscribe</button>
</form>

<script>
  document.getElementById('newsletter-form').addEventListener('submit', function(e) {
    fbq('trackCustom', 'Newsletter_Signup', {
      content_name: 'Newsletter Subscription',
      source_page: '{{ craft.app.request.pathInfo }}',
      value: 2.00,
      currency: 'USD'
    });
  });
</script>

Social Share Tracking

Track social media shares:

{# templates/_components/social-share.twig #}

<div class="social-share">
  <a href="https://twitter.com/intent/tweet?url={{ entry.url|url_encode }}"
     target="_blank" 'SocialShare', {
       method: 'twitter',
       content_type: '{{ entry.type.handle }}',
       content_id: '{{ entry.id }}',
       content_name: '{{ entry.title|e('js') }}'
     });">
    Share on Twitter
  </a>

  <a href="https://www.facebook.com/sharer/sharer.php?u={{ entry.url|url_encode }}"
     target="_blank" 'SocialShare', {
       method: 'facebook',
       content_type: '{{ entry.type.handle }}',
       content_id: '{{ entry.id }}',
       content_name: '{{ entry.title|e('js') }}'
     });">
    Share on Facebook
  </a>
</div>

Comment Submission

Track comment interactions:

{# If using a comments plugin like Comments by Verbb #}

<form method="post" id="comment-form">
  {{ csrfInput() }}
  {{ actionInput('comments/comments/save') }}

  <input type="hidden" name="elementId" value="{{ entry.id }}">
  <textarea name="comment" required></textarea>

  <button type="submit">Post Comment</button>
</form>

<script>
  document.getElementById('comment-form').addEventListener('submit', function(e) {
    fbq('trackCustom', 'CommentSubmit', {
      content_type: '{{ entry.type.handle }}',
      content_id: '{{ entry.id }}',
      content_name: '{{ entry.title|e('js') }}',
      value: 1.00,
      currency: 'USD'
    });
  });
</script>

Scroll Depth Tracking

Track content engagement by scroll depth:

{# templates/_analytics/scroll-tracking.twig #}

<script>
  (function() {
    var scrollDepths = [25, 50, 75, 100];
    var triggered = [];

    window.addEventListener('scroll', function() {
      var scrollPercent = Math.round(
        (window.scrollY / (document.documentElement.scrollHeight - window.innerHeight)) * 100
      );

      scrollDepths.forEach(function(depth) {
        if (scrollPercent >= depth && triggered.indexOf(depth) === -1) {
          fbq('trackCustom', 'ScrollDepth', {
            scroll_depth: depth,
            {% if entry is defined %}
            content_type: '{{ entry.type.handle }}',
            content_id: '{{ entry.id }}',
            content_name: '{{ entry.title|e('js') }}'
            {% endif %}
          });
          triggered.push(depth);
        }
      });
    });
  })();
</script>

Event Tracking with Freeform

Form Submission Tracking

Track Freeform submissions:

{# templates/_forms/freeform-form.twig #}

{{ craft.freeform.form("contactForm").render() }}

<script>
  document.addEventListener('freeform-ready', function(event) {
    var form = event.target.form;

    form.addEventListener('freeform-on-submit', function(event) {
      fbq('track', 'Lead', {
        content_name: 'Freeform - {{ form.name }}',
        content_category: 'form_submission',
        value: 5.00,
        currency: 'USD'
      });
    });

    form.addEventListener('freeform-on-success', function(event) {
      fbq('trackCustom', 'FormSuccess', {
        form_name: '{{ form.name }}',
        form_handle: '{{ form.handle }}',
        submission_id: event.detail.submissionId
      });
    });
  });
</script>

Server-Side Events (Conversions API)

For better tracking reliability, implement server-side events:

<?php
// modules/metapixel/services/ConversionsApi.php

namespace modules\metapixel\services;

use Craft;
use craft\commerce\elements\Order;
use FacebookAds\Api;
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;

class ConversionsApi
{
    public function trackPurchase(Order $order)
    {
        $pixelId = getenv('META_PIXEL_ID');
        $accessToken = getenv('META_CONVERSION_API_TOKEN');

        if (!$pixelId || !$accessToken) {
            return;
        }

        Api::init(null, null, $accessToken);

        $userData = (new UserData())
            ->setEmail(hash('sha256', $order->email))
            ->setClientIpAddress(Craft::$app->request->userIP)
            ->setClientUserAgent(Craft::$app->request->userAgent);

        $contents = [];
        foreach ($order->lineItems as $item) {
            $contents[] = (new Content())
                ->setProductId($item->purchasable->sku ?? $item->purchasable->id)
                ->setQuantity($item->qty)
                ->setItemPrice($item->price);
        }

        $customData = (new CustomData())
            ->setContents($contents)
            ->setCurrency($order->currency)
            ->setValue($order->total);

        $event = (new Event())
            ->setEventName('Purchase')
            ->setEventTime(time())
            ->setUserData($userData)
            ->setCustomData($customData)
            ->setEventSourceUrl($order->returnUrl)
            ->setActionSource('website');

        $request = (new EventRequest($pixelId))
            ->setEvents([$event]);

        $response = $request->execute();

        Craft::info('Meta Conversions API Purchase tracked: ' . $order->number, __METHOD__);
    }
}

Use in order completion:

// In your module or plugin
Event::on(
    Order::class,
    Order::EVENT_AFTER_COMPLETE_ORDER,
    function(Event $event) {
        $order = $event->sender;
        \modules\metapixel\Module::getInstance()
            ->conversionsApi
            ->trackPurchase($order);
    }
);

Live Preview Exclusion

Prevent event tracking during Live Preview:

{% if not craft.app.request.isLivePreview %}
  <script>
    fbq('track', 'ViewContent', {...});
  </script>
{% endif %}

Event Tracking Macro

Create a reusable macro for cleaner event tracking:

{# templates/_macros/meta-pixel.twig #}

{% macro track(eventName, params = {}) %}
  {% if not craft.app.request.isLivePreview %}
  <script>
    fbq('{{ params.custom ?? false ? 'trackCustom' : 'track' }}', '{{ eventName }}', {{ params|json_encode|raw }});
  </script>
  {% endif %}
{% endmacro %}

Use in templates:

{% import '_macros/meta-pixel' as fb %}

{{ fb.track('Lead', {
  content_name: 'Contact Form',
  value: 1.00,
  currency: 'USD'
}) }}

Debugging Events

Debug Mode

Enable console logging in development:

{% if craft.app.config.general.devMode %}
<script>
  // Override fbq to log events
  var originalFbq = window.fbq || function() {};
  window.fbq = function() {
    console.log('Meta Pixel Event:', arguments);
    return originalFbq.apply(this, arguments);
  };
</script>
{% endif %}

Test Events

Use Meta's Test Events tool:

{% set testEventCode = craft.app.request.getParam('test_event_code') %}

{% if testEventCode %}
<script>
  fbq('init', '{{ pixelId }}', {}, {
    testEventCode: '{{ testEventCode }}'
  });
</script>
{% endif %}

Best Practices

1. Consistent Event Parameters

Use standardized parameter names:

{
  content_name: 'Product Title',
  content_category: 'Category',
  content_type: 'product',
  value: 99.99,
  currency: 'USD'
}

2. Value and Currency

Always include value and currency for monetizable events:

fbq('track', 'Lead', {
  value: 5.00,  // Estimated lead value
  currency: 'USD'
});

3. Prevent Duplicate Events

Use session flags for one-time events:

{% set eventTracked = craft.app.session.get('event_tracked_' ~ entry.id) %}

{% if not eventTracked %}
  <script>fbq('track', 'ViewContent', {...});</script>
  {% do craft.app.session.set('event_tracked_' ~ entry.id, true) %}
{% endif %}

4. Environment-Specific Tracking

Only track in production:

{% if craft.app.config.general.environment == 'production' %}
  {# Event tracking code #}
{% endif %}

Next Steps

Resources