@walkeros/web-destination-snowplow
v3.2.0
Published
Snowplow web destination for walkerOS
Maintainers
Readme
@walkeros/web-destination-snowplow
Snowplow Analytics destination for walkerOS - send events to your Snowplow collector for powerful, privacy-first analytics.
Installation
npm install @walkeros/collector @walkeros/web-destination-snowplowQuick Start
import { startFlow } from '@walkeros/collector';
import { destinationSnowplow } from '@walkeros/web-destination-snowplow';
const { elb } = await startFlow({
destinations: {
snowplow: {
destination: destinationSnowplow,
config: {
settings: {
collectorUrl: 'https://collector.yourdomain.com',
appId: 'my-web-app',
},
},
},
},
});
// Track events
await elb('page view', { title: 'Home' });
await elb('product view', { id: 'P123', name: 'Laptop', price: 999 });Configuration
Settings
Required Settings
- collectorUrl (string): Your Snowplow collector endpoint URL
Optional Settings
- appId (string): Application identifier. Default:
'walkerOS' - trackerName (string): Tracker instance name. Default:
'sp' - platform (string): Platform identifier. Default:
'web' - scriptUrl (string): Custom URL for the Snowplow tracker script. Used when
loadScript: true. Always pin to a specific version in production. - eventMethod ('struct' | 'self'): Event tracking method. Default:
'struct''struct': Use structured events (category/action/label/property/value)'self': Use self-describing events (schema-based)
- schema (string): Iglu schema URI for self-describing events
- pageViewTracking (boolean): Enable automatic page view tracking. Default:
false - trackPageView (boolean): Track page view on tracker initialization. When
true, callstrackPageView()immediately after init. Default:false - pageViewEvent (string): Event name that triggers
trackPageView(). When a walkerOS event matches this name, Snowplow's native page view tracking is used. Must be explicitly set (no default). - userId (string | Mapping.Value): User ID for cross-session user stitching.
Called once via
setUserId()on the first event where the value resolves. - anonymousTracking (boolean | object): Enable anonymous tracking mode.
true: Basic anonymous tracking (no user identifiers){ withServerAnonymisation: true }: Also anonymize IP on server{ withSessionTracking: true }: Keep session tracking in anonymous mode
- code (TrackerFactory): Tracker factory function for bundled mode. When provided, bypasses sp.js script loading and uses the factory directly.
Event Mapping
Transform walkerOS events into Snowplow structured events:
config: {
settings: {
collectorUrl: 'https://collector.example.com',
appId: 'my-app',
},
mapping: {
product: {
view: {
data: {
map: {
category: { value: 'product' },
action: { value: 'view' },
property: 'data.name',
value: 'data.price',
},
},
},
},
order: {
complete: {
data: {
map: {
category: { value: 'ecommerce' },
action: { value: 'purchase' },
property: 'data.id',
value: 'data.total',
},
},
},
},
},
}How It Works
This destination integrates with the Snowplow JavaScript Tracker using the
window.snowplow() queue function, similar to how Google Analytics uses
gtag() or Meta Pixel uses fbq().
Tracker Interface
The Snowplow tracker exposes a global queue function:
// Initialization
window.snowplow('newTracker', 'sp', 'https://collector.example.com', {
appId: 'my-app',
platform: 'web',
});
// Tracking events
window.snowplow('trackPageView');
window.snowplow(
'trackStructEvent',
'category',
'action',
'label',
'property',
value,
);
window.snowplow('trackSelfDescribingEvent', {
event: {
schema: 'iglu:vendor/name/jsonschema/1-0-0',
data: {
/* event data */
},
},
});Integration with walkerOS
This destination automatically:
- Initializes the tracker - Calls
newTracker()with your configuration - Maps events - Transforms walkerOS events into Snowplow format
- Sends events - Calls the appropriate Snowplow tracking method
You don't need to interact with window.snowplow() directly - just use the
walkerOS elb() function.
Script Loading
The Snowplow tracker script can be loaded automatically:
config: {
settings: {
collectorUrl: 'https://collector.example.com',
},
loadScript: true, // Automatically loads Snowplow tracker from CDN
}Or manually:
<script src="https://cdn.jsdelivr.net/npm/@snowplow/javascript-tracker@latest/dist/sp.js"></script>Custom Script URL
By default, the tracker loads from the jsdelivr CDN with @latest. For
production, always pin to a specific version using the scriptUrl setting:
config: {
settings: {
collectorUrl: 'https://collector.example.com',
scriptUrl: 'https://cdn.jsdelivr.net/npm/@snowplow/[email protected]/dist/sp.js',
},
loadScript: true,
}Security Warning: Using @latest in production is not recommended as it can
introduce breaking changes or security vulnerabilities without notice. Always
pin to a specific version.
NPM Packages Mode (Browser-Tracker)
Instead of loading sp.js via script tag, you can use @snowplow/browser-tracker
npm packages directly. This provides smaller bundles, tree-shaking, and type
safety.
Available tracker functions:
| Function | Package | Required |
| -------------------------- | --------------------------------------------- | -------- |
| newTracker | @snowplow/browser-tracker | ✅ Yes |
| trackSelfDescribingEvent | @snowplow/browser-tracker | ✅ Yes |
| trackPageView | @snowplow/browser-tracker | Optional |
| trackStructEvent | @snowplow/browser-tracker | Optional |
| setUserId | @snowplow/browser-tracker | Optional |
| enableActivityTracking | @snowplow/browser-tracker | Optional |
| addPlugin | @snowplow/browser-tracker | Optional |
| addGlobalContexts | @snowplow/browser-tracker | Optional |
| clearUserData | @snowplow/browser-tracker | Optional |
| enableAnonymousTracking | @snowplow/browser-tracker | Optional |
| disableAnonymousTracking | @snowplow/browser-tracker | Optional |
| setPageType | @snowplow/browser-plugin-snowplow-ecommerce | Optional |
| trackConsentAllow | @snowplow/browser-plugin-enhanced-consent | Optional |
| trackConsentDeny | @snowplow/browser-plugin-enhanced-consent | Optional |
| trackConsentSelected | @snowplow/browser-plugin-enhanced-consent | Optional |
flow.json configuration:
{
"packages": {
"@snowplow/browser-tracker": {
"imports": [
"newTracker",
"trackSelfDescribingEvent",
"trackPageView",
"enableActivityTracking",
"addPlugin",
"addGlobalContexts"
]
},
"@snowplow/browser-plugin-snowplow-ecommerce": {
"imports": ["SnowplowEcommercePlugin", "setPageType"]
},
"@snowplow/browser-plugin-link-click-tracking": {
"imports": ["LinkClickTrackingPlugin"]
}
},
"destinations": {
"snowplow": {
"package": "@walkeros/web-destination-snowplow",
"config": {
"settings": {
"tracker": {
"newTracker": "$code:newTracker",
"trackSelfDescribingEvent": "$code:trackSelfDescribingEvent",
"trackPageView": "$code:trackPageView",
"enableActivityTracking": "$code:enableActivityTracking",
"addPlugin": "$code:addPlugin",
"addGlobalContexts": "$code:addGlobalContexts",
"setPageType": "$code:setPageType"
},
"collectorUrl": "https://collector.example.com",
"appId": "my-app",
"plugins": [
{ "code": "$code:SnowplowEcommercePlugin" },
{
"code": "$code:LinkClickTrackingPlugin",
"config": { "trackContent": true }
}
]
}
}
}
}
}When settings.tracker is provided:
- Tracker functions are called directly (no sp.js script loaded)
- No
loadScriptorscriptUrlsettings needed - Smaller bundle size (only imports what you use)
- Full TypeScript support
Code-Based Plugins
Plugins can be loaded via imports instead of URLs:
{
"packages": {
"@snowplow/browser-tracker": {
"imports": ["newTracker", "trackSelfDescribingEvent", "addPlugin"]
},
"@snowplow/browser-plugin-link-click-tracking": {
"imports": ["LinkClickTrackingPlugin"]
},
"@snowplow/browser-plugin-button-click-tracking": {
"imports": ["ButtonClickTrackingPlugin"]
}
},
"destinations": {
"snowplow": {
"config": {
"settings": {
"tracker": {
"newTracker": "$code:newTracker",
"trackSelfDescribingEvent": "$code:trackSelfDescribingEvent",
"addPlugin": "$code:addPlugin"
},
"collectorUrl": "https://collector.example.com",
"plugins": [
{
"code": "$code:LinkClickTrackingPlugin",
"config": { "trackContent": true }
},
{
"code": "$code:ButtonClickTrackingPlugin",
"config": { "filter": { "allowlist": ["tracked"] } }
}
]
}
}
}
}
}Plugin configuration:
code: The plugin factory (via$code:prefix)config: Options passed to the plugin factory
Examples
Structured Events (Default)
// Configure for structured events
config: {
settings: {
collectorUrl: 'https://collector.example.com',
eventMethod: 'struct', // Default
},
}
// Track events
await elb('product view', { id: 'P123', name: 'Laptop', price: 999 });
// Sends: category='product', action='view', property='Laptop', value=999Self-Describing Events
// Configure for self-describing events
config: {
settings: {
collectorUrl: 'https://collector.example.com',
eventMethod: 'self',
schema: 'iglu:com.mycompany/product_view/jsonschema/1-0-0',
},
}
// Track events
await elb('product view', { id: 'P123', price: 999 });
// Sends self-describing event with your schemaPage View Tracking
Option 1: Auto-track on init
config: {
settings: {
collectorUrl: 'https://collector.example.com',
trackPageView: true, // Calls trackPageView() immediately after init
},
}Option 2: Track via walkerOS event
config: {
settings: {
collectorUrl: 'https://collector.example.com',
pageViewEvent: 'page view', // Explicit - triggers trackPageView()
},
}
// Then in your app:
await elb('page view', { title: document.title });Option 3: Custom event name
config: {
settings: {
collectorUrl: 'https://collector.example.com',
pageViewEvent: 'screen view', // Custom event name for SPAs/mobile
},
}
await elb('screen view', { screenName: 'Home' });E-commerce Tracking
// Product view
await elb('product view', {
id: 'P123',
name: 'Laptop',
price: 999,
category: 'Electronics',
});
// Add to cart
await elb('product add', {
id: 'P123',
quantity: 1,
});
// Purchase
await elb('order complete', {
id: 'ORDER123',
total: 1999,
currency: 'USD',
items: 2,
});Media Tracking
Track video and audio playback events using Snowplow's media tracking schemas:
import {
MEDIA_SCHEMAS,
MEDIA_ACTIONS,
} from '@walkeros/web-destination-snowplow';
// Track video play
await elb('video play', {
id: 'video-123',
title: 'Product Demo',
currentTime: 0,
duration: 120,
});
// Track video progress (25%, 50%, 75% milestones)
await elb('video progress', {
id: 'video-123',
percentProgress: 25,
});
// Track video pause
await elb('video pause', {
id: 'video-123',
currentTime: 45,
});
// Track video complete
await elb('video end', {
id: 'video-123',
duration: 120,
});Media Mapping Configuration
Configure media event mappings with the MEDIA_SCHEMAS constants:
import { MEDIA_SCHEMAS } from '@walkeros/web-destination-snowplow';
config: {
mapping: {
video: {
play: {
settings: {
schema: MEDIA_SCHEMAS.PLAY,
},
data: {
map: {
label: 'data.title',
},
},
},
pause: {
settings: {
schema: MEDIA_SCHEMAS.PAUSE,
},
data: {
map: {
currentTime: 'data.currentTime',
},
},
},
progress: {
settings: {
schema: MEDIA_SCHEMAS.PERCENT_PROGRESS,
},
data: {
map: {
percentProgress: 'data.percentProgress',
},
},
},
end: {
settings: {
schema: MEDIA_SCHEMAS.END,
},
},
},
},
}Ad Tracking
Track video advertisements with pre-roll, mid-roll, and post-roll ad events:
import { MEDIA_SCHEMAS } from '@walkeros/web-destination-snowplow';
config: {
mapping: {
ad: {
break_start: {
settings: {
schema: MEDIA_SCHEMAS.AD_BREAK_START,
},
data: {
map: {
breakId: 'data.breakId',
breakType: 'data.breakType', // 'preroll', 'midroll', 'postroll'
},
},
},
start: {
settings: {
schema: MEDIA_SCHEMAS.AD_START,
},
data: {
map: {
adId: 'data.adId',
name: 'data.name',
duration: 'data.duration',
},
},
},
complete: {
settings: {
schema: MEDIA_SCHEMAS.AD_COMPLETE,
},
data: {
map: {
adId: 'data.adId',
},
},
},
skip: {
settings: {
schema: MEDIA_SCHEMAS.AD_SKIP,
},
data: {
map: {
adId: 'data.adId',
percentProgress: 'data.percentProgress',
},
},
},
},
},
}Media Player Context
Attach media player state as context to your events:
config: {
mapping: {
video: {
play: {
settings: {
schema: MEDIA_SCHEMAS.PLAY,
context: [
{
schema: MEDIA_SCHEMAS.MEDIA_PLAYER,
data: {
map: {
currentTime: 'data.currentTime',
duration: 'data.duration',
muted: 'data.muted',
volume: 'data.volume',
playbackRate: 'data.playbackRate',
paused: { value: false },
},
},
},
],
},
},
},
},
}Available Media Schemas
| Schema | Description | Use Case |
| ------------------- | -------------------------- | --------------------- |
| PLAY | Playback started | Video/audio play |
| PAUSE | Playback paused | User pauses content |
| END | Playback ended | Video/audio completed |
| SEEK_START | User started seeking | Scrubbing timeline |
| SEEK_END | User ended seeking | Scrubbing complete |
| BUFFER_START | Buffering started | Loading content |
| BUFFER_END | Buffering ended | Content ready |
| QUALITY_CHANGE | Video quality changed | Adaptive streaming |
| FULLSCREEN_CHANGE | Fullscreen mode toggled | User interaction |
| VOLUME_CHANGE | Volume level changed | User adjustment |
| PERCENT_PROGRESS | Progress milestone reached | 25%, 50%, 75% markers |
| ERROR | Playback error occurred | Error tracking |
| AD_BREAK_START | Ad break started | Pre/mid/post-roll |
| AD_START | Individual ad started | Ad impression |
| AD_COMPLETE | Individual ad completed | Ad view completion |
| AD_SKIP | User skipped ad | Ad engagement |
Snowplow Event Types
Structured Events
Structured events follow Snowplow's category/action/label/property/value
pattern. Use the struct mapping property to send structured events:
mapping: {
button: {
click: {
settings: {
struct: {
category: { value: 'ui' },
action: { value: 'click' },
label: 'data.button_name',
property: 'data.section',
value: 'data.position',
},
},
},
},
}When struct is configured, the destination calls trackStructEvent directly,
bypassing self-describing events entirely. This is ideal for:
- Simple interactions that don't need schema validation
- Lightweight event tracking
- Google Analytics-style category/action tracking
Available fields:
- category (required): Event category (e.g., 'ui', 'video', 'cta')
- action (required): Action performed (e.g., 'click', 'play', 'submit')
- label (optional): Additional context string
- property (optional): Property name string
- value (optional): Numeric value (automatically converted from string)
Self-Describing Events
Self-describing events use Iglu schemas for structured data:
{
schema: 'iglu:com.example/event/jsonschema/1-0-0',
data: {
// Your event data
}
}Built-in Contexts
Enable automatic context entities to enrich your events:
config: {
settings: {
collectorUrl: 'https://collector.example.com',
contexts: {
webPage: true, // Page view ID (links events to page views)
session: true, // Session tracking (client_session schema)
browser: true, // Browser info (viewport, language, device)
geolocation: true // User location (requires permission)
},
},
}| Context | Schema | Description |
| ------------- | --------------------------- | ------------------------------- |
| webPage | web_page/1-0-0 | Unique page view ID |
| session | client_session/1-0-2 | Session ID, index, timestamps |
| browser | browser_context/2-0-0 | Viewport, language, device info |
| geolocation | geolocation_context/1-1-0 | Latitude, longitude |
User Identity & Privacy
Cross-Session User Stitching
Use userId to link events across sessions when users log in:
config: {
settings: {
collectorUrl: 'https://collector.example.com',
userId: 'user.id', // From walkerOS user object
},
}
// Anonymous browsing - events tracked without user_id
await elb('page view');
// User logs in - set walkerOS user
elb('walker user', { id: 'user-abc123' });
// Next event triggers setUserId, all subsequent events include user_id
await elb('product view', { id: 'P123' });The userId setting supports walkerOS mapping syntax:
'user.id'- From walkerOS user object (recommended)'globals.user_id'- From globals{ value: 'static-id' }- Static value (rare)
Anonymous Tracking
Enable anonymous tracking for privacy-focused collection or before consent:
config: {
settings: {
collectorUrl: 'https://collector.example.com',
anonymousTracking: true, // Basic anonymous tracking
},
}
// Or with fine-grained control:
config: {
settings: {
collectorUrl: 'https://collector.example.com',
anonymousTracking: {
withServerAnonymisation: true, // Anonymize IP on server
withSessionTracking: true, // Keep session context
},
},
}Runtime Privacy Controls
Control tracking modes at runtime using exported utility functions:
import {
clearUserData,
enableAnonymousTracking,
disableAnonymousTracking,
} from '@walkeros/web-destination-snowplow';
// User withdraws consent - clear all identifiers
clearUserData();
// Switch to anonymous mode mid-session
enableAnonymousTracking({ withServerAnonymisation: true });
// User grants consent - resume normal tracking
disableAnonymousTracking();Consent Tracking
Track GDPR/CCPA consent events using Snowplow's Enhanced Consent plugin. The destination automatically reacts to walkerOS consent events and sends the appropriate consent tracking calls.
Prerequisites
Load the Enhanced Consent plugin:
import { EnhancedConsentPlugin } from '@snowplow/browser-plugin-enhanced-consent';
config: {
settings: {
collectorUrl: 'https://collector.example.com',
plugins: [EnhancedConsentPlugin()],
consent: {
required: ['analytics', 'marketing'],
basisForProcessing: 'consent',
consentUrl: 'https://example.com/privacy',
consentVersion: '2.0',
},
},
}Configuration
| Option | Type | Description |
| -------------------- | ---------- | ------------------------------------ |
| required | string[] | walkerOS consent groups to check |
| basisForProcessing | string | GDPR basis (consent, contract, etc.) |
| consentUrl | string | Privacy policy URL |
| consentVersion | string | Policy version |
| domainsApplied | string[] | Domains where consent applies |
| gdprApplies | boolean | Whether GDPR applies |
How It Works
The destination listens for walkerOS consent events and maps them to Snowplow:
| walkerOS Consent State | Snowplow Method |
| --------------------------- | ---------------------- |
| All required scopes granted | trackConsentAllow |
| All required scopes denied | trackConsentDeny |
| Partial consent (mixed) | trackConsentSelected |
Example
// Configure consent tracking
const { elb } = await startFlow({
destinations: {
snowplow: {
code: destinationSnowplow,
config: {
settings: {
collectorUrl: 'https://collector.example.com',
consent: {
required: ['analytics', 'marketing'],
basisForProcessing: 'consent',
consentUrl: 'https://example.com/privacy',
consentVersion: '2.0',
domainsApplied: ['example.com'],
gdprApplies: true,
},
},
},
},
},
});
// User accepts all
await elb('walker consent', { analytics: true, marketing: true });
// → trackConsentAllow called
// User accepts some
await elb('walker consent', { analytics: true, marketing: false });
// → trackConsentSelected called
// User rejects all
await elb('walker consent', { analytics: false, marketing: false });
// → trackConsentDeny calledConsent Schema Constants
import { CONSENT_SCHEMAS } from '@walkeros/web-destination-snowplow';
CONSENT_SCHEMAS.PREFERENCES; // consent_preferences/1-0-0
CONSENT_SCHEMAS.CMP_VISIBLE; // cmp_visible/1-0-0
CONSENT_SCHEMAS.DOCUMENT; // consent_document/1-0-0
CONSENT_SCHEMAS.GDPR; // gdpr/1-0-0Schema Constants
The package exports pre-defined Snowplow schema URIs for convenience:
import {
SCHEMAS,
ACTIONS,
WEB_SCHEMAS,
CONSENT_SCHEMAS,
MEDIA_SCHEMAS,
MEDIA_ACTIONS,
} from '@walkeros/web-destination-snowplow';
// Ecommerce schemas
SCHEMAS.PRODUCT; // 'iglu:com.snowplowanalytics.snowplow.ecommerce/product/jsonschema/1-0-0'
SCHEMAS.TRANSACTION; // 'iglu:com.snowplowanalytics.snowplow.ecommerce/transaction/jsonschema/1-0-0'
// Ecommerce actions
ACTIONS.ADD_TO_CART; // 'add_to_cart'
ACTIONS.TRANSACTION; // 'transaction'
// Web event schemas
WEB_SCHEMAS.LINK_CLICK; // 'iglu:com.snowplowanalytics.snowplow/link_click/jsonschema/1-0-1'
WEB_SCHEMAS.SUBMIT_FORM; // 'iglu:com.snowplowanalytics.snowplow/submit_form/jsonschema/1-0-0'
WEB_SCHEMAS.SITE_SEARCH; // 'iglu:com.snowplowanalytics.snowplow/site_search/jsonschema/1-0-0'
WEB_SCHEMAS.TIMING; // 'iglu:com.snowplowanalytics.snowplow/timing/jsonschema/1-0-0'
WEB_SCHEMAS.WEB_VITALS; // 'iglu:com.snowplowanalytics.snowplow/web_vitals/jsonschema/1-0-0'
// Web context schemas
WEB_SCHEMAS.WEB_PAGE; // 'iglu:com.snowplowanalytics.snowplow/web_page/jsonschema/1-0-0'
WEB_SCHEMAS.BROWSER; // 'iglu:com.snowplowanalytics.snowplow/browser_context/jsonschema/2-0-0'
WEB_SCHEMAS.CLIENT_SESSION; // 'iglu:com.snowplowanalytics.snowplow/client_session/jsonschema/1-0-2'
// Media event schemas
MEDIA_SCHEMAS.PLAY; // 'iglu:com.snowplowanalytics.snowplow.media/play_event/jsonschema/1-0-0'
MEDIA_SCHEMAS.PAUSE; // 'iglu:com.snowplowanalytics.snowplow.media/pause_event/jsonschema/1-0-0'
MEDIA_SCHEMAS.END; // 'iglu:com.snowplowanalytics.snowplow.media/end_event/jsonschema/1-0-0'
MEDIA_SCHEMAS.SEEK_START; // 'iglu:com.snowplowanalytics.snowplow.media/seek_start_event/jsonschema/1-0-0'
MEDIA_SCHEMAS.BUFFER_START; // 'iglu:com.snowplowanalytics.snowplow.media/buffer_start_event/jsonschema/1-0-0'
MEDIA_SCHEMAS.QUALITY_CHANGE; // 'iglu:com.snowplowanalytics.snowplow.media/quality_change_event/jsonschema/1-0-0'
MEDIA_SCHEMAS.FULLSCREEN_CHANGE; // 'iglu:com.snowplowanalytics.snowplow.media/fullscreen_change_event/jsonschema/1-0-0'
MEDIA_SCHEMAS.PERCENT_PROGRESS; // 'iglu:com.snowplowanalytics.snowplow.media/percent_progress_event/jsonschema/1-0-0'
MEDIA_SCHEMAS.ERROR; // 'iglu:com.snowplowanalytics.snowplow.media/error_event/jsonschema/1-0-0'
// Media ad schemas
MEDIA_SCHEMAS.AD_BREAK_START; // 'iglu:com.snowplowanalytics.snowplow.media/ad_break_start_event/jsonschema/1-0-0'
MEDIA_SCHEMAS.AD_START; // 'iglu:com.snowplowanalytics.snowplow.media/ad_start_event/jsonschema/1-0-0'
MEDIA_SCHEMAS.AD_COMPLETE; // 'iglu:com.snowplowanalytics.snowplow.media/ad_complete_event/jsonschema/1-0-0'
MEDIA_SCHEMAS.AD_SKIP; // 'iglu:com.snowplowanalytics.snowplow.media/ad_skip_event/jsonschema/1-0-0'
// Media context schemas
MEDIA_SCHEMAS.MEDIA_PLAYER; // 'iglu:com.snowplowanalytics.snowplow/media_player/jsonschema/1-0-0'
MEDIA_SCHEMAS.SESSION; // 'iglu:com.snowplowanalytics.snowplow.media/session/jsonschema/1-0-0'
MEDIA_SCHEMAS.AD; // 'iglu:com.snowplowanalytics.snowplow.media/ad/jsonschema/1-0-0'
MEDIA_SCHEMAS.AD_BREAK; // 'iglu:com.snowplowanalytics.snowplow.media/ad_break/jsonschema/1-0-0'
// Media action types (for use with mapping.name)
MEDIA_ACTIONS.PLAY; // 'play'
MEDIA_ACTIONS.PAUSE; // 'pause'
MEDIA_ACTIONS.AD_START; // 'ad_start'Use these constants in your mapping configuration to ensure correct schema URIs.
Advanced Usage
Multiple Trackers
config: {
settings: {
collectorUrl: 'https://collector.example.com',
trackerName: 'mainTracker',
},
}
// Can run multiple instances with different tracker namesCustom Mapping with Functions
config: {
mapping: {
product: {
view: {
data: {
map: {
category: { value: 'product' },
action: { value: 'view' },
property: 'data.name',
value: {
fn: (event) => event.data.price * 1.2, // Add tax
},
},
},
},
},
},
}Integration with Snowplow Pipeline
This destination works with any standard Snowplow pipeline:
- Tracker (this destination) → Sends events
- Collector → Receives and validates events
- Enrich → Enriches events with additional data
- Storage → Loads into your data warehouse (Redshift, BigQuery, Snowflake, etc.)
Make sure your collectorUrl points to your Snowplow collector endpoint.
Troubleshooting
Events not appearing in Snowplow
Check Collector URL: Verify your collector URL is correct
settings: { collectorUrl: 'https://collector.example.com', // Should not include /i or /com.snowplowanalytics.snowplow/tp2 }Check Browser Console: Look for Snowplow errors
- Open DevTools → Console
- Look for
[Snowplow]prefixed messages
Verify Network Requests: Check Network tab in DevTools
- Look for requests to your collector URL
- Check request payload
Test with Simple Event:
await elb('page view', { title: 'Test' });
Initialization Errors
If you see [Snowplow] Collector URL is required:
- Ensure
collectorUrlis provided in settings - Check for typos in configuration
Schema Validation Errors
For self-describing events, ensure:
- Schema URI is correctly formatted:
iglu:vendor/name/format/version - Schema exists in your Iglu registry
- Event data matches the schema definition
Local Testing with Docker
You can test your walkerOS Snowplow integration locally using Snowplow Micro, a lightweight Docker-based collector that validates and enriches events just like a real Snowplow pipeline.
Quick Start with Snowplow Micro
Start Snowplow Micro:
docker run -p 9090:9090 snowplow/snowplow-micro:3.0.1Configure walkerOS to use Micro:
const { elb } = await startFlow({ destinations: { snowplow: { destination: destinationSnowplow, config: { settings: { collectorUrl: 'localhost:9090', // Point to Micro appId: 'test-app', }, }, }, }, });Send test events:
await elb('page view', { title: 'Test Page' }); await elb('product view', { id: 'P123', price: 999 });Inspect events:
- Web UI: Open http://localhost:9090/micro/ui in your browser
- API: Query events via REST endpoints
Snowplow Micro API Endpoints
Snowplow Micro provides several endpoints for inspecting tracked events:
# Get all events (good + bad)
curl http://localhost:9090/micro/all
# Get successfully validated events
curl http://localhost:9090/micro/good
# Get events that failed validation
curl http://localhost:9090/micro/bad
# Reset the event cache
curl -X POST http://localhost:9090/micro/resetExample Response
When you query /micro/good, you'll see events in this format:
[
{
"event": "unstruct",
"event_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"app_id": "test-app",
"platform": "web",
"unstruct_event": {
"schema": "iglu:com.snowplowanalytics.snowplow/unstruct_event/jsonschema/1-0-0",
"data": {
"schema": "iglu:com.example/product_view/jsonschema/1-0-0",
"data": {
"id": "P123",
"price": 999
}
}
}
}
]Advanced Docker Usage
Export events to TSV:
docker run -p 9090:9090 snowplow/snowplow-micro:3.0.1 --output-tsv > events.tsvExport events to JSON:
docker run -p 9090:9090 snowplow/snowplow-micro:3.0.1 --output-json > events.jsonUse custom port:
docker run -p 5000:9090 snowplow/snowplow-micro:3.0.1
# Then set collectorUrl to 'localhost:5000'Automated Testing
Integrate Snowplow Micro into your test suite:
// test/snowplow.test.ts
import { startFlow } from '@walkeros/collector';
import { destinationSnowplow } from '@walkeros/web-destination-snowplow';
describe('Snowplow Integration', () => {
let elb;
beforeAll(async () => {
({ elb } = await startFlow({
destinations: {
snowplow: {
destination: destinationSnowplow,
config: {
settings: {
collectorUrl: 'localhost:9090',
appId: 'test-app',
},
},
},
},
}));
});
afterEach(async () => {
// Reset Micro between tests
await fetch('http://localhost:9090/micro/reset', { method: 'POST' });
});
test('tracks page view events', async () => {
await elb('page view', { title: 'Home' });
// Wait a bit for event to be processed
await new Promise((resolve) => setTimeout(resolve, 100));
const response = await fetch('http://localhost:9090/micro/good');
const events = await response.json();
expect(events).toHaveLength(1);
expect(events[0].event).toBe('page_view');
});
test('tracks product events', async () => {
await elb('product view', { id: 'P123', price: 999 });
await new Promise((resolve) => setTimeout(resolve, 100));
const response = await fetch('http://localhost:9090/micro/good');
const events = await response.json();
expect(events).toHaveLength(1);
expect(events[0].app_id).toBe('test-app');
});
});Integration with E2E Testing
Use Snowplow Micro with Cypress, Playwright, or other E2E frameworks:
// cypress/e2e/tracking.cy.js
describe('Snowplow Tracking', () => {
beforeEach(() => {
// Reset Micro before each test
cy.request('POST', 'http://localhost:9090/micro/reset');
});
it('tracks user journey', () => {
cy.visit('/');
cy.get('[data-elbaction="click"]').click();
// Verify events in Micro
cy.request('http://localhost:9090/micro/good').then((response) => {
expect(response.body).to.have.length.greaterThan(0);
});
});
});Benefits of Testing with Micro
- ✅ No Cloud Setup: Test locally without Snowplow cloud infrastructure
- ✅ Fast Feedback: Instant validation of tracking implementation
- ✅ Event Inspection: See exactly what data is being sent
- ✅ Schema Validation: Catch schema errors before production
- ✅ CI/CD Integration: Run in Docker containers in your pipeline
- ✅ No Data Costs: Test without sending data to production
Resources
Resources
License
MIT
