Events
Define and track type-safe analytics events
Events are the core of analytics tracking. They represent actions that happen in your application—like button clicks, purchases, signups, or any user interaction you want to measure.
Defining Events
Events in @stacksee/analytics are defined with full TypeScript support for autocomplete and type safety:
import type { CreateEventDefinition, EventCollection } from '@stacksee/analytics';
export const appEvents = {
userSignedUp: {
name: 'user_signed_up', // Event name sent to providers
category: 'user', // Optional category for organization
properties: {} as { // Fully typed properties
email: string;
plan: 'free' | 'pro' | 'enterprise';
referralSource?: string; // Optional properties with ?
}
},
buttonClicked: {
name: 'button_clicked',
category: 'engagement',
properties: {} as {
buttonId: string;
location: string;
}
}
} as const satisfies EventCollection<Record<string, CreateEventDefinition<string>>>;Why Use as const satisfies?
The as const satisfies pattern gives you:
- Type inference - TypeScript knows exact event names and property types
- Autocomplete - Your IDE suggests valid events and properties
- Type safety - Compile errors if you use wrong event names or properties
- Validation - Ensures your definitions match the required structure
Event Structure
Each event definition has three parts:
1. Event Name
The name field is what gets sent to your analytics providers:
{
name: 'user_signed_up' // Sent to PostHog, Bento, etc.
}Naming conventions:
- Use snake_case for consistency with most analytics tools
- Be descriptive but concise:
checkout_completednotuser_clicked_complete_checkout_button - Use past tense:
button_clickednotbutton_click
2. Category
Categories help organize events into logical groups:
{
category: 'user' // Groups with other user-related events
}Predefined categories:
product- Product-related events (viewed, added to cart)user- User lifecycle events (signup, login, profile updates)navigation- Page views and navigationconversion- Conversion events and goalsengagement- Feature usage and interactionserror- Error trackingperformance- Performance monitoring
Custom categories:
You can also define your own:
export const appEvents = {
aiResponseGenerated: {
name: 'ai_response_generated',
category: 'ai', // Custom category
properties: {} as {
model: string;
tokensUsed: number;
responseTime: number;
}
}
} as const;3. Properties
Properties are the data attached to each event:
{
properties: {} as {
email: string; // Required property
plan: 'free' | 'pro'; // Union types for valid values
referralSource?: string; // Optional with ?
}
}Best practices for properties:
// ✅ Good - specific types
properties: {} as {
plan: 'free' | 'pro' | 'enterprise';
amount: number;
currency: 'USD' | 'EUR' | 'GBP';
}
// ❌ Bad - too loose
properties: {} as {
plan: string;
amount: any;
currency: string;
}Tracking Events
Once events are defined, tracking them is simple:
Client-Side
import { analytics } from '@/lib/analytics';
// Fire-and-forget (non-blocking)
analytics.track('button_clicked', {
buttonId: 'cta',
location: 'hero'
});
// User continues immediatelyServer-Side
import { serverAnalytics } from '@/lib/server-analytics';
// Await for critical events
await serverAnalytics.track('payment_processed', {
amount: 99.99,
currency: 'USD'
}, {
userId: 'user-123',
user: {
email: 'user@example.com'
}
});
// Always shutdown in serverless
await serverAnalytics.shutdown();Event Organization
Single File Approach
For small apps, keep all events in one file:
export const appEvents = {
// User events
userSignedUp: { /* ... */ },
userLoggedIn: { /* ... */ },
// Product events
productViewed: { /* ... */ },
productPurchased: { /* ... */ },
// Engagement events
buttonClicked: { /* ... */ },
featureUsed: { /* ... */ }
} as const;Multi-File Approach
For larger apps, split by category:
export const userEvents = {
userSignedUp: { /* ... */ },
userLoggedIn: { /* ... */ }
} as const;export const productEvents = {
productViewed: { /* ... */ },
productPurchased: { /* ... */ }
} as const;import { userEvents } from './user';
import { productEvents } from './product';
export const appEvents = {
...userEvents,
...productEvents
} as const;Common Event Patterns
E-commerce
export const appEvents = {
productViewed: {
name: 'product_viewed',
category: 'product',
properties: {} as {
productId: string;
productName: string;
price: number;
currency: string;
}
},
addedToCart: {
name: 'added_to_cart',
category: 'product',
properties: {} as {
productId: string;
quantity: number;
price: number;
}
},
checkoutStarted: {
name: 'checkout_started',
category: 'conversion',
properties: {} as {
cartTotal: number;
itemCount: number;
}
},
purchaseCompleted: {
name: 'purchase_completed',
category: 'conversion',
properties: {} as {
orderId: string;
total: number;
currency: string;
items: Array<{
productId: string;
quantity: number;
price: number;
}>;
}
}
} as const;SaaS Application
export const appEvents = {
userSignedUp: {
name: 'user_signed_up',
category: 'user',
properties: {} as {
email: string;
plan: 'free' | 'pro' | 'enterprise';
referralSource?: string;
}
},
trialStarted: {
name: 'trial_started',
category: 'conversion',
properties: {} as {
plan: 'pro' | 'enterprise';
trialDays: number;
}
},
featureUsed: {
name: 'feature_used',
category: 'engagement',
properties: {} as {
featureName: string;
plan: string;
}
},
subscriptionUpgraded: {
name: 'subscription_upgraded',
category: 'conversion',
properties: {} as {
fromPlan: string;
toPlan: string;
price: number;
}
}
} as const;Best Practices
1. Define Events Early
Create your event definitions before implementing tracking:
// ✅ Good - centralized, typed definitions
// lib/events.ts
export const appEvents = { /* all events */ };
// components/Button.tsx
analytics.track('button_clicked', { /* autocomplete works! */ });// ❌ Bad - scattered, untyped tracking
// components/Button.tsx
analytics.track('btn_click', { id: 'foo' }); // Typo? What properties?2. Document Your Events
Add comments to explain when and why events fire:
export const appEvents = {
// Fired when user completes the onboarding flow
// Triggers: After final step submission
// Used by: Marketing team for conversion tracking
onboardingCompleted: {
name: 'onboarding_completed',
category: 'user',
properties: {} as {
steps: number;
duration: number;
skipped: boolean;
}
}
} as const;3. Keep Event Names Stable
Once an event is in production, avoid renaming it:
// ✅ Good - add new event, deprecate old one
export const appEvents = {
// @deprecated Use 'subscription_upgraded' instead
planUpgraded: { /* ... */ },
subscriptionUpgraded: { /* ... */ } // New event
} as const;4. Use Consistent Naming
Pick a convention and stick to it:
// ✅ Good - consistent past tense
userSignedUp: { name: 'user_signed_up' }
buttonClicked: { name: 'button_clicked' }
formSubmitted: { name: 'form_submitted' }
// ❌ Bad - inconsistent tenses
userSignup: { name: 'user_signup' } // noun
clickButton: { name: 'click_button' } // imperative
formSubmitted: { name: 'form_submitted' } // past tense