Event Transformers
Build custom event transformers to convert NextDataLayer events to any platform's format.
Overview
Event transformers are scripts that intercept events from window.NextDataLayer and convert them to formats required by different analytics platforms. This pattern allows you to:
- Send events to platforms not natively supported by the SDK
- Convert SDK events to platform-specific formats
- Add custom logic for event processing
- Route events to multiple destinations with different formats
How It Works
The transformer pattern:
- Waits for
window.NextDataLayerto be available - Intercepts events by overriding the
push()method - Transforms events to platform-specific format
- Routes transformed events to the destination
- Prevents duplicates using event deduplication
Basic Flow
Campaign Cart SDK
↓
window.NextDataLayer.push()
↓
Transformer (intercepts)
↓
Convert format
↓
window.platformLayer.push()
↓
Platform (GA4, TikTok, etc.)Basic Transformer Template
Here's a generic template you can adapt for any platform:
(function() {
'use strict';
// Track processed events to avoid duplicates
const processedEvents = new Set();
// Your event mapping
const EVENT_MAP = {
'dl_view_item': 'ViewContent', // Platform-specific name
'dl_add_to_cart': 'AddToCart',
'dl_purchase': 'Purchase'
// Add more mappings...
};
// Wait for NextDataLayer
const initTransformer = () => {
if (!window.NextDataLayer) {
setTimeout(initTransformer, 100);
return;
}
console.log('[Transformer] Initializing...');
// Override the push method
const originalPush = window.NextDataLayer.push;
window.NextDataLayer.push = function(...args) {
// Call original push first
const result = originalPush.apply(window.NextDataLayer, args);
// Process each pushed item
args.forEach(item => {
if (item && typeof item === 'object' && item.event) {
processEvent(item);
}
});
return result;
};
// Process existing events
window.NextDataLayer.forEach(item => {
if (item && typeof item === 'object' && item.event) {
processEvent(item);
}
});
console.log('[Transformer] Initialized successfully');
};
// Process individual events
const processEvent = (event) => {
// Skip if not mapped
if (!EVENT_MAP[event.event]) {
return;
}
// Create unique ID to prevent duplicates
const eventId = `${event.event}_${event._metadata?.sequence_number || Date.now()}`;
if (processedEvents.has(eventId)) {
return;
}
processedEvents.add(eventId);
// Get platform-specific event name
const platformEventName = EVENT_MAP[event.event];
// Build platform-specific event
const platformEvent = {
event: platformEventName,
// Add your platform-specific fields...
};
// Send to platform (customize this!)
window.yourPlatformLayer = window.yourPlatformLayer || [];
window.yourPlatformLayer.push(platformEvent);
console.log(`[Transformer] Converted ${event.event} → ${platformEventName}`, platformEvent);
};
// Clean up old events periodically (prevent memory leak)
setInterval(() => {
if (processedEvents.size > 1000) {
const entriesToKeep = Array.from(processedEvents).slice(-500);
processedEvents.clear();
entriesToKeep.forEach(entry => processedEvents.add(entry));
}
}, 60000);
// Start the transformer
initTransformer();
})();Platform-Specific Examples
GA4 Transformer
Convert events to Google Analytics 4 format and send to window.dataLayer:
const EVENT_MAP = {
'dl_add_to_cart': 'add_to_cart',
'dl_remove_from_cart': 'remove_from_cart',
'dl_view_item': 'view_item',
'dl_view_item_list': 'view_item_list',
'dl_begin_checkout': 'begin_checkout',
'dl_purchase': 'purchase',
'dl_add_payment_info': 'add_payment_info',
'dl_add_shipping_info': 'add_shipping_info',
'dl_login': 'login',
'dl_sign_up': 'sign_up'
};
const processEvent = (event) => {
if (!EVENT_MAP[event.event]) return;
const ga4Event = {
event: EVENT_MAP[event.event],
event_id: event.event_id
};
// Handle ecommerce events
if (event.ecommerce) {
// Clear ecommerce first (GTM best practice)
window.dataLayer.push({ ecommerce: null });
ga4Event.ecommerce = {
...event.ecommerce,
items: event.ecommerce.items?.map(item => ({
item_id: item.item_id || item.id,
item_name: item.item_name || item.name,
price: item.price,
quantity: item.quantity,
currency: item.currency
}))
};
}
window.dataLayer.push(ga4Event);
};TikTok Pixel Transformer
Convert events to TikTok Pixel format:
const EVENT_MAP = {
'dl_view_item': 'ViewContent',
'dl_add_to_cart': 'AddToCart',
'dl_begin_checkout': 'InitiateCheckout',
'dl_purchase': 'CompletePayment',
'dl_view_item_list': 'ViewContent'
};
const processEvent = (event) => {
if (!EVENT_MAP[event.event]) return;
const tiktokEventName = EVENT_MAP[event.event];
// Build TikTok event data
const tiktokData = {
content_type: 'product'
};
if (event.ecommerce) {
tiktokData.currency = event.ecommerce.currency;
tiktokData.value = event.ecommerce.value;
if (event.ecommerce.items?.[0]) {
tiktokData.content_id = event.ecommerce.items[0].item_id;
tiktokData.content_name = event.ecommerce.items[0].item_name;
}
// For purchase events
if (event.event === 'dl_purchase') {
tiktokData.content_ids = event.ecommerce.items?.map(i => i.item_id);
tiktokData.contents = event.ecommerce.items?.map(i => ({
content_id: i.item_id,
content_name: i.item_name,
quantity: i.quantity,
price: i.price
}));
}
}
// Send to TikTok Pixel
if (window.ttq) {
window.ttq.track(tiktokEventName, tiktokData);
console.log(`[TikTok] ${event.event} → ${tiktokEventName}`, tiktokData);
}
};Snapchat Pixel Transformer
Convert events to Snapchat Pixel format:
const EVENT_MAP = {
'dl_view_item': 'VIEW_CONTENT',
'dl_add_to_cart': 'ADD_CART',
'dl_begin_checkout': 'START_CHECKOUT',
'dl_purchase': 'PURCHASE',
'dl_sign_up': 'SIGN_UP'
};
const processEvent = (event) => {
if (!EVENT_MAP[event.event]) return;
const snapEventName = EVENT_MAP[event.event];
const snapData = {};
if (event.ecommerce) {
snapData.currency = event.ecommerce.currency;
snapData.price = event.ecommerce.value;
if (event.ecommerce.transaction_id) {
snapData.transaction_id = event.ecommerce.transaction_id;
}
if (event.ecommerce.items?.length > 0) {
snapData.item_ids = event.ecommerce.items.map(i => i.item_id);
snapData.item_category = event.ecommerce.items[0].item_category;
snapData.number_items = event.ecommerce.items.length;
}
}
// Send to Snapchat Pixel
if (window.snaptr) {
window.snaptr('track', snapEventName, snapData);
console.log(`[Snapchat] ${event.event} → ${snapEventName}`, snapData);
}
};Pinterest Tag Transformer
Convert events to Pinterest Tag format:
const EVENT_MAP = {
'dl_view_item': 'pagevisit',
'dl_add_to_cart': 'addtocart',
'dl_begin_checkout': 'checkout',
'dl_purchase': 'checkout',
'dl_sign_up': 'signup',
'dl_view_item_list': 'viewcategory'
};
const processEvent = (event) => {
if (!EVENT_MAP[event.event]) return;
const pinterestEventName = EVENT_MAP[event.event];
const pinterestData = {};
if (event.ecommerce) {
pinterestData.currency = event.ecommerce.currency;
pinterestData.value = event.ecommerce.value;
if (event.ecommerce.items?.length > 0) {
pinterestData.line_items = event.ecommerce.items.map(item => ({
product_name: item.item_name,
product_id: item.item_id,
product_price: item.price,
product_quantity: item.quantity
}));
}
if (event.event === 'dl_purchase' && event.ecommerce.transaction_id) {
pinterestData.order_id = event.ecommerce.transaction_id;
pinterestData.order_quantity = event.ecommerce.items?.reduce((sum, i) => sum + i.quantity, 0);
}
}
// Send to Pinterest Tag
if (window.pintrk) {
window.pintrk('track', pinterestEventName, pinterestData);
console.log(`[Pinterest] ${event.event} → ${pinterestEventName}`, pinterestData);
}
};Advanced Patterns
Multi-Platform Transformer
Send events to multiple platforms from one transformer:
const processEvent = (event) => {
// Send to GA4
if (window.dataLayer && GA4_EVENT_MAP[event.event]) {
const ga4Event = buildGA4Event(event);
window.dataLayer.push(ga4Event);
}
// Send to TikTok
if (window.ttq && TIKTOK_EVENT_MAP[event.event]) {
const tiktokData = buildTikTokEvent(event);
window.ttq.track(TIKTOK_EVENT_MAP[event.event], tiktokData);
}
// Send to Snapchat
if (window.snaptr && SNAP_EVENT_MAP[event.event]) {
const snapData = buildSnapEvent(event);
window.snaptr('track', SNAP_EVENT_MAP[event.event], snapData);
}
};Conditional Routing
Route events based on conditions:
const processEvent = (event) => {
// Only send high-value purchases to premium platforms
if (event.event === 'dl_purchase' && event.ecommerce?.value > 1000) {
sendToPremiumPlatform(event);
}
// Send all events to GA4
sendToGA4(event);
// Filter test users
if (event.user_properties?.customer_email?.includes('@test.com')) {
return; // Don't send to paid platforms
}
sendToFacebookPixel(event);
};Event Enrichment
Add additional data before sending:
const processEvent = (event) => {
// Enrich with custom data
const enrichedEvent = {
...event,
app_version: window.APP_VERSION,
environment: window.ENV,
user_segment: getUserSegment(),
ab_test_variant: getActiveVariant()
};
// Send enriched event
sendToPlatform(enrichedEvent);
};Installation
-
Create transformer script
Save your transformer as a
.jsfile (e.g.,transformer-tiktok.js) -
Add to page after SDK
<!-- Campaign Cart SDK --> <script src="https://cdn.jsdelivr.net/gh/NextCommerceCo/[email protected]/dist/loader.js" type="module"></script> <!-- Your Transformer --> <script src="/path/to/transformer-tiktok.js"></script> -
Load platform script
Ensure the target platform's script loads before the transformer:
<!-- TikTok Pixel --> <script src="https://analytics.tiktok.com/i18n/pixel/sdk.js?sdkid=YOUR_PIXEL_ID"></script> <!-- Campaign Cart SDK --> <script src="https://cdn.jsdelivr.net/gh/NextCommerceCo/[email protected]/dist/loader.js" type="module"></script> <!-- TikTok Transformer --> <script src="/path/to/transformer-tiktok.js"></script>
Best Practices
1. Deduplicate Events
Always track processed events to prevent duplicates:
const processedEvents = new Set();
const processEvent = (event) => {
const eventId = `${event.event}_${event._metadata?.sequence_number}`;
if (processedEvents.has(eventId)) {
return; // Skip duplicate
}
processedEvents.add(eventId);
// Process event...
};2. Clean Up Memory
Prevent memory leaks by periodically cleaning old data:
setInterval(() => {
if (processedEvents.size > 1000) {
const entriesToKeep = Array.from(processedEvents).slice(-500);
processedEvents.clear();
entriesToKeep.forEach(entry => processedEvents.add(entry));
}
}, 60000); // Every minute3. Handle Missing Data
Gracefully handle missing or malformed data:
const processEvent = (event) => {
if (!event || !event.event) {
console.warn('[Transformer] Invalid event', event);
return;
}
const value = event.ecommerce?.value ?? 0;
const items = event.ecommerce?.items || [];
// Use defaults...
};4. Debug Logging
Add conditional debug logging:
const DEBUG = window.location.hostname === 'localhost' ||
window.location.search.includes('debug=true');
const processEvent = (event) => {
// Process event...
if (DEBUG) {
console.log(`[Transformer] Converted ${event.event}`, platformEvent);
}
};5. Error Handling
Wrap platform calls in try-catch:
const processEvent = (event) => {
try {
const platformEvent = buildEvent(event);
window.platform.track(platformEvent);
} catch (error) {
console.error('[Transformer] Error processing event:', error, event);
// Don't throw - let other code continue
}
};Debugging
Check Transformer Status
// Check if NextDataLayer exists
console.log(window.NextDataLayer);
// Check if transformer modified push
console.log(window.NextDataLayer.push.toString());
// View all events
console.log(window.NextDataLayer);Enable Debug Mode
Add ?debug=true to your URL to see console logs:
https://yoursite.com?debug=trueVerify Platform Script Loaded
// TikTok
console.log(typeof window.ttq); // Should be 'object'
// Snapchat
console.log(typeof window.snaptr); // Should be 'function'
// Pinterest
console.log(typeof window.pintrk); // Should be 'function'
// GA4
console.log(typeof window.gtag); // Should be 'function'Full Example: TikTok Transformer
Complete working example for TikTok Pixel:
(function() {
'use strict';
const processedEvents = new Set();
const DEBUG = window.location.search.includes('debug=true');
const EVENT_MAP = {
'dl_view_item': 'ViewContent',
'dl_add_to_cart': 'AddToCart',
'dl_begin_checkout': 'InitiateCheckout',
'dl_purchase': 'CompletePayment'
};
const initTransformer = () => {
if (!window.NextDataLayer) {
setTimeout(initTransformer, 100);
return;
}
if (DEBUG) console.log('[TikTok Transformer] Initializing...');
const originalPush = window.NextDataLayer.push;
window.NextDataLayer.push = function(...args) {
const result = originalPush.apply(window.NextDataLayer, args);
args.forEach(item => {
if (item && typeof item === 'object' && item.event) {
processEvent(item);
}
});
return result;
};
window.NextDataLayer.forEach(item => {
if (item && typeof item === 'object' && item.event) {
processEvent(item);
}
});
if (DEBUG) console.log('[TikTok Transformer] Initialized');
};
const processEvent = (event) => {
if (!EVENT_MAP[event.event]) return;
const eventId = `${event.event}_${event._metadata?.sequence_number || Date.now()}`;
if (processedEvents.has(eventId)) return;
processedEvents.add(eventId);
const tiktokEventName = EVENT_MAP[event.event];
const tiktokData = {
content_type: 'product'
};
if (event.ecommerce) {
tiktokData.currency = event.ecommerce.currency || 'USD';
tiktokData.value = event.ecommerce.value || 0;
if (event.ecommerce.items?.length > 0) {
tiktokData.content_id = event.ecommerce.items[0].item_id;
tiktokData.content_name = event.ecommerce.items[0].item_name;
if (event.event === 'dl_purchase') {
tiktokData.content_ids = event.ecommerce.items.map(i => i.item_id);
tiktokData.contents = event.ecommerce.items.map(i => ({
content_id: i.item_id,
content_name: i.item_name,
quantity: i.quantity,
price: i.price
}));
}
}
}
try {
if (window.ttq) {
window.ttq.track(tiktokEventName, tiktokData);
if (DEBUG) {
console.log(`[TikTok] ${event.event} → ${tiktokEventName}`, tiktokData);
}
}
} catch (error) {
console.error('[TikTok Transformer] Error:', error);
}
};
setInterval(() => {
if (processedEvents.size > 1000) {
const entriesToKeep = Array.from(processedEvents).slice(-500);
processedEvents.clear();
entriesToKeep.forEach(entry => processedEvents.add(entry));
}
}, 60000);
initTransformer();
})();Download Example Scripts
Get ready-to-use transformer scripts:
- Analytics Overview - Analytics setup and configuration
- TikTok Transformer - Copy from example above
- Snapchat Transformer - Copy from example above
- Pinterest Transformer - Copy from example above
Related Documentation
- Analytics Overview - Main analytics documentation
- Custom Events - Creating custom events
- Examples - Built-in provider integrations
- Configuration - SDK configuration options