Meta Pixel on Commercetools | Blue Frog Docs

Meta Pixel on Commercetools

Implementing Facebook/Instagram Pixel on Commercetools headless commerce storefronts

Meta Pixel on Commercetools

This guide covers implementing Meta Pixel (Facebook Pixel) on Commercetools headless commerce for Facebook and Instagram advertising optimization.

Prerequisites

  1. Create Meta Pixel

    • Go to Meta Business Suite
    • Events Manager → Connect Data Sources → Web → Meta Pixel
    • Copy your Pixel ID (15-16 digit number)
  2. Conversions API Access Token (for server-side)

    • Events Manager → Settings → Generate Access Token

Client-Side Implementation

Next.js / React

// components/MetaPixel.tsx
'use client';

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

declare global {
  interface Window {
    fbq: (...args: any[]) => void;
    _fbq: any;
  }
}

interface MetaPixelProps {
  pixelId: string;
}

export function MetaPixel({ pixelId }: MetaPixelProps) {
  const pathname = usePathname();
  const searchParams = useSearchParams();

  useEffect(() => {
    // Track page views on route change
    if (window.fbq) {
      window.fbq('track', 'PageView');
    }
  }, [pathname, searchParams]);

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

Create Tracking Hook

// hooks/useMetaPixel.ts
export function useMetaPixel() {
  const track = (eventName: string, params?: Record<string, any>) => {
    if (typeof window !== 'undefined' && window.fbq) {
      window.fbq('track', eventName, params);
    }
  };

  const trackCustom = (eventName: string, params?: Record<string, any>) => {
    if (typeof window !== 'undefined' && window.fbq) {
      window.fbq('trackCustom', eventName, params);
    }
  };

  return { track, trackCustom };
}

E-commerce Event Implementation

// hooks/useMetaEcommerce.ts
import { useMetaPixel } from './useMetaPixel';
import { ProductProjection, Cart, Order } from '@commercetools/platform-sdk';

export function useMetaEcommerce() {
  const { track } = useMetaPixel();

  const trackViewContent = (product: ProductProjection) => {
    const variant = product.masterVariant;
    const price = variant.prices?.[0];

    track('ViewContent', {
      content_ids: [product.id],
      content_name: product.name['en-US'],
      content_type: 'product',
      content_category: product.categories?.[0]?.obj?.name?.['en-US'],
      value: price ? price.value.centAmount / 100 : 0,
      currency: price?.value.currencyCode || 'USD'
    });
  };

  const trackAddToCart = (product: ProductProjection, quantity: number) => {
    const variant = product.masterVariant;
    const price = variant.prices?.[0];

    track('AddToCart', {
      content_ids: [product.id],
      content_name: product.name['en-US'],
      content_type: 'product',
      value: price ? (price.value.centAmount / 100) * quantity : 0,
      currency: price?.value.currencyCode || 'USD',
      num_items: quantity
    });
  };

  const trackInitiateCheckout = (cart: Cart) => {
    track('InitiateCheckout', {
      content_ids: cart.lineItems.map(item => item.productId),
      content_type: 'product',
      value: cart.totalPrice.centAmount / 100,
      currency: cart.totalPrice.currencyCode,
      num_items: cart.lineItems.reduce((sum, item) => sum + item.quantity, 0)
    });
  };

  const trackAddPaymentInfo = (cart: Cart) => {
    track('AddPaymentInfo', {
      content_ids: cart.lineItems.map(item => item.productId),
      content_type: 'product',
      value: cart.totalPrice.centAmount / 100,
      currency: cart.totalPrice.currencyCode
    });
  };

  const trackPurchase = (order: Order) => {
    track('Purchase', {
      content_ids: order.lineItems.map(item => item.productId),
      content_type: 'product',
      value: order.totalPrice.centAmount / 100,
      currency: order.totalPrice.currencyCode,
      num_items: order.lineItems.reduce((sum, item) => sum + item.quantity, 0),
      contents: order.lineItems.map(item => ({
        id: item.productId,
        quantity: item.quantity,
        item_price: item.price.value.centAmount / 100
      }))
    });
  };

  return {
    trackViewContent,
    trackAddToCart,
    trackInitiateCheckout,
    trackAddPaymentInfo,
    trackPurchase
  };
}

Usage in Components

// Product Detail Page
import { useMetaEcommerce } from '@/hooks/useMetaEcommerce';

export function ProductPage({ product }: Props) {
  const { trackViewContent } = useMetaEcommerce();

  useEffect(() => {
    trackViewContent(product);
  }, [product]);

  return (/* JSX */);
}

// Add to Cart Button
export function AddToCartButton({ product }: Props) {
  const { trackAddToCart } = useMetaEcommerce();
  const { addToCart } = useCart();

  const handleAddToCart = async () => {
    await addToCart(product.id, 1);
    trackAddToCart(product, 1);
  };

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

// Checkout Success
export function CheckoutSuccess({ order }: Props) {
  const { trackPurchase } = useMetaEcommerce();

  useEffect(() => {
    const tracked = sessionStorage.getItem(`meta_purchase_${order.id}`);
    if (!tracked) {
      trackPurchase(order);
      sessionStorage.setItem(`meta_purchase_${order.id}`, 'true');
    }
  }, [order]);

  return (/* JSX */);
}

Server-Side (Conversions API)

For accurate tracking despite ad blockers, implement the Conversions API:

API Route Handler

// app/api/meta-capi/route.ts
import { NextRequest, NextResponse } from 'next/server';
import crypto from 'crypto';

const PIXEL_ID = process.env.META_PIXEL_ID!;
const ACCESS_TOKEN = process.env.META_ACCESS_TOKEN!;

function hashData(data: string): string {
  return crypto.createHash('sha256').update(data.toLowerCase()).digest('hex');
}

export async function POST(request: NextRequest) {
  const body = await request.json();
  const { eventName, eventData, userData } = body;

  const payload = {
    data: [{
      event_name: eventName,
      event_time: Math.floor(Date.now() / 1000),
      action_source: 'website',
      event_source_url: eventData.source_url,
      user_data: {
        em: userData.email ? [hashData(userData.email)] : undefined,
        ph: userData.phone ? [hashData(userData.phone)] : undefined,
        fn: userData.firstName ? [hashData(userData.firstName)] : undefined,
        ln: userData.lastName ? [hashData(userData.lastName)] : undefined,
        client_ip_address: request.headers.get('x-forwarded-for')?.split(',')[0],
        client_user_agent: request.headers.get('user-agent'),
        fbc: eventData.fbc,
        fbp: eventData.fbp,
      },
      custom_data: eventData.customData,
    }]
  };

  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(payload)
    }
  );

  const result = await response.json();
  return NextResponse.json(result);
}

