Skip to content
Data engineering6 min read

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.

Part ofData Engineering, Databases & Analytics

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.

text
object_action, lowercase, snake_case, past tense.

  order_completed      not  completedOrder, CompleteOrder,
                            "Order Completed", order-complete
  signup_started
  invite_sent
  subscription_cancelled

Why object first: events sort into related groups alphabetically, so
every order_* event appears together in whatever tool lists them.

Why past tense: an event records something that happened. Present
tense reads as a command, which invites the mistake of firing it on
intent rather than on completion.

The rule that matters more than the convention: one action, one
event name, forever. A second name for the same action splits its
history in two and neither half is complete.

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.

ValueBelongs onBecause
Order total, currency, item countThe eventDescribes this occurrence and must not change later
Plan tier at the time of purchaseThe eventTheir current plan tells you nothing about what they paid then
Current email addressThe user profileIt changes, and history should not be rewritten by an update
Account creation dateThe user profileStable, and needed for cohorting every event
Device, platform, app versionThe eventVaries per occurrence
Experiment variantThe eventThe assignment in force at that moment

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.

ClientServer
Blocked by extensionsFrequentlyNever
Lost on navigation or crashYesNo
Can be tampered withYesNo
Sees interaction detailYesOnly what reaches the server
Reflects actual outcomeNo — fires on attemptYes — fires on completion
  • 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.

  1. An anonymous identifier, assigned on first visit and persisted, so pre-signup activity is attributable to someone.
  2. A stable user identifier that never changes — not an email address, which does change.
  3. An explicit alias step at signup, linking the anonymous identifier to the user one.
  4. An account or organisation identifier on every event in business software, since the account is the unit of revenue and the user is not.
  5. 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.

ChangeSafe?Approach
Adding a new propertyYesOld events have it null — expected and queryable
Adding a new eventYesNo effect on anything existing
Renaming a propertyNoAdd the new one, write both, migrate readers, then stop
Changing a property's typeNoAdd a new property; never redefine in place
Changing what triggers an eventNoNew event name — the old series measures something else now
Removing a propertyOnly after checkingSearch every consumer first; nothing warns you

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.

Want this applied to your systems?

Most of what is described here begins as an assessment: a fixed-scope review producing a prioritised written report you can act on with any supplier.

Prefer email? hello@kornspire.com