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' }) // ✗ stringVerify 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:
| Convention | Example | Avoid |
|---|---|---|
snake_case | signup_completed | signupCompleted, Signup Completed |
| Past tense for actions | purchase, form_submitted | purchasing, submit_form |
| Noun for views | product_viewed, page_view | view_product |
| Same names across platforms | purchase on web and mobile | Different 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:
| Property | Description |
|---|---|
$url | Current page URL |
$referrer | Document referrer |
$screen | Screen dimensions |
$utm_source/medium/campaign/term/content | UTM parameters |
$sdkVersion | SDK 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:
- Trigger the event in your app
- Check Live — event appears in activity feed within 15 seconds
- Check Events — event name and properties in schema explorer
- Build an Insight query to confirm aggregation works
Common issues
| Symptom | Fix |
|---|---|
| Event not in dashboard | Confirm init ran; check project ID; remove dryRun |
| Property missing in schema | Property not included in SDK call — check call site |
| Revenue Sum shows 0 | Pass revenue as number, not string |
| Duplicate events | Check for double init or both auto-track and manual track |