Skip to main content

Event Filters

Available Since

Version 1.51.0 of the Pixel Manager

Customize event tracking data for all pixels using JavaScript (front-end) and PHP (server-side) filters.

Why Use the Command Queue?

Always wrap your filters in the Pixel Manager command queue:

window._pmwq = window._pmwq || [];
window._pmwq.push(function() {
pmw.hooks.addFilter('pmw_pixel_data_facebook', 'my-plugin', function(pixelData) {
pixelData.custom_data.custom_field = 'my_value';
return pixelData;
});
});

Why? Caching systems and JavaScript optimizers may shuffle, combine, or delay script loading. The command queue provides a 100% reliable way to ensure your filters register after Pixel Manager loads, preventing race conditions and initialization errors. Without it, pmw.hooks may not exist yet, causing your code to fail silently.

Adding to WordPress:

/wp-content/themes/child-theme/functions.php
add_action('wp_head', function() {
?>
<script>
window._pmwq = window._pmwq || [];
window._pmwq.push(function() {
pmw.hooks.addFilter('pmw_pixel_data_facebook', 'my-plugin', function(pixelData) {
pixelData.custom_data.custom_field = 'my_value';
return pixelData;
});
});
</script>
<?php
}, 1); // Priority 1 to load early in <head>

Planning Your Filters

Before implementing filters, consider where your events are processed:

Front-End vs. Server-Side Events

Front-end events (processed in the browser):

  • All events when server-to-server tracking is disabled
  • Events like add_to_cart, view_item, begin_checkout, etc.
  • Purchase events only if processed through the browser

Server-side events (processed on the server):

  • Purchase events when server-to-server tracking is enabled (Facebook CAPI, TikTok EAPI, Pinterest APIC, Snapchat CAPI)
  • Google Analytics purchase events when Measurement Protocol is enabled
  • These are always sent from the server, never through the browser

Why server-side purchase events? Browser-based tracking can be blocked by ad blockers, browser extensions, privacy settings, or network-level filters. When server-to-server tracking is enabled, purchase events are compiled and sent directly from your server to the advertising platforms, completely bypassing the browser. This makes them immune to client-side blocking, ensuring 100% reliable conversion tracking. The tradeoff is that these events are processed in a completely independent pipeline, which means they require separate filters.

Critical: Purchase Events with Server-to-Server Tracking

When server-to-server tracking is active, purchase events are compiled and sent exclusively from the server, built from the WooCommerce order on an order status transition. This means:

  • Front-end filters will NOT affect these purchase events
  • The pmw_server_event_payload_* pipeline below will NOT affect them either. That pipeline processes browser events that are forwarded to the server, and its endpoint rejects purchase events by design, so a callback on pmw_server_event_payload_event_purchase or pmw_server_event_payload_facebook_purchase never runs.

What to use instead:

GoalFilter
Suppress the server-side purchase for an orderpmw_skip_s2s_purchase_event
Modify the Google Analytics 4 Measurement Protocol purchase payloadpmw_server_event_payload_google_analytics, pmw_server_event_payload_google_analytics_purchase, pmw_server_event_payload_post, which the GA4 Measurement Protocol applies to its own payload
Modify the purchase payload of Facebook, TikTok, Pinterest, Snapchat, Reddit or Microsoft AdvertisingNo payload filter exists on that path. Use pmw_skip_s2s_purchase_event to gate the event, or the platform's own settings to change what is sent
// Suppress the server-side purchase for orders that are not paid yet
add_filter('pmw_skip_s2s_purchase_event', function ($skip, $order) {

if (!$order instanceof WC_Order) {
return $skip;
}

return $order->is_paid() ? $skip : true;
}, 10, 2);

For the full pattern, including which order status transitions trigger the server-side purchase, see the Status-Driven Purchase Conversions recipe.

Front-End Filters (JavaScript)

Filter Pipeline

Events flow through 4 stages. Each filter must return the modified data:

  1. pmw_event_payload_pre - Before pixel transformations (modify core event data)
  2. pmw_pixel_data_{pixel} - Per-pixel transformations (e.g., pmw_pixel_data_facebook)
  3. pmw_event_payload_{event} - Per-event type (e.g., pmw_event_payload_purchase)
  4. pmw_event_payload_post - Final stage (logging, debugging)

Supported pixels: facebook, google_ads, google_analytics, tiktok, pinterest, snapchat, linkedin, microsoft_ads, twitter, reddit, taboola, outbrain

