TYPO3 Meta Pixel Event Tracking | OpsBlu Docs

TYPO3 Meta Pixel Event Tracking

Implement Meta Pixel standard and custom events in TYPO3 for forms, e-commerce, and user interactions

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

  1. Install Meta Pixel Helper
  2. Visit your TYPO3 site
  3. Click extension icon to see:
    • Pixel ID
    • Events fired
    • Parameters sent
    • Any errors

Facebook Events Manager Test Events

  1. Go to Events Manager → Test Events
  2. Enter your website URL or use browser extension
  3. Navigate site and trigger events
  4. 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);

Next Steps