Bad event tracking is worse than no event tracking. At least with no tracking, you know you're guessing. With messy tracking, you think you have answers — but they're wrong.
Here's how to build a tracking system that actually works.
Naming conventions#
This is the single most important decision you'll make. Pick a convention and enforce it ruthlessly.
Use object-action format#
Name events as object_action:
page_viewed
button_clicked
form_submitted
project_created
user_invited
payment_completed
Not clicked_button, not ButtonClick, not user clicked the signup button. Consistent object_action with snake_case.
Why this matters#
When you have 200 events (and you will), you need them to be sortable and searchable. Object-action format means all page events group together, all form events group together.
Compare:
❌ Random naming
click_signup
UserRegistered
page - pricing viewed
completed checkout
form_submit_contact
✅ Object-action, snake_case
signup_clicked
user_registered
page_viewed
checkout_completed
contact_form_submitted
Track events without writing code
Grain's point-and-click event capture lets you instrument any element on your site — no developer tickets, no GTM configuration. Clean event data from day one.
Property schemas#
Events without properties are almost useless. page_viewed tells you nothing. page_viewed with { path: "/pricing", referrer: "google", duration: 45 } tells you a story.
Required properties for every event#
Every event should include:
- timestamp — When it happened (usually handled by your analytics SDK)
- page_path — Where it happened
- session_id — Which session it belongs to
Event-specific properties#
Keep properties flat (no nesting), use consistent types, and be specific:
{
"event": "project_created",
"properties": {
"project_name": "My App",
"template_used": "blank",
"team_size": 3,
"is_first_project": true
}
}
Property naming rules#
- snake_case — Always.
teamSizeandteam-sizeandTeam Sizeare the same thing named three different ways. - Boolean prefixes — Use
is_orhas_:is_first_project,has_team. - No PII — Never track emails, names, or phone numbers in event properties. Use anonymized IDs.
- Enums over free text —
plan_type: "growth"notplan_type: "Growth Plan (Annual)".
Building a tracking plan#
A tracking plan is a spreadsheet (or document) that lists every event your product tracks. It's the source of truth.
What to include#
| Column | Example |
|---|---|
| Event name | checkout_completed |
| Description | User completes payment flow |
| Trigger | Payment confirmation page loads |
| Properties | amount, currency, plan_type, is_annual |
| Owner | Growth team |
| Added | 2026-01-15 |
Start small#
Track 15-20 events, not 200. You can always add more. You can never clean up a mess of 500 poorly-named events without breaking dashboards.
Focus on:
- Core conversion events — The 5-7 events in your primary funnel
- Feature adoption events — Key features you want users to discover
- Error events — When things go wrong (payment failures, form errors)
Common mistakes#
Tracking everything#
"We'll just track every click and figure it out later." You won't. You'll have a database full of button_clicked events with no context, and nobody will know what any of them mean six months from now.
Inconsistent implementations#
Developer A uses signupCompleted, Developer B uses signup_complete, Developer C uses user_signed_up. Now you have three events that mean the same thing and none of your dashboards are accurate.
Solution: the tracking plan is reviewed in PR. No event gets shipped without being in the plan.
Tracking PII in properties#
Passing email: "user@example.com" in event properties seems harmless until your analytics database is subject to a data access request and you have to find and delete every instance across millions of events.
Use anonymous user IDs. Always.
Not validating events#
Add validation at the SDK level. If an event isn't in your tracking plan, log a warning in development. This catches typos and unauthorized tracking before it ships.
Maintenance#
Quarterly audit#
Every quarter, review your events:
- Which events have zero volume? Remove them.
- Which events have inconsistent property values? Fix them.
- Which events are missing properties that would make them useful? Add them.
Deprecation process#
When you remove an event:
- Mark it as deprecated in the tracking plan
- Remove it from the codebase
- Update any dashboards or alerts that reference it
- Keep the historical data (don't delete it)
The payoff#
Clean event tracking isn't glamorous work. But it's the foundation everything else is built on. Your funnels, dashboards, A/B tests, and product decisions are only as good as the data feeding them.
Start with a clean foundation
Grain gives you structured event tracking with point-and-click capture, automatic property schemas, and a tracking plan that scales. No messy migrations later.
Invest the time upfront. Your future self will thank you.