Supported events: page_view, add_to_cart, view_item, view_item_list, begin_checkout, add_payment_info, add_to_wishlist, search, purchase

API

pmw.hooks.addFilter(hookName, namespace, callback, priority)
pmw.hooks.removeFilter(hookName, namespace)
pmw.hooks.hasFilter(hookName, namespace)
  • namespace - Unique identifier to prevent conflicts (e.g., 'my-plugin/feature')
  • priority - Execution order (default: 10, lower runs first)
  • Return null to block an event from firing

Examples

Add Custom Facebook Parameters

window._pmwq.push(function() {
pmw.hooks.addFilter('pmw_pixel_data_facebook', 'my-store', function(pixelData) {
pixelData.custom_data = pixelData.custom_data || {};
pixelData.custom_data.store_location = 'NYC';
pixelData.custom_data.user_segment = 'premium';
return pixelData;
});
});

Adjust Prices Globally

window._pmwq.push(function() {
pmw.hooks.addFilter('pmw_event_payload_pre', 'price-modifier', function(payload) {
if (payload.event_data?.product?.price) {
payload.event_data.product.price *= 1.15; // +15% markup
}
return payload;
});
});

Filter by Event Type

window._pmwq.push(function() {
pmw.hooks.addFilter('pmw_event_payload_purchase', 'purchase-tags', function(payload) {
if (payload.event_data?.order_total > 500) {
payload.event_data.high_value = true;
}
return payload;
});
});

Block Events Conditionally

window._pmwq.push(function() {
pmw.hooks.addFilter('pmw_pixel_data_facebook', 'admin-filter', function(pixelData) {
if (window.userIsAdmin) {
return null; // Block event
}
return pixelData;
});
});

Use Priority for Execution Order

window._pmwq.push(function() {
// Runs first (priority 5)
pmw.hooks.addFilter('pmw_event_payload_pre', 'early-filter', function(payload) {
payload.event_data.processed_by = ['early-filter'];
return payload;
}, 5);

// Runs second (priority 10)
pmw.hooks.addFilter('pmw_event_payload_pre', 'late-filter', function(payload) {
payload.event_data.processed_by.push('late-filter');
return payload;
}, 10);
});

Event Handling Filters (JavaScript)

Two more front-end filters do not change an event payload. They change how the library treats an event. They are registered exactly like the payload filters above, through the command queue.

pmw_duplicate_event_window

Available Since

Version 1.67.1 of the Pixel Manager

One shopper action often reaches the library twice. WooCommerce and its ecosystem announce the same add to cart through a click and through their own event, themes re-render regions that hold a bound button, and a preselected variation is reported both on page load and by WooCommerce's own variation event. Each pass produces its own event ID, so the destination cannot recognize the repeat and counts the interaction twice, on the browser pixel and on the server-side API alike.

The Pixel Manager therefore suppresses an event that is identical to one it reported within the last 1000 milliseconds. Identical means the same event name on the same URL with the same product, transaction, shipping tier and payment method, so the same product viewed again after a navigation is its own event, and so is a search for a different term. The purchase event is never suppressed by this guard, because it is already locked to one per order by a stronger mechanism.

Use the filter to widen the window, to narrow it, or to switch the guard off by returning 0.

window._pmwq = window._pmwq || [];
window._pmwq.push(function () {
pmw.hooks.addFilter('pmw_duplicate_event_window', 'my-plugin', function (windowMs, eventName, eventData) {

// A theme that double-fires with more delay than the default covers.
if (eventName === 'add_to_cart') {
return 2500;
}

return windowMs;
});
});
warning

Returning 0 brings the double counting back for every theme and plugin combination that causes it. Raise the window instead of switching it off, and only switch it off while you are tracking down where a duplicate comes from.

pmw_navigation_events

Available Since

Version 1.60.0 of the Pixel Manager

Some events fire on a click that immediately navigates the browser away. begin_checkout is the canonical case: it fires on the click of the Proceed to checkout button, which then loads the checkout page. For those events the server-to-server send has to be issued synchronously, before the page unloads. Otherwise the asynchronous enrichment steps defer the send past the navigation, navigator.sendBeacon is never reached, and the server-side event is silently lost while the browser pixel still fires.

The list contains begin_checkout. Add your own event names when a custom template fires one of them on a navigating link.

