Event Tracking Best Practices: How to Name and Structure Analytics Events

Bad event naming and bloated payloads make analytics painful. These event tracking best practices cover naming conventions, property design, cardinality, and PII hygiene.

JR4 min read

I've reviewed a lot of event streams, and the difference between a team that gets answers from their analytics and a team that drowns in data almost always comes down to a handful of conventions. Here are the event tracking best practices I'd follow from day one.

Use a consistent naming convention#

Name events as noun_verb in past tense, snake_case: user_signed_up, subscription_created, transaction_sent, report_exported. Past tense matters because events describe something that already happened; noun_verb keeps events grouped by the object they act on, which makes scanning an event list much easier than a pile of clicked, clicked_button, button_click variants.

Whatever you pick, write it down and apply it everywhere. Inconsistency is worse than any particular choice.

Prefer properties over new event types#

Don't create purchase_pro and purchase_team events — create one purchase event with a plan property. Analysis gets dramatically easier when related events share a type: you can chart all purchases, then break them down by plan with one click instead of unioning event types together.

The rule of thumb: a new event type for a distinct action, a new property for a variant of an action.

Always include a user identifier#

Every event should carry a stable id for the user who triggered it — your internal user id, in a consistently named field like user_id:

{
  "event": "report_exported",
  "user_id": "user_58231",
  "format": "pdf",
  "rows": 4200
}

Without it you can't build funnels, retention, or per-user behavior analysis — and retrofitting identity later is painful. With GraphJSON, events land in ClickHouse and fields inside the JSON payload are queryable directly, e.g. JSONExtractString(json, 'user_id').

Keep payloads flat and focused#

Log the fields you'll actually analyze or debug with — and resist dumping entire request bodies. Flat, focused payloads are easier to scan in dashboards, easier to chart, and faster to typeahead. (GraphJSON flattens nested JSON by default, but starting flat is still cleaner than relying on it.)

Ten meaningful properties beat a hundred incidental ones every time.

Never log PII or secrets#

No passwords, tokens, API keys, session cookies, or full credit card numbers — ever. Be careful with emails, names, IP addresses, and free-text fields too; they often carry PII you didn't intend. Analytics stores are long-lived and broadly accessible inside a company, so treat everything you log as if it will be read by everyone forever.

If you need to correlate without exposing identity, hash the value or use an opaque id.

Watch your cardinality#

Cardinality is the number of distinct values a field takes. A plan property with 3 values is great to chart and filter on. A request_id property with a million unique values is useless for grouping and expensive to store and index.

Keep high-cardinality values (UUIDs, timestamps, full URLs) out of fields you intend to aggregate. Bucket continuous values where it helps — price_tier: "50_100" charts better than raw cents.

Rename instead of overloading#

If the meaning of an event changes materially, give it a new name rather than silently changing its payload. checkout_completed meaning one thing before March and another after makes every chart that spans March a lie. A new event name (checkout_completed_v2 or a better name entirely) keeps history honest.

Keep a lightweight event catalog#

A single page listing every event, its properties, and who added it prevents the two failure modes I see most: duplicate events (signup vs user_signup) and orphaned events nobody remembers. It doesn't need tooling — a markdown file in your repo, updated in the same PR that adds the event, is enough.

Putting it into practice#

Good conventions compound: consistent names plus clean properties mean your dashboards, funnels, and SQL queries write themselves. If you're setting up tracking in a Next.js app, my guide to Next.js analytics events shows the full flow from fetch call to live chart.

JR

Written by JR

Founder and builder of GraphJSON.