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 %}
External Link Tracking
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
- Meta Pixel Setup - Configure Meta Pixel installation
- GTM Data Layer - Use GTM for Meta Pixel events
- Troubleshooting - Fix tracking issues