.dev
Storefront templates

Marketing pixels

How Meta and TikTok events fire from themes — automatic events, the FeeefPixels helper, dynamic Lead/Purchase objectives, and testing.

Merchants configure pixel ids on the store (store.metaPixelIds, store.tiktokPixelIds, plus Google Analytics/GTM and Microsoft Clarity ids). Lithium loads the scripts and ships an event layer that fans one logical event out to Meta (fbq), TikTok (ttq) and GA4 at once. Theme code never talks to window.fbq / window.ttq directly — it calls the injected helpers so all platforms stay aligned.

How the scripts load

Every storefront route (home, products, product, checkout, thanks, contact, landing, embed, legal pages) renders a PixelsWrapper that injects the pixel scripts with Next.js lazyOnload strategy — tracking never blocks first paint or LCP. When a store has multiple Meta pixel ids, events fire per id via fbq('trackSingle', id, …).

Loading the scripts also fires the baseline PageView (Meta) / ttq.page() (TikTok) automatically.

Automatic vs manual

SurfaceWho fires events
Script loadAutomatic — PageView on every tracked route
Built-in order form (product_order_form, checkout)Automatic — ViewContent on mount, AddToCart, identify, draft/pending conversions with dedupe
Custom order forms / custom PDP componentsYour theme code — via FeeefPixels, matching the table below

If your theme replaces the native order form with custom JSX, the pixel work moves to you. The checklist at the bottom is what marketplace review expects.

Scope API

Injected into custom components (no imports):

GlobalUse
FeeefPixelsPreferred — fireViewContent, fireAddToCart, fireConversion, fireIdentify, plus the lower-level helpers re-exported
EventManagerLow-level — viewContent, addToCart, lead, purchase, initiateCheckout, identify, search
shouldSuppressOrderPixelEvents(order)Skip fake / security-flagged orders

FeeefPixels wraps EventManager with the store-level guards (pixel ids present, client mode allowed, order suppression) already applied — use it unless you need something exotic.

Events reference

Match the moments Lithium's native form uses:

MomentEventCall
PDP mountViewContentFeeefPixels.fireViewContent({ store, product, currency, value })
Add to cartAddToCartFeeefPixels.fireAddToCart({ store, product, quantity, value }) after cart.add
Draft order created (status: "draft")Lead or Purchase (dynamic)FeeefPixels.fireConversion({ …, phase: "draft" })
Order submitted (status: "pending")Lead or Purchase (dynamic)FeeefPixels.fireConversion({ …, phase: "pending" })
Before the pending conversionAdvanced matchingFeeefPixels.fireIdentify({ store, order, phone })
Checkout start (optional)InitiateCheckoutEventManager.initiateCheckout(...)

GA4 equivalents (view_item, add_to_cart, generate_lead, purchase, begin_checkout) fire from the same calls — no extra work.

ViewContent and AddToCart

// PDP mount
React.useEffect(() => {
  if (!product) return;
  FeeefPixels.fireViewContent({
    store,
    product,
    currency: currencyCode,       // ISO code, e.g. "DZD"
    value: product.price,
  });
}, [product?.id]);

// After a successful add-to-cart
FeeefPixels.fireAddToCart({ store, product, quantity: qty, value: qty * price });

fireViewContent is deduplicated internally (a 2-second window per product id), so a re-render will not double-fire.

Conversions — draft and pending

Feeef COD forms create a draft order as the shopper fills the form, then a pending order on submit. Each phase fires one conversion whose event name is resolved dynamically (next section):

// Draft phase (form partially filled, draft order returned by the API)
FeeefPixels.fireConversion({
  store, product, order, phase: "draft", currency, value,
});

// Pending phase (real submit) — dedupe Purchase if the draft already sent one
FeeefPixels.fireIdentify({ store, order, phone: order.customerPhone });
const res = FeeefPixels.fireConversion({
  store, product, order, phase: "pending", currency, value,
  purchaseAlreadySent: purchaseEventSentRef.current,
});
purchaseEventSentRef.current = res.purchaseSent;

fireConversion returns { fired: "lead" | "purchase" | "none", purchaseSent } — keep purchaseSent in a ref so a draft-phase Purchase suppresses the pending-phase one.

Dynamic objectives

Which event a conversion actually sends is merchant-configurable per phase:

  • Draft phase reads draftObjective — default Lead.
  • Pending phase reads objective — default Purchase.

The value is resolved through a cascade (first non-empty wins):

  1. Product publicIntegrationsData.metaPixelData (when that block is enabled)
  2. Product publicIntegrationsData.tiktokPixelData
  3. Store publicIntegrations.metaPixel
  4. Store publicIntegrations.tiktokPixel
  5. Phase default (Lead for draft, Purchase for pending)

Accepted values are Lead, Purchase, or none (case-insensitive; none disables the phase). FeeefPixels.fireConversion runs this cascade for you — never hardcode Lead or Purchase in theme code.

Client vs server mode

Each platform's store integration has a mode: null / "client" / "both" fire browser events; "server" means the backend sends conversions via the platform APIs instead. The FeeefPixels guards skip client events for a platform configured server-only — when all configured platforms are server-only, no client event fires at all. This is already built into shouldFireClientPixels(store), which every fire* helper checks.

Suppression and dedupe

  • Fake orders — orders flagged by the anti-abuse layer must not fire conversions: every conversion path checks shouldSuppressOrderPixelEvents(order).
  • Purchase dedupe — Meta events carry eventID = order id, and fired order ids are remembered in localStorage, so a re-render or back-navigation cannot double-count a purchase.
  • ViewContent dedupe — 2-second in-memory window per content_ids set.

Checklist for custom COD forms

  • ViewContent on product mount
  • AddToCart on the add-to-cart CTA
  • Draft conversion via FeeefPixels.fireConversion(..., phase: "draft")
  • Pending identify + conversion (phase: "pending") with Purchase dedupe
  • No raw window.fbq / window.ttq calls
  • Fake-order suppression respected (shouldSuppressOrderPixelEvents)

Testing pixels

In the browser — run the shop in a non-production build and watch the console: EventManager logs every fired event ([EventManager] Fired GA4 event…, identify payloads with masked PII) in debug mode. Meta's Pixel Helper and TikTok's Pixel Helper browser extensions confirm the script picked events up, and Meta Events Manager's Test events tab shows them arriving live.

Server-side test events — the API exposes authenticated helpers that send a test event directly to the platform (useful for verifying ids/tokens without a storefront visit):

POST /api/v1/actions/sendMetaPixelTestEvent     { id, key, code? }
POST /api/v1/actions/sendTiktokPixelTestEvent   { id, accessToken, testCode? }

code / testCode is the platform's test-event code, so the event lands in the Test events view instead of production data. See API conventions for auth.

Draft flow — fill a COD form partially on a preview shop and confirm exactly one Lead (or configured draft objective) fires; submit and confirm the pending conversion, with no duplicate Purchase.

On this page