Designing an event schema you will not regret in a year
Analytics instrumentation is added quickly, by different people, with names invented at the keyboard. Two years later there are four events for one action, nobody remembers which is current, and every analysis begins by asking which one to trust. The cost of preventing that is one page of conventions.
Event data has a property that makes it unusually unforgiving: you cannot backfill it. A missing property on an event you shipped six months ago is permanently missing, because the moment it described has passed. Every other data problem can be recomputed from source; this one cannot.
Naming, decided once and enforced
The convention itself barely matters. Having one, and rejecting anything that deviates, matters enormously — because the alternative is four spellings of one event and no way to know which the dashboard used.
What belongs on the event, and what does not
The distinction is whether the value could change without the event changing. Values that describe the moment belong on the event; values that describe an ongoing entity belong on that entity.
The second row is the one that gets missed and cannot be recovered. If plan tier lives only on the user profile, then after an upgrade every historical purchase appears to have been made on the new plan — and any analysis of what each tier actually buys is wrong, with no way to repair it.
Instrument on the server where the event matters
Client-side instrumentation is easier to add and less reliable in ways that bias the data rather than merely thinning it.
- Anything with money or a state change attached — server-side, without exception. A client-side purchase event counts attempts, including ones that failed after the call
- Interaction detail — clicks, scroll depth, hovers — is only observable client-side, and its unreliability matters less
- Critical funnels — server-side, because the bias is not uniform: blocking correlates with technical audiences, so the loss is concentrated
- Both, where a step is only visible in one place, with the shared identifier on both so they can be joined
The bias point is the important one. If client-side collection loses a fifth of events uniformly at random, rates stay roughly correct. It does not lose them uniformly — it loses them from people running blockers, which is a specific population, so every segment comparison is skewed in an unknown direction.
Identity, before the first event ships
Events from one person before and after signing in must be connectable, or every acquisition analysis breaks at the exact moment it becomes interesting.
- An anonymous identifier, assigned on first visit and persisted, so pre-signup activity is attributable to someone.
- A stable user identifier that never changes — not an email address, which does change.
- An explicit alias step at signup, linking the anonymous identifier to the user one.
- An account or organisation identifier on every event in business software, since the account is the unit of revenue and the user is not.
- Never a personal identifier as the key. Email addresses change, are shared, and carry deletion obligations that a surrogate key does not.
Changing an event without destroying its history
Requirements change and events must change with them. The question is whether the change preserves the ability to analyse across it.
The fifth row is the one that causes the most damage while looking innocuous. Moving signup_completed from form submission to email verification is a reasonable product decision that makes the metric discontinuous, and because the name is unchanged nobody notices until someone asks why conversion dropped on a date with no release.
The tracking plan, which is the whole discipline
- One document listing every event, its properties, their types, and what triggers it
- Reviewed before instrumentation is written, not documented afterwards from the code
- Typed in code where possible, so an unknown event or a wrong property type fails the build
- Including a deprecation column, because the useful information is which events are no longer trustworthy
- Owned by someone specific, since an unowned document is stale within a quarter
Typed event definitions are the single highest-value item there. An event emitted with a misspelled name or a number where a string was expected is invisible at runtime and appears as a gap in a chart weeks later. Making it a compile error costs an afternoon and removes the entire category.
Questions
How should analytics events be named?
object_action, lowercase snake_case, past tense — order_completed, signup_started, invite_sent. Object first groups related events together alphabetically in any tool that lists them, and past tense reflects that an event records something that happened rather than an intent. The convention matters less than enforcing one: a second name for the same action splits its history in two, and neither half is complete.
Should properties go on the event or the user profile?
On the event if the value describes that moment and must not change later — order total, plan tier at the time of purchase, device, experiment variant. On the profile if it describes an ongoing entity, like the current email address. Getting plan tier wrong is the costly case: if it lives only on the profile, every historical purchase appears to have been made on the user's current plan, and that cannot be repaired.
Is client-side or server-side tracking better?
Server-side for anything involving money or a state change, because client-side events are blocked, lost on navigation, tamperable and fire on attempt rather than completion. Crucially the loss is not random — it correlates with people running blockers, so every segment comparison is skewed in an unknown direction. Client-side is the only option for interaction detail like scroll depth, where unreliability matters less.
Can I change what triggers an existing event?
Not without a new name. Moving signup_completed from form submission to email verification makes the metric discontinuous, and because the name is unchanged nobody notices until someone asks why conversion dropped on a date with no release. Adding properties and adding events are safe; renaming, retyping and re-triggering all require a new definition alongside the old one.