Documentation Index Fetch the complete documentation index at: https://mintlify.com/vemetric/vemetric/llms.txt
Use this file to discover all available pages before exploring further.
Overview
Following best practices when implementing Vemetric ensures clean, maintainable analytics that provide actionable insights. This guide covers recommended patterns for event naming, data structure, performance optimization, and data quality.
Well-structured analytics are easier to query, analyze, and share across your team. Invest time in planning your tracking strategy before implementation.
Event Naming Conventions
Use Descriptive, Action-Based Names
Event names should clearly describe what happened using past tense verbs:
Good Examples
Poor Examples
vemetric . track ( 'user_signed_up' );
vemetric . track ( 'purchase_completed' );
vemetric . track ( 'video_played' );
vemetric . track ( 'article_shared' );
vemetric . track ( 'filter_applied' );
Naming Convention Rules
Use snake_case
All event names should use lowercase with underscores: ✅ 'checkout_started'
✅ 'payment_method_selected'
❌ 'CheckoutStarted'
❌ 'checkout-started'
❌ 'CHECKOUT_STARTED'
Start with object, end with action
Format: object_action or object_action_detail ✅ 'product_viewed'
✅ 'cart_item_added'
✅ 'subscription_plan_changed'
❌ 'viewed_product'
❌ 'add_to_cart'
Be specific but concise
Include enough detail to be clear, but avoid redundancy: ✅ 'trial_started'
✅ 'premium_trial_started' // If you have multiple trial types
❌ 'user_started_premium_trial_subscription' // Too verbose
❌ 'trial' // Not specific enough
Use consistent terminology
Standardize terms across your application: // Choose one and stick with it
✅ 'post_created' , 'post_edited' , 'post_deleted'
❌ 'post_created' , 'article_updated' , 'content_removed'
// Document your terminology
// "post" = blog content
// "article" = help docs
// "content" = user-generated content
Event Naming Hierarchy
Organize events into logical categories:
// Authentication
'user_signed_up'
'user_logged_in'
'user_logged_out'
'password_reset_requested'
'password_reset_completed'
// E-commerce
'product_viewed'
'product_added_to_cart'
'cart_viewed'
'checkout_started'
'payment_info_entered'
'purchase_completed'
// Content Engagement
'article_viewed'
'article_liked'
'article_shared'
'comment_posted'
'video_played'
'video_completed'
// Feature Usage
'search_performed'
'filter_applied'
'export_downloaded'
'report_generated'
Event Properties
Structure Your Properties
Use clear, consistent property names with appropriate data types:
Good Structure
Poor Structure
vemetric . track ( 'purchase_completed' , {
// Identifiers
order_id: 'ORD-12345' ,
user_id: 'usr_abc123' ,
// Metrics
revenue: 99.99 ,
quantity: 3 ,
discount: 10.00 ,
// Categories
product_category: 'electronics' ,
payment_method: 'credit_card' ,
// Booleans
is_first_purchase: true ,
gift_wrap_requested: false ,
// Metadata
currency: 'USD' ,
shipping_country: 'US'
});
vemetric . track ( 'purchase_completed' , {
// Inconsistent naming
OrderID: 'ORD-12345' , // Wrong casing
'user-id' : 'usr_abc123' , // Wrong delimiter
// Wrong data types
revenue: '$99.99' , // String instead of number
quantity: '3' , // String instead of number
// Redundant nesting
payment: {
method: 'credit_card' // Unnecessary nesting
},
// Ambiguous names
type: 'electronics' , // What type?
flag: true , // Which flag?
// Sensitive data
credit_card_number: '4111...' , // Never send PII!
});
Property Naming Guidelines
Use snake_case for property names
✅ product_name , order_total , is_premium
❌ productName , OrderTotal , isPremium
Use appropriate data types
// Numbers for metrics
revenue : 99.99 , // ✅ Number
revenue : '$99.99' , // ❌ String
// Booleans for flags
is_premium : true , // ✅ Boolean
is_premium : 'yes' , // ❌ String
// Strings for categories
category : 'electronics' , // ✅ String
category : 1 , // ❌ Number
// Arrays for lists
tags : [ 'sale' , 'new' ], // ✅ Array
tags : 'sale, new' , // ❌ String
Avoid deeply nested objects
// ✅ Flat structure (preferred)
{
product_name : 'Widget' ,
product_price : 29.99 ,
product_category : 'tools'
}
// ❌ Nested structure (harder to query)
{
product : {
name : 'Widget' ,
price : 29.99 ,
category : 'tools'
}
}
Keep property names consistent
// ✅ Consistent across events
vemetric . track ( 'product_viewed' , {
product_id: 'prod_123' ,
product_name: 'Widget'
});
vemetric . track ( 'product_purchased' , {
product_id: 'prod_123' , // Same property name
product_name: 'Widget' // Same property name
});
// ❌ Inconsistent naming
vemetric . track ( 'product_viewed' , {
product_id: 'prod_123' ,
product_name: 'Widget'
});
vemetric . track ( 'product_purchased' , {
id: 'prod_123' , // Different name!
name: 'Widget' // Different name!
});
Standard Property Names
Use these standardized property names for common attributes:
Property Type Description Example *_idString Identifiers product_id, user_id, order_id*_nameString Display names product_name, plan_name*_categoryString Categories product_category, content_category*_countNumber Counts item_count, view_count*_amountNumber Monetary values order_amount, refund_amountis_*Boolean Flags is_premium, is_trial, is_first_timehas_*Boolean Possession has_subscription, has_verified_email*_atISO String Timestamps created_at, updated_at
User Properties
Setting User Data
User properties describe attributes of the user and should be updated when they change:
// On user signup
vemetric . identify ( 'user@example.com' , {
displayName: 'Jane Doe' ,
data: {
set: {
plan: 'premium' ,
account_created_at: new Date (). toISOString (),
email_verified: false
},
setOnce: {
signup_source: 'google_ads' ,
initial_referrer: document . referrer
}
}
});
// Later, when email is verified
vemetric . updateUser ({
data: {
set: {
email_verified: true
}
}
});
set vs setOnce vs unset
Use for : Attributes that can change over timedata : {
set : {
plan : 'enterprise' , // Can upgrade/downgrade
total_purchases : 42 , // Increases over time
last_login : '2024-03-04' , // Updates each login
preferred_language : 'en' // User can change
}
}
Behavior : Overwrites previous value each timeUse for : Attributes that should never change once setdata : {
setOnce : {
signup_date : '2024-01-15' ,
first_utm_source : 'google' ,
initial_referrer : 'https://example.com' ,
original_plan : 'free'
}
}
Behavior : Only sets value if not already setUse for : Removing properties that no longer applydata : {
unset : [ 'beta_feature_access' , 'temporary_discount' ]
}
Behavior : Removes the property from user profile
User Property Best Practices
Don't duplicate event data
Avoid : Storing data that changes with every event// ❌ Bad: Storing event-specific data as user properties
vemetric . identify ( 'user@example.com' , {
data: {
set: {
last_page_viewed: '/products/widget' , // Changes constantly
last_product_id: 'prod_123' // Event-specific
}
}
});
// ✅ Good: Use event properties instead
vemetric . track ( 'page_viewed' , {
page_path: '/products/widget'
});
Ensure consistent formatting :// ✅ Normalized values
country : 'US' , // ISO codes
currency : 'USD' , // ISO codes
plan : 'premium' , // Lowercase
status : 'active' , // Lowercase
// ❌ Inconsistent values
country : 'United States' , // Could be 'US', 'USA', 'United States'
currency : 'usd' , // Inconsistent casing
plan : 'Premium' , // Mixed casing
status : 'ACTIVE' , // All caps
Use meaningful property names
// ✅ Clear and descriptive
{
account_type : 'business' ,
email_verified : true ,
trial_ends_at : '2024-04-01' ,
feature_flags : [ 'advanced_analytics' , 'api_access' ]
}
// ❌ Ambiguous or cryptic
{
type : 'b' , // What type?
flag1 : true , // Which flag?
exp : '2024-04-01' , // Expiration? Experiment?
ff : [ 'aa' , 'api' ] // Unclear abbreviations
}
Load SDK Asynchronously
Prevent blocking page render:
<!-- ✅ Async loading (recommended) -->
< script async src = "https://cdn.vemetric.com/sdk.js" ></ script >
< script >
window . addEventListener ( 'load' , function () {
vemetric . init ({ token: 'your-token' });
});
</ script >
<!-- ❌ Blocking load -->
< script src = "https://cdn.vemetric.com/sdk.js" ></ script >
< script >
vemetric . init ({ token: 'your-token' });
</ script >
Debounce High-Frequency Events
Avoid tracking every instance of rapid events:
// ❌ Bad: Tracks every scroll event (hundreds per page)
window . addEventListener ( 'scroll' , () => {
vemetric . track ( 'page_scrolled' );
});
// ✅ Good: Debounced tracking
let scrollTimeout ;
window . addEventListener ( 'scroll' , () => {
clearTimeout ( scrollTimeout );
scrollTimeout = setTimeout (() => {
const scrollPercentage = ( window . scrollY / document . body . scrollHeight ) * 100 ;
vemetric . track ( 'scroll_depth_reached' , {
depth_percentage: Math . round ( scrollPercentage )
});
}, 500 ); // Wait 500ms after scrolling stops
});
// ✅ Better: Track milestone percentages
const milestones = [ 25 , 50 , 75 , 100 ];
const reached = new Set ();
window . addEventListener ( 'scroll' , () => {
const scrollPercentage = ( window . scrollY / document . body . scrollHeight ) * 100 ;
milestones . forEach ( milestone => {
if ( scrollPercentage >= milestone && ! reached . has ( milestone )) {
reached . add ( milestone );
vemetric . track ( 'scroll_milestone_reached' , {
milestone_percentage: milestone
});
}
});
});
Minimize Payload Size
// ❌ Bad: Sending large, unnecessary data
vemetric . track ( 'form_submitted' , {
entire_form_html: document . querySelector ( 'form' ). innerHTML , // Huge!
all_form_data: getAllFormValues (), // Could contain sensitive data
browser_history: window . history // Not needed
});
// ✅ Good: Send only relevant data
vemetric . track ( 'form_submitted' , {
form_id: 'contact-form' ,
field_count: 5 ,
completion_time_seconds: 47 ,
had_errors: false
});
Use Beacon API for Page Unload
Ensure events are sent even when user leaves page:
// ✅ Use sendBeacon for unload events
window . addEventListener ( 'beforeunload' , () => {
// Beacon API ensures delivery even as page unloads
navigator . sendBeacon (
'https://hub.vemetric.com/e' ,
JSON . stringify ({
name: 'session_ended' ,
duration_seconds: sessionDuration
})
);
});
// Or use Vemetric's built-in method
vemetric . track ( 'session_ended' , {
duration_seconds: sessionDuration
}, { beacon: true });
Data Quality
Validate Before Tracking
function trackPurchase ( orderData ) {
// ❌ Bad: No validation
vemetric . track ( 'purchase_completed' , orderData );
// ✅ Good: Validate data first
if ( ! orderData . order_id || ! orderData . revenue ) {
console . error ( 'Invalid order data' , orderData );
return ;
}
vemetric . track ( 'purchase_completed' , {
order_id: String ( orderData . order_id ),
revenue: parseFloat ( orderData . revenue ),
currency: orderData . currency || 'USD' ,
item_count: parseInt ( orderData . item_count ) || 1
});
}
Handle Missing Data Gracefully
// ✅ Use default values or omit properties
vemetric . track ( 'product_viewed' , {
product_id: product . id ,
product_name: product . name ,
// Only include if available
... ( product . category && { product_category: product . category }),
... ( product . price && { product_price: product . price })
});
// Or use defaults
vemetric . track ( 'product_viewed' , {
product_id: product . id ,
product_name: product . name || 'Unknown Product' ,
product_category: product . category || 'uncategorized' ,
product_price: product . price || 0
});
Add Error Tracking
// Track JavaScript errors
window . addEventListener ( 'error' , ( event ) => {
vemetric . track ( 'javascript_error' , {
error_message: event . message ,
error_source: event . filename ,
error_line: event . lineno ,
error_column: event . colno ,
page_url: window . location . href
});
});
// Track unhandled promise rejections
window . addEventListener ( 'unhandledrejection' , ( event ) => {
vemetric . track ( 'unhandled_promise_rejection' , {
reason: event . reason ?. message || String ( event . reason ),
page_url: window . location . href
});
});
Testing and QA
Development vs Production
Use different project tokens for different environments:
const VEMETRIC_TOKEN = process . env . NODE_ENV === 'production'
? 'prod_token_xyz'
: 'dev_token_abc' ;
vemetric . init ({
token: VEMETRIC_TOKEN ,
debug: process . env . NODE_ENV !== 'production'
});
Test Checklist
Verify events in real-time
Open Vemetric dashboard
Go to Real-time view
Trigger events in your app
Confirm events appear with correct properties
Check user identification
Trigger identify() call
Verify user appears in Users section
Confirm user properties are set correctly
Test event attribution to identified user
Test across browsers
Chrome/Edge (Chromium)
Firefox
Safari
Mobile browsers (iOS Safari, Chrome Mobile)
Validate data types
// Add validation in development
if ( process . env . NODE_ENV !== 'production' ) {
const validateEvent = ( name , properties ) => {
console . assert ( typeof name === 'string' , 'Event name must be string' );
console . assert ( name === name . toLowerCase (), 'Event name should be lowercase' );
for ( const [ key , value ] of Object . entries ( properties )) {
console . assert (
key === key . toLowerCase (),
`Property " ${ key } " should be lowercase`
);
}
};
// Wrap track method
const originalTrack = vemetric . track ;
vemetric . track = ( name , properties ) => {
validateEvent ( name , properties );
return originalTrack ( name , properties );
};
}
Documentation
Create a Tracking Plan
Document your events and properties:
# Tracking Plan
## Events
### user_signed_up
**Triggered when** : User completes signup form
**Properties** :
- `signup_method` (string): 'email', 'google', 'github'
- `referral_source` (string, optional): UTM source if present
- `is_email_verified` (boolean): Whether email was verified immediately
### purchase_completed
**Triggered when** : Order is successfully processed
**Properties** :
- `order_id` (string, required): Unique order identifier
- `revenue` (number, required): Total order value in dollars
- `currency` (string, required): ISO currency code (default: 'USD')
- `item_count` (number, required): Number of items in order
- `is_first_purchase` (boolean): Whether this is user's first order
Add context to tracking calls:
/**
* Track checkout start
* Fired when user clicks "Proceed to Checkout" button
* Used for: Conversion funnel analysis, cart abandonment tracking
*/
vemetric . track ( 'checkout_started' , {
cart_value: calculateCartTotal (),
item_count: cart . items . length ,
has_discount: discountCode !== null
});
Common Patterns
Track Page Views
// Single Page Application (SPA)
router . afterEach (( to , from ) => {
vemetric . track ( 'page_view' , {
page_path: to . path ,
page_title: to . meta . title ,
previous_page: from . path
});
});
// Traditional multi-page site
document . addEventListener ( 'DOMContentLoaded' , () => {
vemetric . track ( 'page_view' , {
page_path: window . location . pathname ,
page_title: document . title ,
referrer: document . referrer
});
});
// Form started
document . querySelectorAll ( 'form' ). forEach ( form => {
let interacted = false ;
form . addEventListener ( 'focusin' , () => {
if ( ! interacted ) {
interacted = true ;
vemetric . track ( 'form_started' , {
form_id: form . id ,
form_name: form . getAttribute ( 'name' )
});
}
});
// Form submitted
form . addEventListener ( 'submit' , () => {
vemetric . track ( 'form_submitted' , {
form_id: form . id ,
form_name: form . getAttribute ( 'name' )
});
});
});
Track Feature Usage
// When user uses a feature
function exportData () {
vemetric . track ( 'data_exported' , {
export_format: 'csv' ,
row_count: data . length ,
feature: 'user_management'
});
// Perform export
downloadCSV ( data );
}
Next Steps
Privacy Compliance Learn about GDPR/CCPA compliance and cookie management.
Troubleshooting Debug common tracking issues and integration problems.