Install Meta Pixel with Sanity | Blue Frog Docs

Install Meta Pixel with Sanity

How to install and configure Meta Pixel (Facebook Pixel) on websites and applications powered by Sanity headless CMS.

Install Meta Pixel with Sanity

Meta Pixel (formerly Facebook Pixel) tracks user behavior for Facebook and Instagram advertising. Since Sanity is a headless CMS, Meta Pixel is installed in your frontend framework, not in Sanity Studio itself.

Before You Begin

Prerequisites:

  • Meta Business Manager account
  • Meta Pixel ID (format: 1234567890123456)
  • Sanity content integrated into your frontend
  • Developer access to your frontend codebase

Implementation Options:

  1. Direct pixel implementation in frontend
  2. GTM implementation (recommended for flexibility)
  3. Server-side events via Conversions API

Direct Pixel Implementation

Method 1: Next.js (App Router)

1. Create Meta Pixel Component

Create app/components/MetaPixel.tsx:

'use client'

import Script from 'next/script'

export function MetaPixel() {
  const PIXEL_ID = process.env.NEXT_PUBLIC_META_PIXEL_ID

  if (!PIXEL_ID) {
    console.warn('Meta Pixel ID not found')
    return null
  }

  return (
    <>
      <Script
        id="meta-pixel"
        strategy="afterInteractive"
        dangerouslySetInnerHTML={{
          __html: `
            !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');
          `,
        }}
      />
      <noscript>
        <img
          height="1"
          width="1"
          style={{ display: 'none' }}
          src={`https://www.facebook.com/tr?id=${PIXEL_ID}&ev=PageView&noscript=1`}
        />
      </noscript>
    </>
  )
}

2. Add to Root Layout

Update app/layout.tsx:

import { MetaPixel } from './components/MetaPixel'

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        {children}
        <MetaPixel />
      </body>
    </html>
  )
}

3. Track Route Changes

Create app/components/MetaPixelPageView.tsx:

'use client'

import { usePathname, useSearchParams } from 'next/navigation'
import { useEffect } from 'react'

export function MetaPixelPageView() {
  const pathname = usePathname()
  const searchParams = useSearchParams()

  useEffect(() => {
    if (typeof window !== 'undefined' && window.fbq) {
      window.fbq('track', 'PageView')
    }
  }, [pathname, searchParams])

  return null
}

Add to layout:

import { MetaPixelPageView } from './components/MetaPixelPageView'

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        {children}
        <MetaPixel />
        <MetaPixelPageView />
      </body>
    </html>
  )
}

4. Set Environment Variable

Create .env.local:

NEXT_PUBLIC_META_PIXEL_ID=1234567890123456

Method 2: Next.js (Pages Router)

1. Update _document.tsx

Create or update pages/_document.tsx:

import { Html, Head, Main, NextScript } from 'next/document'

export default function Document() {
  const PIXEL_ID = process.env.NEXT_PUBLIC_META_PIXEL_ID

  return (
    <Html>
      <Head>
        <script
          dangerouslySetInnerHTML={{
            __html: `
              !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');
            `,
          }}
        />
        <noscript>
          <img
            height="1"
            width="1"
            style={{ display: 'none' }}
            src={`https://www.facebook.com/tr?id=${PIXEL_ID}&ev=PageView&noscript=1`}
          />
        </noscript>
      </Head>
      <body>
        <Main />
        <NextScript />
      </body>
    </Html>
  )
}

2. Track Route Changes

Update pages/_app.tsx:

import { useEffect } from 'react'
import { useRouter } from 'next/router'

export default function App({ Component, pageProps }) {
  const router = useRouter()

  useEffect(() => {
    const handleRouteChange = () => {
      if (window.fbq) {
        window.fbq('track', 'PageView')
      }
    }

    router.events.on('routeChangeComplete', handleRouteChange)
    return () => {
      router.events.off('routeChangeComplete', handleRouteChange)
    }
  }, [router.events])

  return <Component {...pageProps} />
}

Method 3: Gatsby

1. Install Plugin

npm install gatsby-plugin-facebook-pixel

2. Configure in gatsby-config.js

module.exports = {
  plugins: [
    {
      resolve: `gatsby-source-sanity`,
      options: {
        projectId: process.env.SANITY_PROJECT_ID,
        dataset: process.env.SANITY_DATASET,
      },
    },
    {
      resolve: `gatsby-plugin-facebook-pixel`,
      options: {
        pixelId: process.env.META_PIXEL_ID,
      },
    },
  ],
}

3. Set Environment Variable

Create .env.production:

META_PIXEL_ID=1234567890123456

Method 4: React SPA

1. Add to index.html

Update public/index.html:

