Facebook Pixel: Technical Details
Facebook Pixel plugin for Sylius: flow, plugin structure, entity, admin wiring and Messenger routing of the CAPI messages.
Facebook Pixel: Technical Details
Live Demo: Try this and other plugins live β no installation, no risk: demo.markus-michalski.net
Flow
graph TD
A["Twig hook / add-to-cart event"] --> B["ChannelConfigurationResolver"]
B --> C{"Event tracked for channel?"}
C -->|"No"| D["Render nothing"]
C -->|"Yes"| E["Render fbq script"]
C -->|"Yes"| F["PixelEventDispatched"]
F --> G["CapiEventListener"]
G --> H{"CAPI selected and consent ok?"}
H -->|"No"| I["Stop"]
H -->|"Yes"| J["PiiHasher hashes email and phone"]
J --> K["Messenger: SendCapiEventMessage"]
K --> L["SendCapiEventHandler"]
L --> M["ConversionsApiClient"]
M --> N["Meta Conversions API"]
Both halves of an event, the rendered fbq() call and the dispatched PixelEventDispatched, arise at the same call site and share one event_id generated once (PixelEventIdGenerator). For AddToCart, the server-side copy arises in the listener on sylius.cart_item_post_add, and the browser call is handed to the next page via the session.
The consent check and the hashing run synchronously in the request, because only there are the cookies and customer data available. Every error in this chain is logged and swallowed, so a tracking problem never breaks the page or the add-to-cart action.
Plugin structure
src/
βββ Api/ Shop API resource + provider (read-only settings)
βββ Capi/ Conversions API payload, client, PII hasher, server event context
βββ Command/ SetupCommand (bin/console mmd:facebook-pixel:setup)
βββ Consent/ CapiConsentPolicy + CapiConsentEvidenceProviderInterface, Klaro cookie reader
βββ Customer/ Resolves the logged-in customer's email for CAPI matching
βββ Entity/ ChannelConfiguration (one row per Sylius channel)
βββ Event/ PixelEventDispatched
βββ EventListener/ AddToCartListener, CapiEventListener, ProvisionKlaroServiceListener
βββ Form/Type/ Admin form (ChannelConfigurationFormType)
βββ Klaro/ Runtime detection of Klaro, consent gate, catalog creation
βββ Menu/ Admin sidebar entry (group MMKreativ Plugins)
βββ Messenger/ SendCapiEventMessage + its handler
βββ Migrations/ Four Doctrine migrations (see Installation)
βββ Model/ FacebookPixelEvent enum, PendingAddToCartFlash
βββ Renderer/ Builds the fbq() script markup
βββ Repository/ ChannelConfigurationRepository
βββ Resolver/ ChannelConfigurationResolver (channel to settings row)
βββ Security/ CapiAccessTokenCipher (libsodium secretbox)
βββ Service/ PixelEventIdGenerator
βββ Twig/ FacebookPixelTwigExtension (the functions the hooks call)
Entity
ChannelConfiguration (table mmd_sylius_facebook_pixel_channel_configuration), one row per channel (unique on channel_id):
| Column | Type | Notes |
|---|---|---|
channel_id |
int, unique FK | ON DELETE CASCADE |
enabled |
bool | Master switch, default off |
pixel_id |
string(32), nullable | Digits only, checked by form validation |
track_page_view ... track_purchase |
bool | One column per browser event, default on |
track_gross_prices |
bool | Default on |
use_klaro_consent |
bool, default true | Klaro gating box |
capi_enabled |
bool | Default off |
capi_require_consent_evidence |
bool, default true | Fail-closed consent requirement |
capi_access_token_ciphertext |
text, nullable | Encrypted, never plaintext |
capi_test_event_code |
string(255), nullable | |
capi_event_selection |
json | List of FacebookPixelEvent values sent via CAPI, default empty |
Admin
| Resource | Route alias | Grid |
|---|---|---|
| Channel settings | mmd_sylius_facebook_pixel_plugin.channel_configuration |
mmd_sylius_facebook_pixel_plugin_admin_channel_configuration (Channel, Pixel ID, Tracking enabled, Conversions API enabled) |
The page is reachable in the admin under MMKreativ Plugins > Facebook Pixel (icon tabler:brand-meta) and lives under the path /<admin-path>/facebook-pixel-channel-configurations. If the admin route import is missing, only the menu entry is missing; the other admin pages are not affected.
Messaging
SendCapiEventMessage is routed to the main Messenger transport that Sylius itself uses for asynchronous work. No transport of its own is needed, but a running worker is (bin/console messenger:consume), otherwise the messages stay in the queue.
The message carries the channel code, event name, event ID, timestamp, source URL, event data, IP address, user agent, the _fbc/_fbp cookie values and the already hashed email and phone number. It contains no plaintext email or phone number, and there is no snapshot of the settings: the handler reads the current ChannelConfiguration and the token only at delivery, so a setting changed in the meantime (or CAPI switched off) is respected.
The call to Meta is an HTTP POST to the Graph API (https://graph.facebook.com, version v21.0) with a 5-second timeout. Only HTTP 200 counts as success; anything else is logged without a retry.