Overview
Event tracking in Amplitude captures discrete user actions and their context, forming the foundation for behavioral analytics. A well-designed event taxonomy enables powerful funnel analysis, cohort segmentation, and retention studies while remaining maintainable as your product evolves.
This guide covers event design principles, implementation patterns, and quality assurance practices for Amplitude.
Event Model Architecture
Event Structure
Every Amplitude event consists of:
- Event Name: Action being tracked (e.g.,
product_viewed,purchase_completed) - Event Properties: Contextual data specific to this action
- User Properties: Attributes describing the user
- Timestamp: When the event occurred
- User ID / Device ID: Who performed the action
amplitude.track('product_viewed', {
// Event properties
product_id: 'SKU-12345',
product_name: 'Wireless Headphones',
category: 'Electronics',
price: 79.99,
currency: 'USD',
view_source: 'search_results'
});
Event Design Principles
- Specificity: Events should represent distinct actions (
checkout_startedvs. genericbutton_clicked) - Consistency: Use the same event for the same action across all contexts
- Clarity: Event names should be self-documenting (
user_logged_innotevent1) - Avoid overloading: Don't use one event for multiple unrelated actions
Event vs. Property Decision
| Use Event Name When | Use Event Property When |
|---|---|
| Action is fundamentally different | Action is the same, context varies |
| You need separate funnel steps | You want to filter/segment same action |
| Business logic differs | Reporting grouping needed |
Example:
// Good: Same action, context as property
amplitude.track('video_played', {
video_id: 'VID-123',
video_category: 'tutorial' // Property
});
amplitude.track('video_played', {
video_id: 'VID-456',
video_category: 'marketing' // Property
});
// Bad: Separate events for same action
amplitude.track('tutorial_video_played', { video_id: 'VID-123' });
amplitude.track('marketing_video_played', { video_id: 'VID-456' });
Core Event Catalog
Navigation Events
Page View
amplitude.track('page_viewed', {
page_path: window.location.pathname,
page_title: document.title,
page_url: window.location.href,
page_type: 'product', // home, category, product, cart, checkout
referrer: document.referrer
});
Route View (SPAs)
// For single-page applications
router.afterEach((to, from) => {
amplitude.track('route_viewed', {
route_path: to.path,
route_name: to.name,
page_title: to.meta.title,
previous_route: from.path
});
});
Navigation Click
amplitude.track('navigation_clicked', {
link_text: 'Products',
link_url: '/products',
link_position: 'header',
navigation_type: 'main_menu'
});
Ecommerce Events
Product Viewed
amplitude.track('product_viewed', {
product_id: 'SKU-12345',
product_name: 'Wireless Headphones',
sku: 'SKU-12345',
category: 'Electronics',
subcategory: 'Audio',
brand: 'AudioTech',
price: 79.99,
currency: 'USD',
availability: 'in_stock',
view_source: 'search_results' // search, recommendation, direct, category
});
Product List Viewed
amplitude.track('product_list_viewed', {
list_name: 'Search Results',
list_id: 'search_electronics_headphones',
category: 'Electronics',
item_count: 24,
sort_order: 'price_low_to_high',
filters_applied: ['brand:AudioTech', 'price:50-100']
});
Add to Cart
amplitude.track('product_added_to_cart', {
product_id: 'SKU-12345',
product_name: 'Wireless Headphones',
price: 79.99,
quantity: 1,
currency: 'USD',
cart_total: 79.99,
cart_item_count: 1,
add_method: 'quick_add' // quick_add, pdp_button, recommendation
});
Remove from Cart
amplitude.track('product_removed_from_cart', {
product_id: 'SKU-12345',
product_name: 'Wireless Headphones',
price: 79.99,
quantity: 1,
cart_total: 0,
cart_item_count: 0,
removal_reason: 'user_action' // user_action, out_of_stock
});
Cart Viewed
amplitude.track('cart_viewed', {
cart_total: 142.97,
cart_item_count: 3,
currency: 'USD',
cart_items: ['SKU-12345', 'SKU-67890', 'SKU-11111']
});
Checkout Started
amplitude.track('checkout_started', {
cart_total: 142.97,
cart_item_count: 3,
currency: 'USD',
coupon_code: 'SUMMER10',
discount_amount: 14.30,
checkout_type: 'standard' // standard, express, guest
});
Shipping Info Added
amplitude.track('shipping_info_added', {
shipping_method: 'ground',
shipping_cost: 5.99,
shipping_tier: 'standard',
estimated_delivery_days: 5
});
Payment Info Added
amplitude.track('payment_info_added', {
payment_method: 'credit_card',
payment_type: 'Visa',
saved_payment_used: false
});
Purchase Completed
// Revenue API (recommended for revenue tracking)
const revenue = new amplitude.Revenue()
.setProductId('SKU-12345')
.setPrice(79.99)
.setQuantity(1)
.setRevenueType('purchase');
amplitude.logRevenueV2(revenue);
// Purchase event
amplitude.track('purchase_completed', {
order_id: 'ORD-98765',
total_amount: 156.96,
subtotal: 142.97,
tax: 8.00,
shipping: 5.99,
discount: 14.30,
currency: 'USD',
coupon_code: 'SUMMER10',
item_count: 3,
payment_method: 'credit_card',
is_first_purchase: false,
items: [
{
product_id: 'SKU-12345',
product_name: 'Wireless Headphones',
category: 'Electronics',
price: 79.99,
quantity: 1
}
]
});
Feature Engagement Events
Feature Used
amplitude.track('feature_used', {
feature_name: 'advanced_search',
feature_category: 'search',
feature_context: 'product_catalog',
usage_count: 1 // Increment with user property
});
Search Performed
amplitude.track('search_performed', {
search_query: 'wireless headphones',
search_type: 'product', // product, help, content
result_count: 24,
has_filters: true,
filters_applied: ['category:Electronics'],
search_location: 'header'
});
Filter Applied
amplitude.track('filter_applied', {
filter_type: 'category',
filter_value: 'Electronics',
filter_group: 'product_filters',
result_count_before: 100,
result_count_after: 24
});
Sort Applied
amplitude.track('sort_applied', {
sort_option: 'price_low_to_high',
sort_location: 'product_list',
result_count: 24
});
User Lifecycle Events
User Signed Up
amplitude.track('user_signed_up', {
signup_method: 'email', // email, google, facebook, apple
referral_source: 'organic',
signup_location: 'homepage_hero',
account_type: 'individual'
});
User Logged In
amplitude.track('user_logged_in', {
login_method: 'email_password',
login_location: 'header',
session_count: 5
});
User Logged Out
amplitude.track('user_logged_out', {
logout_location: 'account_menu',
session_duration_seconds: 1200
});
Account Created
amplitude.track('account_created', {
account_type: 'business',
plan: 'free',
signup_method: 'email',
referral_code: 'FRIEND123',
team_size: 5
});
Subscription Events
Subscription Started
amplitude.track('subscription_started', {
plan: 'premium',
billing_cycle: 'monthly',
price: 29.99,
currency: 'USD',
trial_included: true,
trial_days: 14
});
Subscription Upgraded
amplitude.track('subscription_upgraded', {
previous_plan: 'basic',
new_plan: 'premium',
price_difference: 15.00,
upgrade_reason: 'user_initiated'
});
Subscription Downgraded
amplitude.track('subscription_downgraded', {
previous_plan: 'premium',
new_plan: 'basic',
downgrade_reason: 'cost',
feedback_provided: true
});
Subscription Canceled
amplitude.track('subscription_canceled', {
plan: 'premium',
cancellation_reason: 'too_expensive',
days_subscribed: 90,
total_value_paid: 89.97,
retention_offer_shown: true,
retention_offer_accepted: false
});
Engagement Events
Form Started
amplitude.track('form_started', {
form_id: 'contact_form',
form_name: 'Contact Us',
form_location: 'footer'
});
Form Submitted
amplitude.track('form_submitted', {
form_id: 'contact_form',
form_name: 'Contact Us',
form_location: 'footer',
time_to_complete_seconds: 45,
fields_completed: 5,
fields_total: 5
});
Form Error
amplitude.track('form_error', {
form_id: 'contact_form',
error_type: 'validation',
error_field: 'email',
error_message: 'Invalid email format'
});
Video Started
amplitude.track('video_started', {
video_id: 'VID-123',
video_title: 'Product Demo',
video_category: 'tutorial',
video_duration_seconds: 120,
video_location: 'product_page',
autoplay: false
});
Video Progress
amplitude.track('video_progress', {
video_id: 'VID-123',
percent_watched: 50,
seconds_watched: 60,
milestone: '50%' // 25%, 50%, 75%, 100%
});
Video Completed
amplitude.track('video_completed', {
video_id: 'VID-123',
video_title: 'Product Demo',
total_watch_time_seconds: 120,
completion_rate: 100
});
Share Action
amplitude.track('content_shared', {
content_type: 'product',
content_id: 'SKU-12345',
share_method: 'email', // email, facebook, twitter, link
share_location: 'product_page'
});
Event Property Standards
Required Properties by Event Type
All Events (Recommended)
event_timestamp: ISO 8601 formatschema_version: Track implementation version
Ecommerce Events
currency: ISO 4217 code (USD, EUR, GBP)product_id: Unique product identifierprice: Numeric value
Navigation Events
page_pathorroute_pathpage_title
User Action Events
- Context about where action occurred
- Outcome or result of action
Property Value Guidelines
| Property Type | Format | Example |
|---|---|---|
| IDs | String | "SKU-12345", "ORD-98765" |
| Prices | Number | 79.99 (not "$79.99") |
| Currencies | String (ISO 4217) | "USD" |
| Timestamps | String (ISO 8601) | "2024-01-15T10:30:00Z" |
| Booleans | Boolean | true (not "true") |
| Enums | Lowercase snake_case | "credit_card", "product_page" |
Naming Conventions
Event Naming
Use past tense + snake_case:
product_viewed,cart_updated,purchase_completedproductView,Cart Update,Purchase Complete
Object-Action Pattern
Structure: [object]_[action]
product_viewedcart_updatedsubscription_canceledfilter_applied
Property Naming
Use snake_case and be descriptive:
product_id,total_amount,payment_methodprodId,amt,pm
Consistent Terminology
Maintain consistency across events:
| Use Consistently | Avoid Mixing |
|---|---|
product_id |
prod_id, productId, sku |
user_id |
userId, uid, customer_id |
total_amount |
total, amount, price |
Deduplication with insert_id
Prevent duplicate events during retries:
const insertId = `${userId}_${eventType}_${Date.now()}_${Math.random()}`;
amplitude.track('purchase_completed', {
order_id: 'ORD-98765',
total_amount: 156.96
}, {
insert_id: insertId
});
For deterministic deduplication:
// Use order_id as insert_id for purchase events
amplitude.track('purchase_completed', {
order_id: 'ORD-98765',
total_amount: 156.96
}, {
insert_id: `purchase_${orderId}` // Dedupe based on order
});
User ID and Device ID Management
Pre-Authentication (Anonymous)
// Amplitude auto-generates device_id
amplitude.init('YOUR_API_KEY');
// All events tracked with device_id only
amplitude.track('product_viewed', { product_id: 'SKU-12345' });
Post-Authentication
// Set user_id after login
amplitude.setUserId('user_12345');
// Events now have both user_id and device_id
amplitude.track('product_viewed', { product_id: 'SKU-67890' });
Logout Handling
// On logout
amplitude.track('user_logged_out');
// Clear user_id (keep device_id for continuity)
amplitude.setUserId(null);
amplitude.regenerateDeviceId(); // Optional: start fresh device tracking
Tracking Plan Governance
Define Expected Events
Create a tracking plan document:
| Event Name | Description | Required Properties | Optional Properties | Owner |
|---|---|---|---|---|
product_viewed |
User viewed product detail | product_id, product_name, price |
category, brand |
Product Team |
purchase_completed |
Purchase confirmed | order_id, total_amount, currency |
coupon_code, discount |
Commerce Team |
Use Amplitude Govern
- Define taxonomy: Mark events as "Expected" or "Planned"
- Set properties: Define required vs. optional properties
- Enforce schema: Block unexpected events or properties
- Monitor violations: Track when unexpected events occur
Schema Versioning
Include version in events:
amplitude.track('purchase_completed', {
schema_version: '2.1.0',
order_id: 'ORD-98765',
total_amount: 156.96
});
Change management:
- v2.0.0 → v2.1.0: Added optional property (backward compatible)
- v2.1.0 → v3.0.0: Renamed/removed property (breaking change)
QA and Validation
Pre-Launch Checklist
| Check | Validation Method |
|---|---|
| Event fires correctly | Browser console + debugger |
| Properties have correct types | Event Stream inspection |
| Required properties present | Govern schema validation |
| No PII in properties | Manual review |
| User ID set after login | Event Stream user_id field |
| Revenue events track correctly | Revenue LTV chart |
| Funnels work end-to-end | Funnel chart test |
| Session continuity maintained | User Sessions view |
Testing in Event Stream
- Navigate to Data > Events in Amplitude
- Enable Event Stream (real-time view)
- Trigger events in your application
- Verify in Event Stream:
- Event name appears
- Properties present with correct values
- User ID/Device ID correct
- Timestamps accurate
Funnel Testing
Test critical funnels end-to-end:
// Test checkout funnel
amplitude.track('product_viewed', { product_id: 'TEST-SKU' });
amplitude.track('product_added_to_cart', { product_id: 'TEST-SKU' });
amplitude.track('checkout_started', { cart_total: 99.99 });
amplitude.track('payment_info_added', { payment_method: 'test_card' });
amplitude.track('purchase_completed', { order_id: 'TEST-ORDER' });
Then in Amplitude:
- Create funnel: Product Viewed → Added to Cart → Checkout Started → Purchase Completed
- Filter by
order_id = "TEST-ORDER" - Verify 100% conversion through all steps
Revenue Validation
Confirm revenue tracking works:
// Track test revenue
const revenue = new amplitude.Revenue()
.setProductId('TEST-SKU')
.setPrice(99.99)
.setQuantity(1)
.setRevenueType('test_purchase');
amplitude.logRevenueV2(revenue);
Validate:
- Go to Analytics > Revenue LTV
- Filter by recent test events
- Confirm revenue appears correctly
Attribution Testing
Verify attribution persists through funnels:
- Land with UTM:
?utm_source=test&utm_campaign=qa - Trigger multiple events
- Complete conversion
- In Amplitude, verify all events attributed to
utm_source=test
Troubleshooting
| Symptom | Likely Cause | Solution |
|---|---|---|
| Events not appearing | API key incorrect | Verify API key in init |
| Properties missing | Not passed in track call | Check track() parameters |
| Wrong data types | String instead of number | Enforce types: parseFloat(), parseInt() |
| Duplicate events | No insert_id | Add unique insert_id to events |
| User ID not persisting | Not set after login | Call setUserId() after authentication |
| Revenue not in LTV chart | Using track() instead of Revenue API | Use logRevenueV2() for revenue |
| Funnel steps missing | Event name typo | Verify exact event names in Event Stream |
| Session fragmentation | Session timeout too short | Increase sessionTimeout in init |
| Attribution lost | UTM not captured | Capture UTM on first page load |
Advanced Patterns
Conditional Event Tracking
function trackConditionalEvent(eventName, properties, condition) {
if (condition) {
amplitude.track(eventName, properties);
}
}
// Only track for premium users
trackConditionalEvent('premium_feature_used', {
feature_name: 'advanced_analytics'
}, user.plan === 'premium');
Event Queuing for Offline
class OfflineEventQueue {
constructor() {
this.queue = JSON.parse(localStorage.getItem('eventQueue') || '[]');
}
add(eventName, properties) {
this.queue.push({ eventName, properties, timestamp: Date.now() });
localStorage.setItem('eventQueue', JSON.stringify(this.queue));
}
flush() {
this.queue.forEach(({ eventName, properties }) => {
amplitude.track(eventName, properties);
});
this.queue = [];
localStorage.setItem('eventQueue', '[]');
}
}
const eventQueue = new OfflineEventQueue();
// When offline
if (!navigator.onLine) {
eventQueue.add('product_viewed', { product_id: 'SKU-12345' });
}
// When back online
window.addEventListener('online', () => {
eventQueue.flush();
});
Batch Event Tracking
function trackBatchEvents(events) {
events.forEach(({ eventName, properties }) => {
amplitude.track(eventName, properties);
});
}
// Track multiple events at once
trackBatchEvents([
{ eventName: 'page_viewed', properties: { page_path: '/products' } },
{ eventName: 'feature_impression', properties: { feature: 'banner' } },
{ eventName: 'recommendation_shown', properties: { item_count: 4 } }
]);
Best Practices
- Start simple: Begin with 10-20 core events, expand as needed
- Document everything: Maintain a tracking plan with ownership
- Test thoroughly: Validate in Event Stream before production
- Use consistent naming: Enforce snake_case and past tense
- Include context: Add properties that explain "why" and "where"
- Deduplicate carefully: Use insert_id for idempotent events
- Track user lifecycle: Capture signup, login, subscription changes
- Measure outcomes: Focus on events that indicate success/failure
- Version your schema: Communicate breaking changes
- Review regularly: Audit events quarterly and deprecate unused ones