window._pmwq = window._pmwq || [];
window._pmwq.push(function () {
pmw.hooks.addFilter('pmw_navigation_events', 'my-plugin', function (events) {
events.push('add_payment_info');
return events;
});
});

Return an array of internal event names. See Internal Event Names for the list.

note

A synchronous send skips the asynchronous parts of the enrichment, so an event on this list can carry slightly less user data than the same event would on a page that stays. That is the trade-off: less data on an event that arrives, instead of a complete event that never arrives.

Server-Side Filters (PHP)

Filter Pipeline

Browser events that are forwarded to the server (Facebook CAPI, TikTok EAPI, Pinterest APIC, Snapchat CAPI) flow through 5 stages:

  1. pmw_server_event_payload_pre - Before pixel processing (all pixels at once)
  2. pmw_server_event_payload_{pixel} - Per-pixel, all events (e.g., facebook)
  3. pmw_server_event_payload_event_{event} - Per-event, all pixels (e.g., purchase)
  4. pmw_server_event_payload_{pixel}_{event} - Specific pixel + event (e.g., facebook_purchase)
  5. pmw_server_event_payload_post - Final stage before API transmission

:::danger Purchase events do not run through this pipeline The events that reach these filters are the browser funnel events forwarded to the server: view_item, add_to_cart, begin_checkout and the rest. Purchase events are built from the WooCommerce order on an order status transition, on a separate path, and the endpoint that feeds this pipeline rejects purchase outright. A callback on pmw_server_event_payload_event_purchase or on pmw_server_event_payload_{pixel}_purchase therefore never runs, with one exception: the Google Analytics 4 Measurement Protocol applies stages 2, 4 and 5 to its own purchase payload, so pmw_server_event_payload_google_analytics_purchase does work for GA4.

To suppress a server-side purchase, use pmw_skip_s2s_purchase_event. See the Status-Driven Purchase Conversions recipe for the full pattern. :::

note

Since Pixel Manager Pro 1.66.0, the server-side Google Analytics refund events run through this pipeline as well, under the event name refund (pmw_server_event_payload_google_analytics_refund). Both the partial and the full refund payload pass through it. To switch refund reporting off completely instead, use the pmw_google_analytics_refund_tracking filter.

Examples

Modify All Add-To-Cart Events (All Pixels)

add_filter('pmw_server_event_payload_event_add_to_cart', function($pixel_data, $pixel_name, $event_name) {
// Add a timestamp to all add_to_cart events across all pixels
$pixel_data['custom_data']['event_timestamp'] = time();

// Categorize by value
if (isset($pixel_data['custom_data']['value'])) {
$value = $pixel_data['custom_data']['value'];
$pixel_data['custom_data']['value_tier'] = $value < 50 ? 'low' : ($value < 200 ? 'medium' : 'high');
}

return $pixel_data;
}, 10, 3);

Add Custom Data to Facebook Only

add_filter('pmw_server_event_payload_facebook', function($pixel_data, $pixel_name) {
$pixel_data['user_data']['subscription_status'] = 'premium';
return $pixel_data;
}, 10, 2);

Target Specific Pixel + Event

add_filter('pmw_server_event_payload_facebook_begin_checkout', function($pixel_data, $pixel_name, $event_name) {
$user_id = get_current_user_id();
if ($user_id) {
$ltv = get_user_meta($user_id, 'customer_ltv', true);
if ($ltv > 1000 && isset($pixel_data['custom_data']['value'])) {
$pixel_data['custom_data']['value'] *= 1.2; // Boost value for high-LTV customers
}
}
return $pixel_data;
}, 10, 3);

Block Events Conditionally

// Block low-value add-to-cart events from all pixels
add_filter('pmw_server_event_payload_event_add_to_cart', function($pixel_data, $pixel_name, $event_name) {
if (isset($pixel_data['custom_data']['value']) && $pixel_data['custom_data']['value'] < 10) {
return null; // Blocks event for all pixels
}
return $pixel_data;
}, 10, 3);

// To block a low-value PURCHASE, gate it on the order instead. Purchase events
// never reach the filters above.
add_filter('pmw_skip_s2s_purchase_event', function ($skip, $order) {
if (!$order instanceof WC_Order) {
return $skip;
}
return ((float) $order->get_total() < 10) ? true : $skip;
}, 10, 2);

