Install Meta Pixel on Grav Sites | OpsBlu Docs

Install Meta Pixel on Grav Sites

Complete guide to installing Facebook/Meta Pixel on Grav flat-file CMS using Twig templates and custom plugins for tracking conversions and events.

This guide covers installing the Meta Pixel (formerly Facebook Pixel) on your Grav site for tracking conversions, retargeting, and measuring ad campaign performance.

Before You Begin

  1. Create Meta Pixel

  2. Admin Access Required

    • Access to Grav Admin Panel or file system
    • Ability to edit theme templates or install plugins

Add Meta Pixel directly to your theme templates for complete control.

Add to Base Template

{# user/themes/your-theme/templates/partials/base.html.twig #}

<!DOCTYPE html>
<html lang="{{ grav.language.getActive ?: 'en' }}">
<head>
    {% block head %}
        <meta charset="utf-8" />
        <title>{% if page.title %}{{ page.title }} | {% endif %}{{ site.title }}</title>

        {# Meta Pixel Code #}
        {% set pixel_id = grav.config.site.meta_pixel_id %}
        {% if pixel_id %}
        <!-- Meta Pixel Code -->
        <script>
        !function(f,b,e,v,n,t,s)
        {if(f.fbq)return;n=f.fbq=function(){n.callMethod?
        n.callMethod.apply(n,arguments):n.queue.push(arguments)};
        if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';
        n.queue=[];t=b.createElement(e);t.async=!0;
        t.src=v;s=b.getElementsByTagName(e)[0];
        s.parentNode.insertBefore(t,s)}(window, document,'script',
        'https://connect.facebook.net/en_US/fbevents.js');
        fbq('init', '{{ pixel_id }}');
        fbq('track', 'PageView');
        </script>
        <noscript>
            <img height="1" width="1" style="display:none"
                 src="https://www.facebook.com/tr?id={{ pixel_id }}&ev=PageView&noscript=1"/>
        </noscript>
        <!-- End Meta Pixel Code -->
        {% endif %}

        {{ assets.css()|raw }}
    {% endblock %}
</head>
<body>
    {% block content %}{% endblock %}
    {{ assets.js()|raw }}
</body>
</html>

Configure in site.yaml

# user/config/site.yaml
title: My Grav Site
default_lang: en
meta_pixel_id: '1234567890123456'

Add to Header Partial

If your theme uses a separate header partial:

{# user/themes/your-theme/templates/partials/head.html.twig #}

<meta charset="utf-8" />
<title>{% if page.title %}{{ page.title|e('html') }} | {% endif %}{{ site.title|e('html') }}</title>

{# Meta Pixel #}
{% set pixel_id = grav.config.site.meta_pixel_id %}
{% if pixel_id and grav.config.system.environment == 'production' %}
<!-- Meta Pixel Code -->
<script>
!function(f,b,e,v,n,t,s)
{if(f.fbq)return;n=f.fbq=function(){n.callMethod?
n.callMethod.apply(n,arguments):n.queue.push(arguments)};
if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';
n.queue=[];t=b.createElement(e);t.async=!0;
t.src=v;s=b.getElementsByTagName(e)[0];
s.parentNode.insertBefore(t,s)}(window, document,'script',
'https://connect.facebook.net/en_US/fbevents.js');
fbq('init', '{{ pixel_id }}');
fbq('track', 'PageView');
</script>
<noscript>
    <img height="1" width="1" style="display:none"
         src="https://www.facebook.com/tr?id={{ pixel_id }}&ev=PageView&noscript=1"/>
</noscript>
<!-- End Meta Pixel Code -->
{% endif %}

Method 2: Custom Plugin

Create a minimal Meta Pixel plugin for easier management.

Create Plugin Structure

# Create plugin directory
mkdir -p user/plugins/meta-pixel

# Create files
touch user/plugins/meta-pixel/meta-pixel.php
touch user/plugins/meta-pixel/meta-pixel.yaml

Plugin Code

<?php
// user/plugins/meta-pixel/meta-pixel.php

namespace Grav\Plugin;

use Grav\Common\Plugin;

class MetaPixelPlugin extends Plugin
{
    public static function getSubscribedEvents()
    {
        return [
            'onPluginsInitialized' => ['onPluginsInitialized', 0]
        ];
    }

    public function onPluginsInitialized()
    {
        // Don't run in admin
        if ($this->isAdmin()) {
            return;
        }

        $this->enable([
            'onPageContentRaw' => ['onPageContentRaw', 0]
        ]);
    }

    public function onPageContentRaw()
    {
        $config = $this->config->get('plugins.meta-pixel');

        if (!$config['enabled'] || empty($config['pixel_id'])) {
            return;
        }

        $pixel_id = $config['pixel_id'];
        $track_page_view = $config['track_page_view'] ? 'true' : 'false';

        $pixel_code = <<<HTML
<!-- Meta Pixel Code -->
<script>
!function(f,b,e,v,n,t,s)
{if(f.fbq)return;n=f.fbq=function(){n.callMethod?
n.callMethod.apply(n,arguments):n.queue.push(arguments)};
if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';
n.queue=[];t=b.createElement(e);t.async=!0;
t.src=v;s=b.getElementsByTagName(e)[0];
s.parentNode.insertBefore(t,s)}(window, document,'script',
'https://connect.facebook.net/en_US/fbevents.js');
fbq('init', '{$pixel_id}');
HTML;

        if ($config['track_page_view']) {
            $pixel_code .= "\nfbq('track', 'PageView');";
        }

        $pixel_code .= <<<HTML

</script>
<noscript>
    <img height="1" width="1" style="display:none"
         src="https://www.facebook.com/tr?id={$pixel_id}&ev=PageView&noscript=1"/>
</noscript>
<!-- End Meta Pixel Code -->
HTML;

        $this->grav['assets']->addInlineJs($pixel_code, ['position' => 'head']);
    }
}

Plugin Configuration

# user/plugins/meta-pixel/meta-pixel.yaml
enabled: true
pixel_id: '1234567890123456'
track_page_view: true

Method 3: Multiple Pixels

Track with multiple pixels for different audiences or ad accounts.

{# Multiple Meta Pixels #}
{% set pixels = {
    'main': '1234567890123456',
    'remarketing': '6543210987654321'
} %}

<script>
!function(f,b,e,v,n,t,s)
{if(f.fbq)return;n=f.fbq=function(){n.callMethod?
n.callMethod.apply(n,arguments):n.queue.push(arguments)};
if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';
n.queue=[];t=b.createElement(e);t.async=!0;
t.src=v;s=b.getElementsByTagName(e)[0];
s.parentNode.insertBefore(t,s)}(window, document,'script',
'https://connect.facebook.net/en_US/fbevents.js');

{% for key, pixel_id in pixels %}
fbq('init', '{{ pixel_id }}');
{% endfor %}

fbq('track', 'PageView');
</script>

Conversion Tracking

Track Form Submissions

{# Contact form with Meta Pixel tracking #}

<form name="contact-form"
      action="{{ page.route }}"
      method="POST"

    {{ form.fields|join|raw }}

    <button type="submit">Send Message</button>
</form>

<script>
function trackFormSubmit() {
    if (typeof fbq !== 'undefined') {
        fbq('track', 'Contact', {
            content_name: 'Contact Form',
            content_category: 'Form Submission'
        });
    }
    return true;
}

// Track successful submission
{% if grav.uri.query('sent') == 'true' %}
if (typeof fbq !== 'undefined') {
    fbq('track', 'Lead', {
        content_name: 'Contact Form',
        value: 0.00,
        currency: 'USD'
    });
}
{% endif %}
</script>

Track Downloads

{# Track file downloads #}

<a href="{{ page.media['guide.pdf'].url }}" 'Lead Magnet')">
    Download Free Guide (PDF)
</a>

<script>
function trackDownload(fileName, category) {
    if (typeof fbq !== 'undefined') {
        fbq('track', 'Lead', {
            content_name: fileName,
            content_category: category
        });
    }
}
</script>

Track Newsletter Signup

{# Newsletter signup form #}

<form id="newsletter-form" method="POST">
    <input type="email" name="email" placeholder="Your email" required>
    <button type="submit">Subscribe</button>
</form>

<script>
document.getElementById('newsletter-form').addEventListener('submit', function(e) {
    if (typeof fbq !== 'undefined') {
        fbq('track', 'CompleteRegistration', {
            content_name: 'Newsletter',
            status: 'subscribed'
        });
    }
});
</script>

E-commerce Tracking

If using Grav for e-commerce, track purchases and cart events.

View Content Event

{# Product page #}

{% block bottom %}
<script>
if (typeof fbq !== 'undefined') {
    fbq('track', 'ViewContent', {
        content_name: '{{ page.title }}',
        content_ids: ['{{ page.header.product_id }}'],
        content_type: 'product',
        value: {{ page.header.price }},
        currency: 'USD'
    });
}
</script>
{% endblock %}

Add to Cart Event

<button product.id }}', '{{ product.title }}', {{ product.price }})">
    Add to Cart
</button>

<script>
function addToCart(productId, productName, price) {
    // Your cart logic here

    // Track with Meta Pixel
    if (typeof fbq !== 'undefined') {
        fbq('track', 'AddToCart', {
            content_ids: [productId],
            content_name: productName,
            content_type: 'product',
            value: price,
            currency: 'USD'
        });
    }
}
</script>

Purchase Event

{# Thank you page after purchase #}

{% if page.template == 'thank-you' %}
<script>
if (typeof fbq !== 'undefined') {
    fbq('track', 'Purchase', {
        content_ids: {{ order.items|map(i => i.id)|json_encode|raw }},
        content_type: 'product',
        value: {{ order.total }},
        currency: 'USD',
        num_items: {{ order.items|length }}
    });
}
</script>
{% endif %}

Custom Events

Track custom events specific to your Grav site.

Blog Post Engagement

{# Track blog post reading #}

<script>
// Track when user scrolls to bottom
var trackingFired = false;

window.addEventListener('scroll', function() {
    if (trackingFired) return;

    var scrollPercentage = (window.scrollY / (document.documentElement.scrollHeight - window.innerHeight)) * 100;

    if (scrollPercentage > 90 && typeof fbq !== 'undefined') {
        fbq('trackCustom', 'ArticleRead', {
            article_title: '{{ page.title }}',
            article_category: '{{ page.taxonomy.category|first ?: "General" }}'
        });
        trackingFired = true;
    }
});
</script>

Video Views

{# Track video plays #}

<video id="featured-video" src="{{ page.media['video.mp4'].url }}" controls></video>

<script>
document.getElementById('featured-video').addEventListener('play', function() {
    if (typeof fbq !== 'undefined') {
        fbq('trackCustom', 'VideoView', {
            video_title: '{{ page.title }}',
            video_type: 'mp4'
        });
    }
});
</script>

Advanced Matching (Hashed User Data)

Improve matching with hashed customer information.

<script>
// Advanced Matching
{% set user_email = grav.user.email %}
{% if user_email %}
fbq('init', '{{ pixel_id }}', {
    em: '{{ user_email|lower|md5 }}',  // Hashed email
    fn: '{{ grav.user.firstname|lower|md5 }}',  // Hashed first name
    ln: '{{ grav.user.lastname|lower|md5 }}'    // Hashed last name
});
{% else %}
fbq('init', '{{ pixel_id }}');
{% endif %}
fbq('track', 'PageView');
</script>

GDPR Compliance

<script>
// Check for cookie consent
function hasMetaPixelConsent() {
    return localStorage.getItem('meta_pixel_consent') === 'true';
}

// Load Meta Pixel only if consent given
if (hasMetaPixelConsent()) {
    !function(f,b,e,v,n,t,s)
    {if(f.fbq)return;n=f.fbq=function(){n.callMethod?
    n.callMethod.apply(n,arguments):n.queue.push(arguments)};
    if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';
    n.queue=[];t=b.createElement(e);t.async=!0;
    t.src=v;s=b.getElementsByTagName(e)[0];
    s.parentNode.insertBefore(t,s)}(window, document,'script',
    'https://connect.facebook.net/en_US/fbevents.js');
    fbq('init', '{{ pixel_id }}');
    fbq('track', 'PageView');
}
</script>
{# Cookie consent banner #}

<div id="cookie-consent" style="display:none;">
    <p>We use cookies for advertising. Accept to continue.</p>
    <button
    <button
</div>

<script>
function grantMetaPixelConsent() {
    localStorage.setItem('meta_pixel_consent', 'true');
    document.getElementById('cookie-consent').style.display = 'none';
    location.reload(); // Reload to load pixel
}

function denyMetaPixelConsent() {
    localStorage.setItem('meta_pixel_consent', 'false');
    document.getElementById('cookie-consent').style.display = 'none';
}

// Show banner if no consent choice made
if (!localStorage.getItem('meta_pixel_consent')) {
    document.getElementById('cookie-consent').style.display = 'block';
}
</script>

Testing & Verification

Use Meta Pixel Helper

  1. Install Extension

  2. Test Your Site

    • Navigate to your Grav site
    • Click the extension icon
    • Verify pixel is firing
  3. Check Events

    • Look for green checkmarks
    • Review event parameters
    • Check for errors or warnings

Test Events Manager

  1. Go to Meta Events Manager
  2. Select your pixel
  3. Click Test Events
  4. Enter your website URL
  5. Interact with your site
  6. Verify events appear in real-time

Console Testing

// Check if Meta Pixel loaded
console.log(typeof fbq); // Should be 'function'

// Test custom event
fbq('trackCustom', 'TestEvent', {
    test_param: 'test_value'
});

Debug Mode

<script>
// Enable Meta Pixel debug mode
{% if grav.config.system.debugger.enabled %}
if (typeof fbq !== 'undefined') {
    fbq('track', 'PageView');
    console.log('Meta Pixel fired: PageView');
}
{% endif %}
</script>

Common Issues

Pixel Not Firing

Possible Causes:

  • Incorrect Pixel ID
  • Ad blocker active
  • JavaScript errors
  • Pixel loaded after page content

Solutions:

  1. Verify Pixel ID in site.yaml or plugin config
  2. Test in incognito mode
  3. Check browser console for errors
  4. Ensure pixel loads in <head>

Duplicate Events

Cause: Pixel code included multiple times.

Solution: Use conditional check:

{% if not pixel_loaded %}
    {% set pixel_loaded = true %}
    {# Pixel code here #}
{% endif %}

Events Not Matching

Cause: Advanced matching data incorrect.

Solution: Ensure proper hashing:

{# Correct hashing #}
em: '{{ user_email|lower|trim|md5 }}'

Multi-Language Sites

Track language in custom parameters:

{% set active_lang = grav.language.getActive ?: 'en' %}

<script>
fbq('track', 'PageView', {
    content_language: '{{ active_lang }}',
    content_locale: '{{ active_lang }}_{{ grav.config.site.default_lang|upper }}'
});
</script>

Next Steps

For general Meta Pixel concepts, see Meta Pixel Guide.