Skip to Content
DocsSDKsWebTracking Events

Tracking Events

Send custom and well-known events from your application. Events are the foundation of all analytics in Pug.

import { track } from '@poluruprvn/pug-web' track('button_clicked', { label: 'Sign up', page: '/pricing' })

track() never throws — failures are logged silently so analytics never break your app.

Well-known events

Use standard names for schema validation, TypeScript types, and built-in dashboard templates:

track('purchase', { revenue: 29.99, currency: 'USD', order_id: 'ord-456' }) track('signup', { method: 'google' }) track('product_viewed', { product_id: 'sku-123', price: 29.99 }) track('add_to_cart', { product_id: 'sku-123', quantity: 1 }) track('search', { query: 'running shoes' })

See the full list in Well-known events.

Property types matter

Insights aggregations depend on correct types:

// Revenue must be a number for Sum aggregation track('purchase', { revenue: 29.99, currency: 'USD' }) // ✓ track('purchase', { revenue: '29.99', currency: 'USD' }) // ✗ string — Sum won't work // Booleans for filters track('feature_used', { is_premium: true }) // ✓ track('feature_used', { is_premium: 'true' }) // ✗ string

Verify types in the dashboard Events schema explorer after deploying.

Custom events

Any string event name works without schema validation:

track('experiment_exposed', { variant: 'B', experiment_id: 'exp-001' }) track('onboarding_step_completed', { step: 3, step_name: 'invite_team' })

Custom events appear in Insights, Profiles, and the Events catalog identically to well-known events.

Naming conventions

Consistent naming makes analytics reliable across teams and platforms:

ConventionExampleAvoid
snake_casesignup_completedsignupCompleted, Signup Completed
Past tense for actionspurchase, form_submittedpurchasing, submit_form
Noun for viewsproduct_viewed, page_viewview_product
Same names across platformspurchase on web and mobileDifferent names per platform

Document your event catalog in Events and share with the team before implementation.

Immediate flush

Bypass the batch queue for critical events that must arrive before the user navigates away:

track('purchase', { revenue: 99, currency: 'USD', order_id: 'ord-789' }, { immediate: true })

Use immediate: true for:

  • Purchase confirmations
  • Signup completions
  • Any conversion event where the next page is a redirect or external URL

Custom timestamps

Override event time for offline replay or delayed sending:

track('offline_action', { action: 'sync' }, { timestamp: new Date('2026-01-15T10:30:00Z') })

Timestamps in the future are rejected. Timestamps older than your retention window may not appear in queries.

Auto-properties

The SDK attaches context automatically on every event:

PropertyDescription
$urlCurrent page URL
$referrerDocument referrer
$screenScreen dimensions
$utm_source/medium/campaign/term/contentUTM parameters
$sdkVersionSDK version string

Server workers add $geo.*, $browser, $os, $device, $botScore after ingestion. See Auto-properties.

You don’t need to send these manually — they’ll be duplicated if you do.

Server-side tracking

For events that should never be client-visible (refunds, admin actions), send from your backend via the Events API:

// Server-side — use shared or SDK key depending on your setup await eventsClient.batchCreate({ events: [{ name: 'refund_processed', properties: { order_id: 'ord-789', amount: 29.99 }, timestamp: new Date() }] })

Verify events

After adding tracking:

  1. Trigger the event in your app
  2. Check Live — event appears in activity feed within 15 seconds
  3. Check Events — event name and properties in schema explorer
  4. Build an Insight query to confirm aggregation works

Common issues

SymptomFix
Event not in dashboardConfirm init ran; check project ID; remove dryRun
Property missing in schemaProperty not included in SDK call — check call site
Revenue Sum shows 0Pass revenue as number, not string
Duplicate eventsCheck for double init or both auto-track and manual track

Further reading

Last updated on