// Block only from Facebook
add_filter('pmw_server_event_payload_facebook', function($pixel_data) {
if (some_condition()) {
return null;
}
return $pixel_data;
});

Event Payload Structure

{
event: 'add_to_cart',
event_data: {
product: {
id: 123,
name: 'Product Name',
price: 99.99,
quantity: 1,
currency: 'USD',
categories: ['Electronics']
}
},
pixels: {
facebook: {
event_name: 'AddToCart',
event_id: 'unique-id',
custom_data: { /* pixel-specific data */ }
},
google_analytics: {
event_name: 'add_to_cart',
event_data: { /* pixel-specific data */ }
}
}
}

Best Practices

  1. Always return the value - Filters must return the modified data or null to block
  2. Use unique namespaces - Format: 'plugin-name/feature' or 'company-name/modifier'
  3. Wrap in _pmwq - Ensures Pixel Manager loads before your filters
  4. Check data exists - Use optional chaining: payload.event_data?.product?.price
  5. Use appropriate priority - Default is 10; lower numbers run first
  6. Test thoroughly - Check browser console for filter execution logs

Debugging

Console Logging

Filter execution is logged to the browser console:

🔍 Pre-processing filter called: add_to_cart
📊 GA pixel data filter called: add_to_cart

Inspect Payloads

window._pmwq.push(function() {
pmw.hooks.addFilter('pmw_event_payload_post', 'debugger', function(payload, eventName) {
console.log(`Event: ${eventName}`, payload);
return payload;
}, 999); // High priority to run last
});

PHP Debugging

add_filter('pmw_server_event_payload_post', function($pixel_data, $pixel_name) {
if (defined('WP_DEBUG') && WP_DEBUG) {
error_log("Sending {$pixel_data['event_name']} to {$pixel_name}: " . json_encode($pixel_data));
}
return $pixel_data;
}, 10, 2);

Migration from Old System

If you were using jQuery event listeners:

Before:

jQuery(document).on("pmw:add-to-cart", function(event, product) {
product.price = product.price * 1.1;
});

After (Recommended - Using Filters):

window._pmwq.push(function() {
pmw.hooks.addFilter('pmw_event_payload_add_to_cart', 'namespace', function(payload) {
payload.event_data.product.price *= 1.1;
return payload;
});
});

Alternative - Using Public API Events:

Available Since Version 1.52.0

If you only need to listen to events (not modify them), you can use the new public API events instead of filters.

window._pmwq.push(function() {
// Listen to the official public API event
jQuery(document).on('pmw:event:add-to-cart', function(event, payload) {
// payload contains fully processed event data
console.log('Product added:', payload.event_data.product);
console.log('Pixel-specific data:', payload.pixels);

// You can trigger your own tracking here
myCustomTracker.track('add_to_cart', payload.event_data);
});
});

The pmw:event:* events provide the complete processed payload including all pixel adaptations. See the Command Queue documentation for the full list of available events.

Event Name Mapping

The Pixel Manager uses internal snake_case event names which each pixel adapter transforms to the vendor-specific format.

Internal Event Names

Internal EventDescription
page_viewUser views any page
view_itemUser views a single product
view_categoryUser views a product category
add_to_cartUser adds product to cart
begin_checkoutUser begins checkout process
add_payment_infoUser adds payment information
purchasePurchase completed
add_to_wishlistUser adds product to wishlist
searchUser performs a search
loginUser logs in

Vendor Event Name Mappings

Each pixel adapter transforms the internal event name to the vendor's format:

Internal EventFacebookTikTokSnapchatPinterest
page_viewPageViewPAGE_VIEW
view_itemViewContentViewContentVIEW_CONTENTpagevisit
add_to_cartAddToCartAddToCartADD_CARTaddtocart
purchasePurchasePurchasePURCHASEcheckout

Note: A "—" indicates the pixel doesn't support this event (the adapter returns null and the event is skipped for that pixel).

Modifying Vendor-Specific Data

Use pmw_pixel_data_{pixel} filters to modify data after it's been transformed to the vendor format:

window._pmwq.push(function() {
// Modify Facebook-specific data structure
pmw.hooks.addFilter('pmw_pixel_data_facebook', 'my-plugin', function(pixelData, eventName) {
// pixelData.event_name is already "AddToCart" (Facebook format)
if (eventName === 'add_to_cart') {
pixelData.custom_data.source = 'mobile_app';
}
return pixelData;
});
});

Make more money from your ads with high-precision tracking