Hybrid Client + Server Tracking

// hooks/useMetaHybrid.ts
import { useMetaPixel } from './useMetaPixel';

export function useMetaHybrid() {
  const { track } = useMetaPixel();

  const trackPurchase = async (order: Order, userData?: UserData) => {
    // Client-side tracking
    track('Purchase', {
      content_ids: order.lineItems.map(item => item.productId),
      value: order.totalPrice.centAmount / 100,
      currency: order.totalPrice.currencyCode,
    });

    // Server-side tracking (Conversions API)
    await fetch('/api/meta-capi', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        eventName: 'Purchase',
        eventData: {
          source_url: window.location.href,
          fbc: getCookie('_fbc'),
          fbp: getCookie('_fbp'),
          customData: {
            content_ids: order.lineItems.map(item => item.productId),
            value: order.totalPrice.centAmount / 100,
            currency: order.totalPrice.currencyCode,
          }
        },
        userData: {
          email: userData?.email,
          firstName: userData?.firstName,
          lastName: userData?.lastName,
        }
      })
    });
  };

  return { trackPurchase };
}

function getCookie(name: string): string | undefined {
  const value = `; ${document.cookie}`;
  const parts = value.split(`; ${name}=`);
  if (parts.length === 2) return parts.pop()?.split(';').shift();
}

Commercetools Subscription for CAPI

// Cloud Function for server-side purchase tracking
import * as functions from '@google-cloud/functions-framework';
import crypto from 'crypto';

const PIXEL_ID = process.env.META_PIXEL_ID!;
const ACCESS_TOKEN = process.env.META_ACCESS_TOKEN!;

functions.cloudEvent('trackMetaPurchase', async (cloudEvent: any) => {
  const message = JSON.parse(
    Buffer.from(cloudEvent.data.message.data, 'base64').toString()
  );

  const order = message.order;
  const customer = message.customer;

  const hashData = (data: string) =>
    crypto.createHash('sha256').update(data.toLowerCase()).digest('hex');

  const payload = {
    data: [{
      event_name: 'Purchase',
      event_time: Math.floor(Date.now() / 1000),
      action_source: 'website',
      user_data: {
        em: customer?.email ? [hashData(customer.email)] : undefined,
        external_id: order.customerId ? [hashData(order.customerId)] : undefined,
      },
      custom_data: {
        content_ids: order.lineItems.map((item: any) => item.productId),
        content_type: 'product',
        value: order.totalPrice.centAmount / 100,
        currency: order.totalPrice.currencyCode,
        num_items: order.lineItems.reduce(
          (sum: number, item: any) => sum + item.quantity, 0
        ),
      }
    }]
  };

  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(payload)
    }
  );
});

Event Reference

Event When to Fire Key Parameters
PageView Every page load (automatic)
ViewContent Product page content_ids, content_name, value
AddToCart Item added to cart content_ids, value, currency
InitiateCheckout Checkout starts content_ids, value, num_items
AddPaymentInfo Payment entered content_ids, value
Purchase Order complete content_ids, value, currency, num_items

Testing and Validation

Meta Pixel Helper

Install Meta Pixel Helper Chrome extension to validate events.

Events Manager Test Events

  1. Go to Events Manager → Test Events
  2. Enter your website URL
  3. Perform actions on your site
  4. Verify events appear in real-time

Conversions API Diagnostics

Check Events Manager → Data Sources → Your Pixel → Diagnostics for:

  • Event match quality
  • Deduplication status
  • Data freshness

Common Issues

Duplicate Events

When using hybrid (client + server) tracking, add event_id for deduplication:

const eventId = `${order.id}_${Date.now()}`;

// Client-side
fbq('track', 'Purchase', params, { eventID: eventId });

// Server-side
payload.data[0].event_id = eventId;

Missing User Data

Improve match quality by passing hashed user data:

  • Email (most important)
  • Phone number
  • First/Last name
  • Country, City

Next Steps

// SYS.FOOTER