<!DOCTYPE html>
<html lang="en">
  <head>
    <!-- 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', '%VITE_META_PIXEL_ID%');
      fbq('track', 'PageView');
    </script>
    <noscript>
      <img height="1" width="1" style="display:none"
        src="https://www.facebook.com/tr?id=%VITE_META_PIXEL_ID%&ev=PageView&noscript=1"
      />
    </noscript>
    <!-- End Meta Pixel Code -->
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>

2. Track Route Changes

// App.tsx
import { useEffect } from 'react'
import { useLocation } from 'react-router-dom'

function App() {
  const location = useLocation()

  useEffect(() => {
    if (window.fbq) {
      window.fbq('track', 'PageView')
    }
  }, [location])

  return <Router>{/* Routes */}</Router>
}

Using GTM provides better flexibility and management.

1. Install GTM

See GTM Setup for Sanity for full GTM installation.

2. Create Meta Pixel Tag in GTM

Create Base Pixel Tag:

  1. In GTM, go to TagsNew
  2. Tag ConfigurationCustom HTML
  3. Add Meta Pixel base 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', 'YOUR_PIXEL_ID');
fbq('track', 'PageView');
</script>
<noscript>
  <img height="1" width="1" style="display:none"
    src="https://www.facebook.com/tr?id=YOUR_PIXEL_ID&ev=PageView&noscript=1"
  />
</noscript>
  1. Triggering: Select All Pages
  2. Save and name it "Meta Pixel - Base Code"

Sanity-Specific Events

ViewContent Event

Track when users view Sanity content:

// Track content view
export function trackContentView(document: any) {
  if (typeof window === 'undefined' || !window.fbq) return

  window.fbq('track', 'ViewContent', {
    content_name: document.title,
    content_category: document.category?.title || 'uncategorized',
    content_ids: [document._id],
    content_type: document._type,
  })
}

// Usage in component
'use client'

export function BlogPost({ post }) {
  useEffect(() => {
    trackContentView(post)
  }, [post])

  return <article>{/* Content */}</article>
}

Search Event

Track content searches with GROQ:

export function SearchBar() {
  const handleSearch = async (query: string) => {
    window.fbq?.('track', 'Search', {
      search_string: query,
      content_category: 'sanity_content',
    })

    // Perform GROQ search
    const results = await client.fetch(
      `*[_type in ["post", "page"] && title match $query]`,
      { query: `${query}*` }
    )
  }

  return <form onSubmit={(e) => {
    e.preventDefault()
    handleSearch(searchQuery)
  }}>{/* Form */}</form>
}

Lead Event

Track newsletter signups or form submissions:

export function NewsletterForm() {
  const handleSubmit = async (email: string) => {
    window.fbq?.('track', 'Lead', {
      content_name: 'newsletter_signup',
      value: 0,
      currency: 'USD',
    })

    // Submit form...
  }

  return <form onSubmit={handleSubmit}>{/* Form */}</form>
}

Custom Events

Track Sanity-specific interactions:

// Track content engagement
window.fbq?.('trackCustom', 'ContentEngagement', {
  content_id: document._id,
  content_type: document._type,
  engagement_type: 'scroll_75',
  time_on_page: 120,
})

// Track file download
window.fbq?.('trackCustom', 'FileDownload', {
  file_name: asset.originalFilename,
  file_type: asset.mimeType,
  content_id: document._id,
})

E-commerce Events (Sanity + Commerce)

ViewContent for Products

export function trackProductView(product: any) {
  window.fbq?.('track', 'ViewContent', {
    content_ids: [product.sku || product._id],
    content_type: 'product',
    content_name: product.title,
    value: product.price,
    currency: 'USD',
  })
}

AddToCart

export function AddToCartButton({ product, quantity = 1 }) {
  const handleClick = () => {
    window.fbq?.('track', 'AddToCart', {
      content_ids: [product.sku || product._id],
      content_type: 'product',
      content_name: product.title,
      value: product.price * quantity,
      currency: 'USD',
    })

    // Add to cart logic...
  }

  return <button onClick={handleClick}>Add to Cart</button>
}

Purchase

export function trackPurchase(order: any) {
  window.fbq?.('track', 'Purchase', {
    value: order.total,
    currency: 'USD',
    content_ids: order.items.map(item => item.sku),
    content_type: 'product',
    num_items: order.items.length,
  })
}

Advanced Matching

Send hashed user data for better attribution:

// Hash function (use a proper SHA-256 library in production)
async function hashValue(value: string): Promise<string> {
  const encoder = new TextEncoder()
  const data = encoder.encode(value.toLowerCase().trim())
  const hashBuffer = await crypto.subtle.digest('SHA-256', data)
  const hashArray = Array.from(new Uint8Array(hashBuffer))
  return hashArray.map(b => b.toString(16).padStart(2, '0')).join('')
}

