npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@walkeros/web-destination-snowplow

v3.2.0

Published

Snowplow web destination for walkerOS

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-snowplow

Quick 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, calls trackPageView() 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:

  1. Initializes the tracker - Calls newTracker() with your configuration
  2. Maps events - Transforms walkerOS events into Snowplow format
  3. 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 loadScript or scriptUrl settings 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=999

Self-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 schema

Page 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 called

Consent 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-0

Schema 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 names

Custom 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:

  1. Tracker (this destination) → Sends events
  2. Collector → Receives and validates events
  3. Enrich → Enriches events with additional data
  4. 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

  1. Check Collector URL: Verify your collector URL is correct

    settings: {
      collectorUrl: 'https://collector.example.com', // Should not include /i or /com.snowplowanalytics.snowplow/tp2
    }
  2. Check Browser Console: Look for Snowplow errors

    • Open DevTools → Console
    • Look for [Snowplow] prefixed messages
  3. Verify Network Requests: Check Network tab in DevTools

    • Look for requests to your collector URL
    • Check request payload
  4. Test with Simple Event:

    await elb('page view', { title: 'Test' });

Initialization Errors

If you see [Snowplow] Collector URL is required:

  • Ensure collectorUrl is 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

  1. Start Snowplow Micro:

    docker run -p 9090:9090 snowplow/snowplow-micro:3.0.1
  2. Configure walkerOS to use Micro:

    const { elb } = await startFlow({
      destinations: {
        snowplow: {
          destination: destinationSnowplow,
          config: {
            settings: {
              collectorUrl: 'localhost:9090', // Point to Micro
              appId: 'test-app',
            },
          },
        },
      },
    });
  3. Send test events:

    await elb('page view', { title: 'Test Page' });
    await elb('product view', { id: 'P123', price: 999 });
  4. 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/reset

Example 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.tsv

Export events to JSON:

docker run -p 9090:9090 snowplow/snowplow-micro:3.0.1 --output-json > events.json

Use 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