Skip to content

Custom Events & Advanced Tracking

Track custom business events and transform events before they’re sent to providers.

Track custom events with arbitrary data:

window.NextAnalytics.track({
event: 'newsletter_subscribe',
list_name: 'Weekly Newsletter',
source: 'footer_form'
});

Custom events are sent to all enabled providers and stored in window.NextDataLayer.

Every event is automatically enriched with:

  • event_id - Unique event identifier
  • event_time - ISO timestamp
  • user_properties - Current user data (visitor_type, customer_email, etc.)
  • attribution - UTM parameters, funnel, affiliate data
  • session_id - Current session identifier
  • Context - page_location, page_title, user_agent, screen_resolution, viewport_size

You don’t need to add these manually - they’re included automatically.

// Newsletter subscription
window.NextAnalytics.track({
event: 'newsletter_subscribe',
list: 'weekly_digest',
source: 'footer'
});

For custom e-commerce events, include product data using the GA4 ecommerce format:

window.NextAnalytics.track({
event: 'wishlist_add',
ecommerce: {
currency: 'USD',
value: 99.99,
items: [{
item_id: 'SKU-123',
item_name: 'Product Name',
item_category: 'Electronics',
price: 99.99,
quantity: 1
}]
}
});
function trackWishlistAdd(item) {
window.NextAnalytics.track({
event: 'wishlist_add',
ecommerce: {
currency: 'USD',
value: item.price,
items: [{
item_id: item.sku,
item_name: item.name,
item_category: item.category,
price: item.price,
quantity: 1
}]
}
});
}

Modify ALL events before they’re sent to providers using transform functions.

// Access the data layer manager
const dataLayer = window.NextDataLayerManager;
dataLayer.setTransformFunction((event) => {
// Add custom fields to every event
event.app_version = '1.0.0';
event.environment = 'production';
event.custom_user_id = getCurrentUserId();
// Filter out events
if (event.event === 'internal_test') {
return null; // Event won't be sent
}
// Modify specific events
if (event.event === 'dl_purchase') {
event.ecommerce.custom_order_type = getOrderType();
event.fulfillment_center = 'US-WEST';
}
return event;
});

You can also set a transform function globally in the window:

window.NextDataLayerTransformFn = function(event) {
event.custom_property = 'value';
return event;
};

This runs before the configured transform function.

window.NextDataLayerTransformFn = function(event) {
// Add app context to all events
event.app_version = window.APP_VERSION;
event.environment = window.ENV;
event.build_number = window.BUILD_NUM;
// Add user context
if (window.userSession) {
event.session_duration = Date.now() - window.userSession.startTime;
event.page_views = window.userSession.pageViews;
}
return event;
};

Track sequences of related events:

// Start checkout flow
window.NextAnalytics.track({
event: 'checkout_flow_started',
flow_id: generateFlowId(),
entry_point: 'cart_page'
});
// Track each step
window.NextAnalytics.track({
event: 'checkout_step_completed',
flow_id: currentFlowId,
step: 'shipping_info',
duration_ms: stepDuration
});
// Track completion
window.NextAnalytics.track({
event: 'checkout_flow_completed',
flow_id: currentFlowId,
total_duration_ms: totalDuration,
steps_completed: 4
});

Track events based on business logic:

function trackCartMilestone(cartValue) {
const milestones = [
{ threshold: 50, name: 'free_shipping_eligible' },
{ threshold: 100, name: 'discount_eligible' },
{ threshold: 200, name: 'premium_tier_reached' }
];
milestones.forEach(milestone => {
if (cartValue >= milestone.threshold && !hasMilestone(milestone.name)) {
window.NextAnalytics.track({
event: 'cart_milestone',
milestone: milestone.name,
cart_value: cartValue,
threshold: milestone.threshold
});
saveMilestone(milestone.name);
}
});
}

Track engagement duration:

class VideoTracker {
constructor(videoId) {
this.videoId = videoId;
this.startTime = Date.now();
this.milestones = [25, 50, 75, 100];
this.tracked = new Set();
}
trackProgress(percentComplete) {
this.milestones.forEach(milestone => {
if (percentComplete >= milestone && !this.tracked.has(milestone)) {
window.NextAnalytics.track({
event: 'video_progress',
video_id: this.videoId,
milestone: milestone,
duration_watched: Date.now() - this.startTime
});
this.tracked.add(milestone);
}
});
}
trackComplete() {
window.NextAnalytics.track({
event: 'video_completed',
video_id: this.videoId,
total_duration: Date.now() - this.startTime
});
}
}

Use snake_case for all custom events:

// Good
'newsletter_subscribe'
'video_completed'
'form_submitted'
'feature_enabled'
// Avoid
'subscribeNewsletter'
'VideoCompleted'
'form-submitted'
'FEATURE_ENABLED'

Provide event context:

// Good - With context
window.NextAnalytics.track({
event: 'video_completed',
video_id: 'intro-video',
video_title: 'Product Introduction',
video_duration: 120,
video_category: 'onboarding',
user_watched_percent: 100,
completion_time_seconds: 118
});
// Avoid - Minimal context
window.NextAnalytics.track({
event: 'video_completed'
});

For e-commerce events, use the GA4 standard format:

// Good - GA4 format
window.NextAnalytics.track({
event: 'wishlist_add',
ecommerce: {
currency: 'USD',
value: 99.99,
items: [{
item_id: 'SKU-123',
item_name: 'Product Name',
price: 99.99,
quantity: 1
}]
}
});
// Avoid - Non-standard format
window.NextAnalytics.track({
event: 'wishlist_add',
product: item // Wrong structure
});

Never let analytics errors break your app:

try {
window.NextAnalytics.track(customEvent);
} catch (error) {
console.error('Analytics tracking failed:', error);
// Don't throw - continue with app functionality
}

Enable debug mode to see detailed logs:

// In browser console
window.NextAnalytics.setDebugMode(true);
// Or via config
window.nextConfig = {
analytics: {
debug: true
}
};
function trackRecommendationClick(item, recommendationType) {
window.NextAnalytics.track({
event: 'recommendation_clicked',
item_id: item.id,
item_name: item.title,
recommendation_type: recommendationType, // 'similar', 'upsell', 'cross-sell'
recommendation_position: item.position,
algorithm: 'collaborative_filtering'
});
}
function trackExperiment(experimentId, variantId) {
window.NextAnalytics.track({
event: 'experiment_viewed',
experiment_id: experimentId,
variant_id: variantId,
user_id: getCurrentUserId()
});
}
function trackExperimentConversion(experimentId, variantId) {
window.NextAnalytics.track({
event: 'experiment_converted',
experiment_id: experimentId,
variant_id: variantId,
conversion_value: getCartValue()
});
}
function trackSearch(query, resultsCount) {
window.NextAnalytics.track({
event: 'search_performed',
search_query: query,
results_count: resultsCount,
search_filters: getActiveFilters(),
search_sort: getCurrentSort()
});
}
function trackSearchResultClick(query, item, position) {
window.NextAnalytics.track({
event: 'search_result_clicked',
search_query: query,
item_id: item.id,
item_position: position,
results_count: getTotalResults()
});
}

View all tracked events:

// View all events
console.log(window.NextDataLayer);
// Get event count
const count = window.NextDataLayerManager.getEventCount();
console.log(`Tracked ${count} events`);
// Get analytics status
const status = window.NextAnalytics.getStatus();
console.log(status);
// {
// initialized: true,
// debugMode: false,
// providers: ['GTM', 'Facebook'],
// eventsTracked: 15,
// ignored: false
// }