Event Taxonomy: How to Name Analytics Events
We could easily spend days arguing about whether it's Product Viewed or product_viewed. Neither is wrong. That's exactly what makes it a trap.
Most taxonomy debates are about taste wearing the costume of debates about correctness. And because nobody can win on correctness, nobody commits — so you end up with all of them at once (yeah that happened to me). Four naming styles, three tenses, two casings, and an event called sign-up_completed_email 🙃
And that's how you end up on the event naming landfill.
So instead of one "right" answer (there's no right answer here, believe me), here's a tour of the key decisions and the options that come with them: what each costs, what each buys you. Then I'll tell you what I pick and walk you through my decision-making process — knowing full well it's a preference, not a law.
These break into four mostly-independent decisions. You can mix and match across them as you wish. You just can't mix and match inconsistently within one.
New to product analytics? Start with Product Analytics for Designers to get the basics, then come back here.
Decision 1: Event name structure
The big one — how you compose the name itself.
| Convention | Example | Pros | Cons |
|---|---|---|---|
| Object–Action | Product Viewed, Checkout Flow Step Started | Sorts related events together; groups naturally by object; scales cleanly to hundreds of events | Slightly unnatural to write |
| Action–Object | Viewed Product, Completed Checkout Flow Step | Reads like plain English; intuitive for newcomers | Sorting scatters related events; Viewed Product and Clicked Product Card end up rows apart |
| Category–Object–Action | Ecommerce Product Viewed, Ecommerce Checkout Flow Step Started | Extra grouping layer for very large products; suits big orgs | Verbose; the category boundary is always arguable; over-built for most teams |
| Flat / descriptive | homepage signup button click, checkout flow payment step submitted | Zero rules to learn, this is where most beginners start | Becomes a landfill almost immediately; impossible to query consistently |
The field has mostly converged on Object–Action (Segment popularized it early and it stuck).
And then: what divides the parts?
Pick a pattern and you've still got one decision left — the character that physically separates object from action. It feels trivial until an object or action runs more than one word, and suddenly nobody can tell where checkout flow ends and step started begins.
| Divider | Example | Pros | Cons |
|---|---|---|---|
| Space | Checkout Started, Checkout Flow Step Started | Most readable for a human; natural in reports and dashboards | Breaks in SQL, URLs, and code; stray double-spaces creep in silently |
Dot . | checkout.started, checkout_flow.step_started | Familiar namespacing (object.method); sorts and reads as a clean hierarchy | Many warehouses read . as nested-field access — BigQuery especially; can collide with property paths |
Colon : | checkout:started, checkout_flow:step_started | Strong, unambiguous separator; classic namespace style (user:created) | Less conventional, so less tooling muscle memory; looks odd to some eyes |
Slash / | checkout/started, checkout/flow/step/started | Reads as an explicit path; scales to deep hierarchy (checkout/payment/started) | Collides with URLs and file paths; implies more nesting than you usually have |
The divider earns its keep the moment a group runs multi-word — checkout_flow, step_started. With a few single-word events you can lean on the underscore and skip the question. But that's a luxury of small taxonomies. Grow the event count and multi-word names become the norm, not the exception — and the divider stops being optional.
Decision 2: Casing
Purely mechanical, weirdly emotional.
| Style | Example | Pros | Cons |
|---|---|---|---|
| snake_case | checkout_started, checkout_flow_step_started | Machine-friendly; unambiguous; plays nicely with SQL and warehouses | Looks "engineery" in dashboards |
| Title Case | Checkout Started, Checkout Flow Step Started | Reads cleanly in reports and UI; human-facing | Spaces break things downstream; easy to fat-finger inconsistent capitals |
| camelCase | checkoutStarted, checkoutFlowStepStarted | Compact; familiar to JS devs | Harder to scan; word boundaries blur |
| kebab-case | checkout-started, checkout-flow-step-started | Readable; URL-safe | Breaks in tools that read - as subtraction |
There's really no winner here — only a warehouse to please. If your data lands in SQL, snake_case saves you from the day Checkout Started, Checkout started, and checkout started show up as three separate events — same click, three rows, and a number that's quietly wrong.
Decision 3: Tense
Small decision, big consistency cost if you waffle.
| Tense | Example | Notes |
|---|---|---|
| Past tense | Checkout Completed, Checkout Flow Step Completed | An event describes something that already happened. Reads as a fact. |
| Present / imperative | Checkout Complete, Checkout Flow Step Complete | Reads like a command, not a record. Confuses events with actions you want a user to take. |
Nearly settled. Events are history, so past tense matches what an event actually is.
Decision 4: Where meaning lives — name vs. properties
Not a naming style, but it's the decision that most determines whether your taxonomy stays small or explodes.
| Approach | Example | Pros | Cons |
|---|---|---|---|
| Encode in the name | Video Played HD Mobile, Checkout Flow Step Completed Mobile | Dead simple to read one event | Combinatorial explosion; every new dimension multiplies your event count; rigid forever |
| Push into properties | Video Played + {quality, device, duration}, Checkout Flow Step Completed + {step, device} | One event, infinite slices; flexible; tiny event list | Needs properties defined up front; one more thing to govern |
Properties keep your event count flat while your analytical power grows. If you're adding adjectives to an event name, they're almost always properties in disguise.
The habits that beat any convention
Pick whatever lane you want from the menu above. Then protect it with these three habits — they matter more than which lane you picked.
Stay consistent, religiously. The fastest road to a landfill is a second style that "just this once" felt easier. So pick one convention and never quietly break it. Put one person in charge of saying no to off-spec events, or wire a check into CI — whatever it takes so event number 300 looks exactly like event number one.
Pre-define your objects and actions. Before you ship the second event, write the two lists: the objects (Checkout, Payment, Video) and the actions (Started, Completed, Failed). Every new event gets assembled from that vocabulary, never invented on the spot. It's the one move that stops Checkout Started, Checkout Begin, and Checkout Initiated from becoming three events for the same click.
Keep a living document. One file with every event — its name, its properties, and a one-line description of what it actually means.
Not because it's tidy. Because it's the context your AI needs.
The moment you start pulling data points with an AI in the loop — and you will — it has to know that Checkout Started fires on button click, not on page load, and that quality is one of sd | hd | 4k. Without that doc, you're re-explaining your schema every single session. With it, you paste one file and the model already speaks your taxonomy.
A taxonomy nobody writes down is a taxonomy only you can query. So write it down.
What the biggest providers recommend
Before I inflict my own preferences on you, here's where the companies that sell analytics for a living have landed. I read the naming guidance from PostHog, Amplitude, Mixpanel, Heap (now Contentsquare), Statsig, and Segment. The interesting part isn't where they disagree — it's how little they do.
| Provider | Name structure | Casing | Tense | Detail lives in |
|---|---|---|---|---|
| PostHog | category:object_action | snake_case | present (click, submit) | properties |
| Amplitude | object_action | snake_case | past (checkout_completed) | properties |
| Mixpanel | object_action | snake_case | past (song_played) | properties |
| Heap / Contentsquare | location-action (enforced in-app) | your defined list | present (view, click) | properties |
| Statsig | consistent + descriptive (no hard rule) | — | — | properties |
| Segment | Object Action | Title Case events, snake_case properties | past (Order Completed) | properties |
A line on each:
PostHog is the strictest of the bunch: lowercase snake_case, a category:object_action shape, and — unusually — present-tense verbs drawn from a fixed, approved list (click, submit, create). It also tells you to version events (registration_v2:...) and to keep names static.
Amplitude wants object_action in snake_case, past tense, grouped into a product-area hierarchy — with a full governance layer bolted on top: owners, an approval workflow, change logs, deprecation windows.
Mixpanel lands on the same (object) (verb) shape and recommends snake_case specifically because it survives the trip to your warehouse. It hammers one point hardest: push variants into properties (Add to Cart + an item property), never mint a new event per variation.
Heap (now Contentsquare) is the only one that enforces the convention inside the tool: you define the prefixes — by default location-action — and a controlled list of values for each, and new events have to fit. Governance by guardrail.
Statsig stays deliberately high-level: a "comprehensive taxonomy with consistent, descriptive naming," planned against your goals and audited regularly — but it won't pick a casing or structure for you.
Segment is the casing rebel: Title Case for event names, snake_case for properties, an Object + Action framework, past tense (Order Completed). Plus the familiar trio — few core events, rich properties, one tracking plan as the single source of truth.
Strip away the house styles and the same rules keep falling out:
Everyone composes names as object plus action. Nobody argues for action-first, and nobody defends flat descriptive blobs.
Everyone pushes detail into properties instead of the event name — one Video Played with a quality property, never video_played_hd_mobile.
Everyone bans dynamically generated names — static strings only, no purchase_${date}.
And everyone tells you to write it down and govern it — a tracking plan, a style guide, an owner, a review step.
Where they split is exactly the two calls that are pure taste: casing (snake_case for the warehouse-first crowd, Title Case for Segment) and tense (PostHog and Heap lean present; Amplitude, Mixpanel, and Segment lean past). That's the whole thesis of this post in one paragraph — the structure is settled, the styling is preference.
What I actually pick
Everything above is a menu. Here's my preference: the Human-Readable Event Naming taxonomy. My only focus is making event names as readable for humans as possible — so querying and presentation come easy.
Object:Action with a colon divider, Title Case, past tense, and properties carry the nuance.
So: Product:Viewed, Payment:Failed, Video:Played — with quality, device, and duration riding along as properties. And the colon earns its keep the moment a name runs multi-word: Checkout Flow:Step Started stays readable, where Checkout Flow Step Started leaves you guessing where the object ends and the action begins.
The reasoning, fast:
- Title Case, because I read these names all day — in dashboards, in reports, in a query I'm explaining to someone who's never seen the schema.
Checkout:Startedbeatscheckout_startedevery single time. - Object–Action, because at 300 events I want every
Checkout:*to collapse into one tidy block. - The colon divider, because multi-word objects and actions are where naming falls apart.
Checkout Flow Step Startedcould split three ways;Checkout Flow:Step Startednever leaves a doubt about what's the object and what's the action. - Past tense, because an event is a receipt, not an instruction.
- Properties over name-encoding, because I'd rather add one property next quarter than deprecate forty events.
Yes, spaces can annoy your warehouse in some cases. I've decided I care more about the human reading the dashboard than the query that has to wrap a name in quotes. Your call may land differently — that's allowed.
But the load-bearing word is mine. If your team already writes product_viewed in snake_case and does it religiously — don't migrate. A consistent convention you dislike beats a "correct" one applied half the time.
The only real sin is inconsistency. That's the thing that turns a taxonomy into a landfill.
Design your own event taxonomy
Four decisions, one combined result. Toggle the options below and watch the same five events rename themselves in real time — it's the fastest way to feel why some combinations read clean and others fall apart.
Interactive · Event Taxonomy
Try the four naming decisions.
There's no single right answer — only consistency. Change a rule and every event below moves with it.
Live preview
Checkout:Started
- User:Signed UpAccount
- Checkout:StartedConversion
- Payment:FailedConversion
- Product:ViewedProduct
- Video:PlayedMedia
Object·Action · Title Case · past tense · colon