Custom Events
Track user actions, conversions, and business metrics with custom events.
Custom events let you track specific things users do in your app — button clicks, form submissions, purchases, feature usage, whatever matters to your product. VibePing auto-tracks page views and errors, but custom events are where you get the data that actually drives decisions.
Sending Events
There are two ways to track events depending on how you installed VibePing.
Script tag installation — use the global window.__vibeping.track:
window.__vibeping.track('signup_completed', {
plan: 'pro',
source: 'landing_page'
});npm package — import and call track:
import { track } from '@vibeping/sdk';
track('signup_completed', {
plan: 'pro',
source: 'landing_page'
});Both do the same thing. The first argument is the event name (string). The second is an optional properties object.
Naming Conventions
Stick with snake_case and a verb_noun pattern. This keeps your event list readable and sortable as it grows.
Good names:
signup_completedcart_addedfeature_usedinvoice_downloaded
Bad names:
SignupCompleted— inconsistent casingclick— too vague, tells you nothinguser-clicked-the-big-blue-button— too specific, hard to querysignup_completed_from_landing_page_v2— put variants in properties, not the event name
The key rule: use properties for variants, not separate event names. Instead of signup_completed_google and signup_completed_email, track one signup_completed event with a method property:
track('signup_completed', { method: 'google' });
track('signup_completed', { method: 'email' });This way you can filter and break down a single event instead of hunting through dozens of similar names.
Property Types and Limits
Properties are key-value pairs attached to an event. Supported value types:
| Type | Example | Notes |
|---|---|---|
string | "pro" | Max 500 characters |
number | 49.99 | Integers or floats |
boolean | true | Useful for flags |
Keep property keys in snake_case too. You can attach up to 50 properties per event. Property keys have a max length of 100 characters.
Don't nest objects — properties are flat key-value pairs only.
// Good
track('purchase_completed', {
amount: 49.99,
currency: 'usd',
item_count: 3,
is_first_purchase: true
});
// Bad — no nested objects
track('purchase_completed', {
cart: { items: [{ name: 'shirt' }] } // won't work as expected
});Event Taxonomy Examples
Here are real-world event structures for common app types. Copy and adapt these.
E-commerce Funnel
track('product_viewed', {
product_id: 'sku_123',
category: 'shoes',
price: 89.99
});
track('cart_added', {
product_id: 'sku_123',
quantity: 1
});
track('checkout_started', {
item_count: 3,
cart_total: 219.97
});
track('purchase_completed', {
order_id: 'ord_456',
total: 219.97,
payment_method: 'stripe',
item_count: 3
});This gives you a full funnel: view → cart → checkout → purchase. You can calculate drop-off at each stage.
SaaS Onboarding
track('signup_started', {
source: 'homepage_cta'
});
track('signup_completed', {
method: 'google',
plan: 'free'
});
track('onboarding_step_completed', {
step: 'connect_repo',
step_number: 1
});
track('onboarding_step_completed', {
step: 'invite_team',
step_number: 2
});
track('first_project_created', {
template: 'nextjs_starter'
});Notice onboarding_step_completed is reused with different step properties instead of creating separate events for each step. This is the pattern you want.
Feature Usage
For tracking which features people actually use:
track('feature_used', {
feature: 'dark_mode',
action: 'enabled'
});
track('feature_used', {
feature: 'export_csv',
row_count: 1500
});
track('feature_used', {
feature: 'ai_summary',
input_length: 2400
});One event name, different feature properties. Easy to query, easy to compare usage across features.
Best Practices
Keep names consistent across your codebase. Define your event names as constants in a shared file. Don't let different developers invent different names for the same action.
// events.ts
export const EVENTS = {
SIGNUP_COMPLETED: 'signup_completed',
PURCHASE_COMPLETED: 'purchase_completed',
FEATURE_USED: 'feature_used',
} as const;Don't track PII in event properties. Never send emails, full names, phone numbers, or anything personally identifiable through event properties. If you need to associate events with users, use identify() separately and keep event properties anonymous.
// Bad
track('signup_completed', { email: 'user@example.com' });
// Good
identify({ userId: 'usr_abc123' });
track('signup_completed', { plan: 'pro' });Batch related properties together. If you're tracking a purchase, include all the relevant context in one event rather than firing multiple events for the same action.
// Good — everything about the purchase in one event
track('purchase_completed', {
total: 99.00,
item_count: 2,
payment_method: 'stripe',
is_first_purchase: false
});
// Bad — splitting one action into multiple events
track('purchase_completed');
track('purchase_amount', { total: 99.00 });
track('purchase_method', { method: 'stripe' });Start small. You don't need to track everything on day one. Start with 5-10 events that map to your core funnel, then add more as you learn what questions you're trying to answer. Too many events too early leads to a messy, unusable event list.
Test your events. Open your VibePing dashboard after deploying and trigger each event manually. Make sure the names and properties show up as expected before you ship to users.