Event Taxonomy 101: The Object-Action Framework
The short version: name every event as object_action, in the past-ish sense of "this thing happened," in snake_case, and put everything variable into properties instead of into the name. That one rule prevents most of the mess I've watched teams accumulate over the years. The rest of this piece is why it works, where it breaks, and how to dig out if your event list already looks like a garage sale.
An event taxonomy is just the agreed-upon set of events and properties your product records, plus the rules for what they're called and how they relate. Amplitude frames it as defining "which events and properties you track, what you name them, and how they fit together." Boring definition. Enormous consequences. Because the taxonomy is the vocabulary your entire company uses to argue about what users do, and a bad vocabulary makes every argument slower.
Why naming is not a bikeshed
People roll their eyes at naming debates. They shouldn't. The cost of getting this wrong is not aesthetic.
Mixpanel published a piece in 2025 on the business impact of data quality, and the opening symptom is painfully familiar: teams stop trusting a tool because the same action shows up as Signup Completed, Signed Up, and user_signup, all at once. Nobody knows which one to chart. So people either build the query three times and average by hand, or they give up and ask an engineer. The same post describes a client who cut their number of priority events by roughly 90% without losing any real analytical power. Ninety percent. That's how much of a typical event list is noise, duplication, and one-off names that never got reused.
Zoom out and the numbers get uncomfortable. Integrate.io's 2026 roundup of data-quality research notes that in 2025 more than a quarter of organizations estimated losses above $5 million a year from poor data quality, and 7% pegged it at $25 million or more. IBM's write-up on the same problem cites a 2025 IBM Institute for Business Value survey where 43% of chief operations officers called data quality their single most significant data priority. Event naming isn't the whole of data quality, obviously. But it's the part product teams actually control, and it's the part that quietly rots first.
Here's the thing that makes taxonomy special: fixing it later is retroactive surgery on data you've already collected. A messy database column you can migrate. A messy event history you mostly can't, because the events already fired with the wrong names and moved on. You end up living with the archaeological layers of every naming decision your team ever made half-awake at 5pm.
The object-action framework
The pattern is object_action: the noun that the behavior happened to, then the behavior. order_completed. video_played. invite_sent. subscription_canceled. Segment's naming-conventions guide and PostHog's best-practices docs both land on this same shape, and there's a real mechanical reason it wins.
Grouping by object is what makes a catalog navigable. When someone types "order" into the event search, they want every order-related event to surface together: order_started, order_completed, order_refunded. Lead with the verb instead (completed_order, canceled_subscription) and related events scatter alphabetically across the list. Small thing at 20 events. Not small at 300.
Object-first also cuts duplicates at the moment of creation, which is where Segment argues the real payoff is. If the convention forces you to ask "what object, then what action," two engineers on two teams instrumenting the same checkout are far more likely to converge on order_completed than they are to independently invent Purchase, checkout_done, and Order Success. The naming decision gets faster because there's a template to fill in, not a blank page.
A few rules that ride along with the pattern:
- snake_case, and pick one case forever. Segment's guide is blunt about why casing matters more than which case you choose: some platforms treat
Order Completedandorder_completedas two different events. Casing drift alone will fragment a metric you thought was one number. I like snake_case because it survives being copy-pasted into SQL, JSON, and URLs without quoting. If your team already lives in Title Case, fine, but then it's Title Case everywhere, no exceptions. - No dynamic values in the name.
signup_2026_07is a trap.plan_upgraded_prois a trap. The date, the plan, the SKU, the button label all belong in properties. Bake a variable into the name and you get an infinite, un-chartable list of near-identical events, and every breakdown you'd want to do is now impossible because the thing you'd group by is welded into the event name. - Keep the action a small, closed vocabulary. This is the sharpest idea PostHog contributes: a restricted verb list. Theirs is roughly
click, submit, create, view, add, invite, update, delete, remove, start, end, cancel, fail, generate, send. You don't need those exact words, but you need a list, agreed once, so that "removed from cart" doesn't becomedeleted,removed, anddroppeddepending on who wrote it.
The tense argument, where the industry genuinely disagrees
Now the fight. The topic notes I was handed say "past tense" (order_completed), and that's the Segment lineage. PostHog's docs explicitly say the opposite: use present-tense verbs, submit and create, not submitted and created.
I'll pick a side. Use past tense: order_completed, not order_complete. An event is a record that something already happened by the time it fires, and the past tense reads that way at every call site and in every chart title. Present tense reads like a command or an intent, which is exactly the ambiguity you don't want when a name has to be self-explanatory two years later to someone who's never met you.
But — and this is the part that matters more than which side you take — the consistency beats the correctness. A taxonomy that's 100% present-tense is fine. A taxonomy that's 100% past-tense is fine. A taxonomy that's 60/40 is the disaster, because now every consumer of the data has to remember which events went which way. Whichever tense you choose, write it down and defend it in code review.
Good names, bad names
Here's the table I actually keep pinned. Left column is what shows up in the wild; right column is the fix and the reason.
| In the wild | Fix | Why |
|---|---|---|
Signup Completed, Signed Up, user_signup |
user_signed_up |
Three names, one action. Pick one shape, kill the rest. |
completed_order |
order_completed |
Object first so all order_* events group together. |
plan_upgraded_pro |
plan_upgraded + property new_plan: "pro" |
Dynamic value belongs in a property, not the name. |
Order Completed and order_completed both firing |
one, in your chosen case | Some tools count these as two events. |
click, click2, button_click_final |
checkout_button_clicked |
Name the object and action, not the debugging session. |
payment_success and payment_failure |
payment_processed + status: "success" | "failure" |
One funnel step, one event, outcome as a property. |
signup_2026_07 |
user_signed_up + signup_date property |
Never encode a date in a name. |
A real-world litmus test: read the name out loud with no context. "Order completed." Clear. "Button click final." Someone was crying when they wrote that.
Events versus properties: the decision rule
The most common instrumentation mistake isn't a bad name. It's creating a new event when you needed a property.
Amplitude's guidance is to use properties to add context "rather than creating separate events for minor variations." The rule I give people is a question: would you ever want to chart these two things on the same line? If yes, they're one event with a property. If the answer is genuinely never, they're separate events.
Take a paywall. paywall_viewed_monthly and paywall_viewed_annual feel like two events. But you'll constantly want "total paywall views" as one number, then break it down by plan. So it's one event, paywall_viewed, with a property plan: "monthly" | "annual". Same logic collapses payment_success and payment_failure into payment_processed with a status property. Outcomes are properties. Steps are events.
Properties get their own light discipline, worth stating because it's cheap and it compounds:
- Booleans read as questions:
is_subscribed,has_seen_upsell. Theis_/has_prefix tells you the value is true or false before you even look. - Dates and timestamps say so:
signup_date,last_seen_timestamp. - Watch cardinality. A property that can take thousands of distinct values, like a raw SKU on a
product_viewedevent firing millions of times, will slow every breakdown that touches it. That data belongs in your warehouse, not a casual dashboard filter.
Get the event-versus-property call right and your whole list stays small. That 90% reduction Mixpanel described? A large chunk of it is just this: someone had been minting an event for every value that should've been a property. Your events describe what happened; your properties describe the flavor of it. The events that survive this discipline are the same ones that feed your metric definitions, which is where a shared vocabulary really earns out — if the whole company can't agree on what an event means, you certainly won't agree on the metrics built on top of it. (For the downstream half of that, the growth-finance metrics glossary is a decent companion, and the north-star metric tree shows how a clean event layer rolls up into something a leadership team can actually steer by.)
Where this goes wrong
Every clean taxonomy I've seen decays in one of three predictable ways.
The rename-in-place. Someone decides order_completed should really be purchase_completed and swaps it in the code. Now you have two events describing the same behavior split across a date boundary, and every trend chart has a cliff on the day of the rename. Renaming an event is almost never worth it. If you must, keep firing both for a transition window and pick a cutover date on purpose, not by accident.
The naming drift under deadline. Q4, a launch, three engineers instrumenting in parallel, and the convention doc nobody reread. You get cart_add, add_to_cart, and AddedToCart in the same feature. This is the failure mode the whole framework exists to prevent, and the only real defense is a schema check in CI or a tracking-plan tool that rejects off-convention names before they ever fire. Mixpanel's Lexicon and Amplitude's Data Planning Playbook both exist precisely because good intentions don't survive a launch week.
The tool that forgives too much. Some platforms are strict about schema and reject anything off-pattern. Others are deliberately loose so you can move fast early. The loose ones are lovely for the first three months and merciless afterward, because they'll happily accept every misspelled, mis-cased, duplicate event you throw at them and hand you the bill in year two. If your tool won't enforce the taxonomy, your CI has to.
Honestly, the tool question is shifting anyway. When your platform lets you ask for "purchases last week by plan" in plain language rather than hunting for the one blessed event name, the exact-string discipline matters a little less than it used to; chat-first tools like Kixo lean on that. It doesn't excuse a messy taxonomy. It just moves some of the pain from "remember the exact name" to "make sure the underlying event actually means one thing."
A migration path when you're already drowning
Say you inherited 400 events and a spreadsheet of confusion. You don't fix this by renaming everything on a Friday. You do it like this:
- Export the full list and sort by volume. The long tail of events firing 3 times a month is not your problem. The top 40 by volume carry almost all your decisions.
- Cluster the duplicates. Group everything that means the same behavior: all the signup variants, all the checkout variants. This is the 90%-reduction moment. Most of your list is the same dozen actions wearing different outfits.
- Define the canonical name for each cluster using
object_action, your chosen case, your chosen tense. Write it in a real tracking plan, not a Slack thread. - Instrument the canonical names alongside the old ones, don't rip the old ones out. Dual-fire for a defined window so your historical charts don't shatter.
- Add a schema gate in CI so no new off-convention name can land. Without this, you'll do the whole migration again in eighteen months. I've watched teams do exactly that, twice, and it's demoralizing every time.
- Deprecate the old names on a scheduled date, communicated in advance, once the canonical ones have enough history to stand on.
It's unglamorous, and it's also the difference between a data set your team trusts and one they route around. The teams that treat their taxonomy like an API contract, versioned and enforced, still trust their numbers at 10 million events a month. The ones who treat it as an afterthought are renaming events on a Friday, wondering why every chart has a cliff in it.
FAQ
Should event names be past or present tense?
Both conventions exist and both work. Segment's lineage uses past tense (order_completed); PostHog's docs recommend present-tense verbs. Pick one, write it down, enforce it. The split is what hurts, not the choice.
How many events should a product track? Fewer than you think. Mixpanel described a client cutting priority events by 90% with no loss of analytical power. Start from the handful of actions that map to your funnel and your north-star metric, and add events when a real question demands one, not preemptively.
When do I make a property instead of a new event?
If you'd ever chart the two things on the same line and break them down, they're one event with a property. Outcomes (success/failure), plans, and variants are almost always properties. Distinct steps in a flow are events.
Can I rename an event after launch? Technically yes, but it splits your history at the rename date and puts a cliff in every trend. Prefer dual-firing the old and new names through a transition window over an in-place swap.