Event definition and naming strategy
Build a consistent, maintainable analytics system with clear naming conventions
What's covered
- Why centralized event definitions matter
- The "Object Verb" naming convention
- How to organize frontend vs backend events
- Patterns for scalable event management
- Common naming mistakes to avoid
Why Event Strategy Matters
Without a strategy, analytics becomes chaos:
// ❌ Inconsistent naming across the team
track('user_searched_something');
track('Search Executed');
track('search-performed');
track('SEARCH_ACTION');
With a strategy, analytics tell a clear story:
// ✅ Consistent, predictable, searchable
track('Search Performed');
track('Filter Applied');
track('Export Generated');
track('Todo Created');
The "Object Verb" Convention
Format: [Object] [Past Tense Verb]
Examples:
- ✅ Todo Created
- ✅ User Invited
- ✅ Search Performed
- ✅ Integration Connected
Not:
- ❌ Create Todo
- ❌ Inviting User
- ❌ Button Clicked
- ❌ Page Viewed
Why This Works:
- Consistent - Always know the format
- Searchable - Easy to find in analytics tools
- Clear - Immediately understand what happened
- Sortable - Groups related events naturally
Centralized Event Definitions
The Problem with Scattered Events
// ❌ Events defined everywhere
// FileA.js
track('user performed search');
// FileB.js
track('Search Performed');
// FileC.js
track('SEARCH_EXECUTED');
Issues:
- Same event tracked differently
- No way to audit all events
- Typos create phantom events
- Hard to maintain
The Solution: Single Source of Truth
Create dedicated event definition files:
src/analytics/
└── events.js # Backend event definitions
static/spa/src/analytics/
└── events.js # Frontend event definitions
Backend Event Definitions
Working Example Available
The following code is available in our sample app available on GitHub.
🔗 View the full Forge Analytics Example on GitHub
src/analytics/events.js
/**
* BACKEND EVENT DEFINITIONS
*
* Single source of truth for all backend analytics events.
*
* NAMING CONVENTION: "Object Verb" format
* - Todo Created (not Create Todo)
* - Search Performed (not Perform Search)
* - Export Generated (not Generate Export)
*/
import { track } from './dispatcher';
// Core tracking function with identify/group
const trackEvent = async (context, eventName) => {
const userId = context.accountId;
const groupId = context.cloudId;
// Bundle identify, group, and track
await track(userId, groupId, eventName);
};
/**
* TODO EVENTS
*/
export const trackTodoCreated = (context) =>
trackEvent(context, 'Todo Created');
export const trackTodoUpdated = (context) =>
trackEvent(context, 'Todo Updated');
export const trackTodoDeleted = (context) =>
trackEvent(context, 'Todo Deleted');
export const trackTodosCleared = (context) =>
trackEvent(context, 'Todos Cleared');
/**
* USER EVENTS
*/
export const trackUserInvited = (context) =>
trackEvent(context, 'User Invited');
export const trackUserRemoved = (context) =>
trackEvent(context, 'User Removed');
export const trackUserRoleChanged = (context) =>
trackEvent(context, 'User Role Changed');
/**
* INTEGRATION EVENTS
*/
export const trackIntegrationConnected = (context) =>
trackEvent(context, 'Integration Connected');
export const trackIntegrationDisconnected = (context) =>
trackEvent(context, 'Integration Disconnected');
export const trackIntegrationSynced = (context) =>
trackEvent(context, 'Integration Synced');
Frontend Event Definitions
Working Example Available
The following code is available in our sample app available on GitHub.
🔗 View the full Forge Analytics Example on GitHub
static/spa/src/analytics/events.js
/**
* FRONTEND EVENT DEFINITIONS
*
* Single source of truth for all frontend analytics events.
* All events are routed through backend resolvers for privacy.
*
* NAMING CONVENTION: "Object Verb" format
*/
import { invoke } from '@forge/bridge';
// Core tracking function
const track = async (eventName) => {
try {
await invoke('track-event', { event: eventName });
} catch (error) {
console.error('[Analytics] Failed to track:', error);
}
};
/**
* SEARCH EVENTS
*/
export const trackSearchPerformed = (searchType) =>
track(`${searchType} Search Performed`);
export const trackFilterApplied = (filterType) =>
track(`${filterType} Filter Applied`);
/**
* MEANINGFUL INTERACTION EVENTS
*/
export const trackFormSubmitted = (formName) =>
track(`${formName} Form Submitted`);
export const trackFormCancelled = (formName) =>
track(`${formName} Form Cancelled`);
export const trackExportGenerated = (exportType) =>
track(`${exportType} Export Generated`);
/**
* FEATURE USAGE EVENTS
*/
export const trackSearchUsed = () =>
track('Search Used');
export const trackFilterApplied = (filterType) =>
track(`${filterType} Filter Applied`);
export const trackExportRequested = (format) =>
track(`${format} Export Requested`);
/**
* APP LIFECYCLE EVENTS
*/
export const trackAppLoaded = () =>
track('App Loaded');
export const trackAppErrored = () =>
track('App Errored');
Event Categories and Patterns
1. CRUD Operations
// Pattern: [Object] [CRUD Verb]
'Todo Created'
'Todo Updated'
'Todo Deleted'
'Todos Listed' // Plural for bulk operations
2. Feature Usage
// Pattern: [Feature] [Action Taken]
'Advanced Search Performed'
'Bulk Export Generated'
'Integration Configured'
'Template Applied'
3. User Workflows
// Pattern: [Workflow] [Completion State]
'Onboarding Completed'
'Setup Wizard Abandoned'
'Migration Finished'
4. Features
// Pattern: [Feature] [Usage Verb]
'Search Performed'
'CSV Export Generated'
'Notification Sent'
'Theme Changed'
5. Errors and Issues
// Pattern: [Context] Error Occurred
'API Error Occurred'
'Validation Error Occurred'
'Permission Error Occurred'
Organizing Complex Apps
For larger apps, organize events by domain:
// src/analytics/events/index.js
export * from './todo-events';
export * from './user-events';
export * from './project-events';
export * from './billing-events';
// src/analytics/events/todo-events.js
export const trackTodoCreated = (context) =>
trackEvent(context, 'Todo Created');
export const trackTodoAssigned = (context) =>
trackEvent(context, 'Todo Assigned');
// src/analytics/events/project-events.js
export const trackProjectCreated = (context) =>
trackEvent(context, 'Project Created');
export const trackProjectArchived = (context) =>
trackEvent(context, 'Project Archived');
Event Naming Checklist
Before adding a new event, verify:
- Follows "Object Verb" format
- Uses past tense verb
- No redundant prefixes (e.g., "Event:", "Track:")
- No implementation details (e.g., "Button1", "Link_2")
- No PII in event name
- Consistent with existing events
- Added to central definition file
Common Anti-Patterns to Avoid
❌ Technical Implementation Details
// Bad - exposes implementation
track('div#search-input focused');
track('GET /api/search?q=test Success');
track('Redux Action SEARCH_EXECUTED');
// Good - describes user intent
track('Search Performed');
track('Filter Applied');
❌ Inconsistent Formatting
// Bad - mixed formats
track('search-performed');
track('FILTER_APPLIED');
track('exportGenerated');
// Good - consistent format
track('Search Performed');
track('Filter Applied');
track('Export Generated');
❌ Present or Future Tense
// Bad - wrong tense
track('Performing Search');
track('Will Generate Export');
track('Is Applying Filter');
// Good - past tense
track('Search Performed');
track('Export Generated');
track('Filter Applied');
❌ Vague or Generic Names
// Bad - not specific
track('Action');
track('Event Happened');
track('User Did Something');
// Good - specific and clear
track('Search Performed');
track('Report Generated');
track('Subscription Upgraded');
Key Takeaways
- Centralize definitions - One file per environment (frontend/backend)
- "Object Verb" format - Consistent and clear
- Past tense only - Describes what happened
- No implementation details - Focus on user intent
Updated 14 days ago