// Send advanced matching data
async function initMetaPixelWithMatching(user: any) {
  if (!window.fbq || !user) return

  const advancedMatching = {
    em: user.email ? await hashValue(user.email) : undefined,
    fn: user.firstName ? await hashValue(user.firstName) : undefined,
    ln: user.lastName ? await hashValue(user.lastName) : undefined,
    ph: user.phone ? await hashValue(user.phone) : undefined,
    ct: user.city ? await hashValue(user.city) : undefined,
    st: user.state ? await hashValue(user.state) : undefined,
    zp: user.zipCode ? await hashValue(user.zipCode) : undefined,
    country: user.country ? await hashValue(user.country) : undefined,
  }

  window.fbq('init', PIXEL_ID, advancedMatching)
}

Conversions API (Server-Side)

Implement server-side events for better reliability:

// pages/api/meta-capi.ts (Next.js API route)
import crypto from 'crypto'

export default async function handler(req, res) {
  const PIXEL_ID = process.env.META_PIXEL_ID
  const ACCESS_TOKEN = process.env.META_CONVERSIONS_API_TOKEN

  const eventData = {
    data: [
      {
        event_name: req.body.event_name,
        event_time: Math.floor(Date.now() / 1000),
        action_source: 'website',
        event_source_url: req.body.url,
        user_data: {
          client_ip_address: req.headers['x-forwarded-for'] || req.connection.remoteAddress,
          client_user_agent: req.headers['user-agent'],
          fbc: req.body.fbc,  // Facebook click ID
          fbp: req.body.fbp,  // Facebook browser ID
        },
        custom_data: req.body.custom_data,
      },
    ],
  }

  const response = await fetch(
    `https://graph.facebook.com/v18.0/${PIXEL_ID}/events?access_token=${ACCESS_TOKEN}`,
    {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(eventData),
    }
  )

  const result = await response.json()
  res.status(200).json(result)
}

// Client-side: Send event to both pixel and CAPI
function trackDualEvent(eventName: string, data: any) {
  // Browser pixel
  window.fbq?.('track', eventName, data)

  // Server-side
  fetch('/api/meta-capi', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      event_name: eventName,
      url: window.location.href,
      fbc: getCookie('_fbc'),
      fbp: getCookie('_fbp'),
      custom_data: data,
    }),
  })
}

TypeScript Support

Add type definitions:

// types/meta-pixel.d.ts
declare global {
  interface Window {
    fbq: (
      action: 'track' | 'trackCustom' | 'init',
      eventName: string,
      parameters?: Record<string, any>
    ) => void
    _fbq: any
  }
}

export {}

Testing & Verification

1. Meta Pixel Helper

Install Meta Pixel Helper Chrome extension:

  • Navigate your site
  • Click extension icon
  • Verify pixel fires correctly
  • Check event parameters

2. Events Manager

In Meta Business Manager:

  1. Go to Events Manager
  2. Select your pixel
  3. Click Test Events
  4. Enter your test URL
  5. Navigate and verify events appear

3. Browser Console

// Check if fbq exists
console.log(typeof window.fbq) // should be 'function'

// Monitor pixel events
const originalFbq = window.fbq
window.fbq = function() {
  console.log('Meta Pixel Event:', arguments)
  return originalFbq.apply(window, arguments)
}

Implement consent management:

// Initialize with consent denied
window.fbq('consent', 'revoke')

// Grant consent after user accepts
function handleConsentAccept() {
  window.fbq('consent', 'grant')
}

GDPR Compliance

Respect user privacy choices:

function initMetaPixel() {
  // Check consent
  const hasConsent = checkUserConsent()

  if (hasConsent) {
    // Initialize pixel
    window.fbq('init', PIXEL_ID)
    window.fbq('track', 'PageView')
  }
}

Troubleshooting

Pixel Not Loading

Checks:

  • Pixel ID is correct
  • No JavaScript errors in console
  • Not blocked by ad blocker (test in incognito)
  • CSP headers allow Facebook domains

Events Not Firing

Checks:

  • window.fbq is defined
  • Event parameters are valid
  • Check Meta Pixel Helper for errors
  • Verify in Events Manager Test Events

Duplicate Events

Cause: Multiple pixel implementations.

Fix: Remove duplicate installations:

  • Check for multiple fbq('init') calls
  • Remove redundant GTM tags
  • Check if pixel installed in both code and GTM

Performance Optimization

Async Loading

Meta Pixel loads asynchronously by default. For Next.js:

<Script
  id="meta-pixel"
  strategy="afterInteractive"  // Load after page interactive
  dangerouslySetInnerHTML={{...}}
/>

Minimize Event Data

Only send necessary parameters:

// Good
fbq('track', 'ViewContent', {
  content_ids: [id],
  content_type: type,
})

// Too much data
fbq('track', 'ViewContent', {
  entire_sanity_document: {...}  // Don't do this
})

Next Steps

For general Meta Pixel concepts, see Meta Pixel Guide.

// SYS.FOOTER