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:
- Direct pixel implementation in frontend
- GTM implementation (recommended for flexibility)
- 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>
}
GTM Implementation (Recommended)
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:
- In GTM, go to Tags → New
- Tag Configuration → Custom HTML
- 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>
- Triggering: Select All Pages
- 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:
2. Events Manager
In Meta Business Manager:
- Go to Events Manager
- Select your pixel
- Click Test Events
- Enter your test URL
- 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)
}
Privacy & Consent
Consent Mode
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.fbqis 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
- GTM Setup - Install via GTM for better management
- Troubleshoot Events - Debug tracking issues
- GA4 Setup - Add Google Analytics
For general Meta Pixel concepts, see Meta Pixel Guide.