Skip to content
All Docs

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_completed
  • cart_added
  • feature_used
  • invoice_downloaded

Bad names:

  • SignupCompleted — inconsistent casing
  • click — too vague, tells you nothing
  • user-clicked-the-big-blue-button — too specific, hard to query
  • signup_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:

TypeExampleNotes
string"pro"Max 500 characters
number49.99Integers or floats
booleantrueUseful 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.