Learn how to track TYPO3-specific user interactions with Meta Pixel events, including form submissions, e-commerce actions, content engagement, and custom conversions using TypoScript and Fluid.
Understanding Meta Pixel Events
Meta Pixel supports two types of events:
- Standard Events: Predefined events like Purchase, Lead, AddToCart
- Custom Events: Custom-named events for specific tracking needs
Standard Events Overview
| Event | Purpose | Use Case in TYPO3 |
|---|---|---|
ViewContent |
Content view | Product pages, news articles |
Search |
Search performed | indexed_search, product search |
AddToCart |
Add to cart | E-commerce extensions |
InitiateCheckout |
Checkout started | Order process |
Purchase |
Order completed | Confirmation page |
Lead |
Lead generated | Form submissions |
CompleteRegistration |
User registration | Frontend user signup |
Contact |
Contact initiated | Contact form submission |
Standard Event Implementation
ViewContent Event
Track product views or content pages:
TypoScript for Product Pages
[request.getQueryParams()['tx_shop']['product'] > 0]
page.jsFooterInline.400 = TEXT
page.jsFooterInline.400.dataWrap (
fbq('track', 'ViewContent', {
content_ids: ['{request.getQueryParams()['tx_shop']['product']}'],
content_type: 'product',
content_name: '{page:title}',
currency: 'EUR',
value: 0
});
)
[END]
Fluid Template Implementation
<!-- Product Detail Template -->
<f:if condition="{product}">
<div class="product-detail">
<h1>{product.title}</h1>
<p class="price">{product.price -> f:format.currency(currencySign: '€')}</p>
</div>
<script>
fbq('track', 'ViewContent', {
content_ids: ['{product.uid}'],
content_type: 'product',
content_name: '{product.title -> f:format.htmlentities()}',
content_category: '{product.category.title -> f:format.htmlentities()}',
currency: 'EUR',
value: {product.price}
});
</script>
</f:if>
News Extension Integration
# Track news article views (EXT:news)
[request.getQueryParams()['tx_news_pi1']['news'] > 0]
page.jsFooterInline.401 = TEXT
page.jsFooterInline.401.dataWrap (
fbq('track', 'ViewContent', {
content_ids: ['{request.getQueryParams()['tx_news_pi1']['news']}'],
content_type: 'article',
content_name: '{page:title}'
});
)
[END]
Search Event
Track search queries:
indexed_search Integration
[request.getQueryParams()['tx_indexedsearch_pi2']['search']['sword'] != '']
page.jsFooterInline.402 = TEXT
page.jsFooterInline.402.stdWrap.dataWrap (
fbq('track', 'Search', {
search_string: '{request.getQueryParams()['tx_indexedsearch_pi2']['search']['sword']}',
content_category: 'site_search'
});
)
[END]
JavaScript-Based Search Tracking
page.jsFooterInline.403 = TEXT
page.jsFooterInline.403.value (
// Track search form submissions
document.addEventListener('DOMContentLoaded', function() {
const searchForm = document.querySelector('.tx-indexedsearch-searchbox-sword');
if (searchForm) {
searchForm.closest('form').addEventListener('submit', function(e) {
const searchTerm = searchForm.value;
if (searchTerm) {
fbq('track', 'Search', {
search_string: searchTerm,
content_category: 'typo3_search'
});
}
});
}
});
)
Lead Event (Form Submissions)
Form Framework Integration
TypoScript Setup:
plugin.tx_form {
settings {
yamlConfigurations {
200 = EXT:your_sitepackage/Configuration/Form/MetaPixelSetup.yaml
}
}
}
Custom Finisher (PHP):
<?php
declare(strict_types=1);
namespace Vendor\Extension\Finisher;
use TYPO3\CMS\Form\Domain\Finishers\AbstractFinisher;
class MetaPixelFinisher extends AbstractFinisher
{
protected function executeInternal()
{
$formRuntime = $this->finisherContext->getFormRuntime();
$formIdentifier = $formRuntime->getIdentifier();
$formTitle = $formRuntime->getFormDefinition()->getLabel();
// Get form values
$formValues = $formRuntime->getFormState()->getFormValues();
$email = $formValues['email'] ?? '';
// Add Meta Pixel Lead event
$GLOBALS['TSFE']->additionalFooterData['fbq_lead_' . $formIdentifier] = sprintf(
'<script>fbq("track", "Lead", %s);</script>',
json_encode([
'content_name' => $formTitle,
'content_category' => 'form_submission',
'status' => 'submitted',
'value' => 1.00,
'currency' => 'EUR'
])
);
return null;
}
}
Powermail Integration
page.jsFooterInline.404 = TEXT
page.jsFooterInline.404.value (
// Track Powermail submissions
document.addEventListener('DOMContentLoaded', function() {
const powermailForms = document.querySelectorAll('.powermail_form');
powermailForms.forEach(function(form) {
form.addEventListener('submit', function(e) {
const formName = this.querySelector('.powermail_fieldset legend')?.textContent || 'Contact Form';
fbq('track', 'Lead', {
content_name: formName,
content_category: 'powermail',
status: 'submitted'
});
});
});
});
)
Contact Form 7 Style Forms
// Track Contact Form submissions
jQuery(document).on('wpcf7mailsent', function(event) {
fbq('track', 'Contact', {
content_name: 'Contact Form',
content_category: 'contact'
});
});
CompleteRegistration Event
Track frontend user registration:
Extbase Controller
<?php
namespace Vendor\Extension\Controller;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
class UserController extends ActionController
{
public function createAction(\Vendor\Extension\Domain\Model\User $user)
{
$this->userRepository->add($user);
// Track registration
$GLOBALS['TSFE']->additionalFooterData['fbq_registration'] = "
<script>
fbq('track', 'CompleteRegistration', {
content_name: 'Frontend User Registration',
status: 'completed',
value: 0,
currency: 'EUR'
});
</script>";
$this->redirect('success');
}
}
Session-Based Tracking
# After successful login (first time)
[frontend.user.isLoggedIn == true]
page.jsFooterInline.405 = TEXT
page.jsFooterInline.405.value (
if (!sessionStorage.getItem('typo3_registration_tracked')) {
fbq('track', 'CompleteRegistration', {
content_name: 'User Registration',
status: 'completed'
});
sessionStorage.setItem('typo3_registration_tracked', '1');
}
)
[END]
E-commerce Event Tracking
AddToCart Event
Custom Extbase Shop
<?php
namespace Vendor\Shop\Controller;
class CartController extends \TYPO3\CMS\Extbase\Mvc\Controller\ActionController
{
public function addToCartAction(\Vendor\Shop\Domain\Model\Product $product, int $quantity = 1)
{
// Add to cart logic
$this->cartService->addItem($product, $quantity);
// Track AddToCart
$GLOBALS['TSFE']->additionalFooterData['fbq_add_to_cart'] = sprintf(
'<script>fbq("track", "AddToCart", %s);</script>',
json_encode([
'content_ids' => [$product->getUid()],
'content_name' => $product->getTitle(),
'content_type' => 'product',
'contents' => [[
'id' => $product->getUid(),
'quantity' => $quantity
]],
'currency' => 'EUR',
'value' => $product->getPrice() * $quantity
])
);
$this->redirect('show', 'Cart');
}
}
Fluid Template Button Tracking
<f:form action="addToCart" controller="Cart">
<f:form.hidden name="product" value="{product.uid}" />
<f:form.hidden name="quantity" value="1" class="quantity-input" />
<f:form.submit value="Add to Cart" class="btn-add-to-cart" />
</f:form>
<script>
document.querySelector('.btn-add-to-cart').addEventListener('click', function(e) {
fbq('track', 'AddToCart', {
content_ids: ['{product.uid}'],
content_name: '{product.title -> f:format.htmlentities()}',
content_type: 'product',
currency: 'EUR',
value: {product.price}
});
});
</script>
InitiateCheckout Event
<!-- Cart/Checkout Template -->
<f:if condition="{cart.items}">
<f:link.action action="checkout" controller="Checkout" class="btn-checkout">
Proceed to Checkout
</f:link.action>
<script>
document.querySelector('.btn-checkout').addEventListener('click', function(e) {
fbq('track', 'InitiateCheckout', {
content_ids: [
<f:for each="{cart.items}" as="item" iteration="iterator">
'{item.product.uid}'<f:if condition="{iterator.isLast}"><f:else>,</f:else></f:if>
</f:for>
],
contents: [
<f:for each="{cart.items}" as="item" iteration="iterator">
{
id: '{item.product.uid}',
quantity: {item.quantity}
}<f:if condition="{iterator.isLast}"><f:else>,</f:else></f:if>
</f:for>
],
currency: 'EUR',
value: {cart.total},
num_items: {cart.itemCount}
});
});
</script>
</f:if>
AddPaymentInfo Event
page.jsFooterInline.406 = TEXT
page.jsFooterInline.406.value (
// Track payment method selection
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('input[name="paymentMethod"]').forEach(function(radio) {
radio.addEventListener('change', function() {
if (this.checked) {
fbq('track', 'AddPaymentInfo', {
content_category: 'payment_method',
content_name: this.value
});
}
});
});
});
)
Purchase Event
<?php
namespace Vendor\Shop\Controller;
class CheckoutController extends \TYPO3\CMS\Extbase\Mvc\Controller\ActionController
{
public function confirmAction()
{
$order = $this->session->get('completed_order');
if ($order) {
// Build contents array
$contents = [];
foreach ($order->getItems() as $item) {
$contents[] = [
'id' => $item->getProduct()->getUid(),
'quantity' => $item->getQuantity()
];
}
$contentIds = array_column($contents, 'id');
// Track purchase
$GLOBALS['TSFE']->additionalFooterData['fbq_purchase'] = sprintf(
'<script>fbq("track", "Purchase", %s);</script>',
json_encode([
'content_ids' => $contentIds,
'content_type' => 'product',
'contents' => $contents,
'currency' => 'EUR',
'value' => $order->getTotal(),
'num_items' => count($contents)
])
);
}
$this->view->assign('order', $order);
}
}
Custom Events
Newsletter Signup
page.jsFooterInline.407 = TEXT
page.jsFooterInline.407.value (
// Track newsletter signups
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('.newsletter-form').forEach(function(form) {
form.addEventListener('submit', function(e) {
fbq('trackCustom', 'NewsletterSignup', {
content_name: 'Newsletter Subscription',
status: 'submitted'
});
});
});
});
)
Download Tracking
page.jsFooterInline.408 = TEXT
page.jsFooterInline.408.value (
// Track file downloads
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('a[href]').forEach(function(link) {
const href = link.getAttribute('href');
if (href && /\.(pdf|zip|docx?|xlsx?|pptx?)$/i.test(href)) {
link.addEventListener('click', function(e) {
const fileName = href.split('/').pop();
fbq('trackCustom', 'FileDownload', {
content_name: fileName,
content_category: 'downloads',
file_type: fileName.split('.').pop()
});
});
}
});
});
)
Video Play Tracking
page.includeJSFooter.videoTracking = EXT:your_sitepackage/Resources/Public/JavaScript/video-pixel-tracking.js
video-pixel-tracking.js:
document.addEventListener('DOMContentLoaded', function() {
const videos = document.querySelectorAll('video');
videos.forEach(function(video) {
const videoTitle = video.getAttribute('title') || 'Untitled Video';
video.addEventListener('play', function() {
fbq('trackCustom', 'VideoPlay', {
content_name: videoTitle,
content_category: 'video'
});
}, {once: true});
video.addEventListener('ended', function() {
fbq('trackCustom', 'VideoComplete', {
content_name: videoTitle,
content_category: 'video'
});
});
});
});
Button/CTA Click Tracking
page.jsFooterInline.409 = TEXT
page.jsFooterInline.409.value (
// Track CTA buttons
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('.cta-button, .btn-primary').forEach(function(button) {
button.addEventListener('click', function() {
fbq('trackCustom', 'CTAClick', {
content_name: this.textContent.trim(),
button_text: this.textContent.trim(),
button_url: this.href || 'no-url'
});
});
});
});
)
Advanced Tracking with Custom Parameters
Dynamic Content Parameters
<f:if condition="{product}">
<script>
fbq('track', 'ViewContent', {
content_ids: ['{product.uid}'],
content_name: '{product.title -> f:format.htmlentities()}',
content_type: 'product',
content_category: '{product.category.title -> f:format.htmlentities()}',
currency: 'EUR',
value: {product.price},
// Custom parameters
brand: '{product.brand -> f:format.htmlentities()}',
availability: '<f:if condition="{product.inStock}" then="in stock" else="out of stock" />',
condition: 'new'
});
</script>
</f:if>
User Properties
[frontend.user.isLoggedIn == true]
page.jsFooterInline.410 = TEXT
page.jsFooterInline.410.value (
// Set user properties for better targeting
fbq('setUserProperties', '{$metaPixel.pixelId}', {
user_type: 'logged_in',
membership_level: 'standard',
account_created: new Date().toISOString()
});
)
[END]
Event Parameters Best Practices
Required Parameters by Event
ViewContent:
content_ids(array)content_type(string)currency(string)value(number)
AddToCart:
content_ids(array)contents(array of objects with id and quantity)currency(string)value(number)
Purchase:
content_ids(array)contents(array)currency(string)value(number)
Example Complete Implementation
<script>
fbq('track', 'Purchase', {
// Required
content_ids: ['123', '456'],
contents: [
{id: '123', quantity: 2},
{id: '456', quantity: 1}
],
currency: 'EUR',
value: 149.99,
// Recommended
content_type: 'product',
num_items: 3,
// Optional
content_name: 'Order #12345',
content_category: 'Electronics'
});
</script>
Debugging Meta Pixel Events
Browser Console Testing
page.jsFooterInline.999 = TEXT
page.jsFooterInline.999.value (
// Log all fbq calls to console
(function() {
const originalFbq = window.fbq;
window.fbq = function() {
console.log('Meta Pixel:', arguments);
return originalFbq.apply(this, arguments);
};
})();
)
Meta Pixel Helper Chrome Extension
- Install Meta Pixel Helper
- Visit your TYPO3 site
- Click extension icon to see:
- Pixel ID
- Events fired
- Parameters sent
- Any errors
Facebook Events Manager Test Events
- Go to Events Manager → Test Events
- Enter your website URL or use browser extension
- Navigate site and trigger events
- Verify events appear with correct parameters
Conditional Event Loading
Load Events Only on Specific Pages
# Only on product pages
[page["uid"] in [10, 15, 20] || page["backend_layout"] == "pagets__product"]
page.includeJSFooter.productEvents = EXT:your_sitepackage/Resources/Public/JavaScript/product-events.js
[END]
# Only on checkout pages
[page["uid"] == 25]
page.includeJSFooter.checkoutEvents = EXT:your_sitepackage/Resources/Public/JavaScript/checkout-events.js
[END]
Conversion API Integration (Server-Side)
For enhanced tracking, combine browser events with server-side events:
<?php
use FacebookAds\Api;
use FacebookAds\Object\ServerSide\Event;
use FacebookAds\Object\ServerSide\EventRequest;
use FacebookAds\Object\ServerSide\UserData;
class MetaPixelService
{
public function trackServerSideEvent(string $eventName, array $customData, array $userData)
{
Api::init(null, null, $accessToken);
$event = (new Event())
->setEventName($eventName)
->setEventTime(time())
->setUserData((new UserData())
->setEmail(hash('sha256', $userData['email']))
->setPhone(hash('sha256', $userData['phone']))
)
->setCustomData($customData)
->setEventSourceUrl($_SERVER['HTTP_REFERER'] ?? '')
->setActionSource('website');
$request = (new EventRequest($pixelId))
->setEvents([$event]);
$response = $request->execute();
}
}
Performance Considerations
Async Event Tracking
// Defer event tracking to avoid blocking
function trackEventAsync(eventName, params) {
if (typeof fbq !== 'function') {
console.warn('Meta Pixel not loaded');
return;
}
requestIdleCallback(() => {
fbq('track', eventName, params);
});
}
// Usage
trackEventAsync('ViewContent', {content_ids: ['123']});
Batch Events
// Collect events and send in batch
const eventQueue = [];
function queueEvent(eventName, params) {
eventQueue.push({event: eventName, params: params});
}
function flushEvents() {
eventQueue.forEach(item => {
fbq('track', item.event, item.params);
});
eventQueue.length = 0;
}
// Flush on page unload
window.addEventListener('beforeunload', flushEvents);