Tracklint
analytics instrumentation linter

Analytics instrumentation linter

Your product data is only as good as the code that sends it.

Tracklint checks the analytics instrumentation in your codebase: the screens shipping with no event tracking, and the event names that drifted apart over a year of releases. It reads your source, because a dashboard can only show you what you remembered to send.

Join the waitlist In development. One email when it opens, nothing else.

A year of shipping, unchecked

alarm_add and alarm_created

review_prompt and review_prompt_requested

app_opened and Application Opened

share_prompt (noun? verb? nobody knows)

One convention, held to

alarm_created one name per moment

review_prompted one concept, one event

app_opened the same name everywhere

share_prompted a verb, past tense

Tracking breaks quietly, and a dashboard cannot tell you

Analytics instrumentation is code, but it is the only code with no failure mode. A missing event does not throw. A misnamed event does not fail a build. Nothing goes red. Your app keeps working, events keep arriving, and the numbers are quietly wrong for months.

It shows up in two shapes. Coverage gaps: a screen ships, the event is forgotten, and nobody notices until someone asks a question the data cannot answer. Naming drift: two names grow around one action, so every funnel built on either one is short in a way that still looks reasonable.

Neither is visible in a product analytics tool, because anything downstream of your code can only report what your code sent. Tracking you forgot is undetectable there by definition. That is why the check belongs next to the code, in review, rather than in a spreadsheet that drifts further from reality with every release.

What it looked like in my own apps

Real event volume from apps I built and shipped myself. Not because I did not know better: because I was shipping fast, much of the code was AI-written and did not hold my conventions, and whenever a dashboard looked odd I was already two features into the next thing.

Two names, and only the code knows the difference

One fires when an alarm is created. The other fires when the editor opens. Nothing in either name tells you which, and I wrote both.

alarm_created5,563
alarm_add5,282

Count "alarms created" with either name, or with both, and the answer looks reasonable and is wrong.

My own analytics project, 365 days to 25 July 2026. Nothing modelled.

What it found in my alarm app

Two from the same scan of Alarm Clock Planner, one of my own apps. Both the kind of thing I would have caught in a review, if a review had been happening.

Add tracking FABNapTipOverlay sends no events at all errorcoverage/dark-screen

The coachmark that teaches people the nap feature. No screen view, no dismissal, nothing. I cannot tell you how many people have seen it, or whether it works.

presented via fullScreenCover in MainTabView.swift:117

Proposed change

Send a shown and dismissed pair, so the coachmark can be judged rather than assumed.

Fix properties Application Installed arrives without app_name errorproperties/missing-super-prop

1,330 installs in 90 days arrived without it, because the SDK captures the install before my code registers the property. Three apps share one project and every query filters on app_name, so my install numbers were excluding every install.

registered after SDK setup, so the first event misses it

Proposed change

Register the property before SDK setup completes, or filter installs on the SDK's own app property instead.

How to name an event so this stops happening

Most of that is a naming problem, and naming problems are solvable with a rule rather than discipline. The same moments, three ways.

The moment snake_case object_action camelCase where_what
Someone sees the paywall paywall_viewedpaywallViewed paywall_view
An alarm is created, from the editor alarm_createdalarmCreated alarmEditor_create
An onboarding step is finished onboarding_step_completedonboardingStepCompleted onboardingStep_complete
The app comes to the foreground app_openedappOpened app_open
A background calendar sync fails calendar_sync_failedcalendarSyncFailed appCalendarSync_fail

All three work. The failure mode is never the convention you picked, it is having three of them at once.

Why the location goes first

An event list is sorted alphabetically. That is the only order you ever see it in.

One flow, as it sorts

onboarding_step_1 1 before 2

onboarding_step_21 and 21 before 5

onboarding_step_5

onboarding_step_complete a verb, mixed in

onboarding_step_viewed

onboarding_steps_done near-duplicate, hiding

The same flow, two events

onboardingStep_view

onboardingStep_complete

properties, not events

flow_step = 5

flow_step_name = "Set first alarm"

Two problems at once: the names do not group, and the step number was never a name. It is a property.

The rest of the rules

viewclickscrollsubmitcreateupdatedeletecompletestartfail

The whole verb list. Ten words, closed on purpose.

Take it and use it without installing anything. If your team has a convention already, keep yours. The point is that something enforces it.

Who this is for

If nobody on your team looks at the analytics, none of this costs you anything, and you can safely ignore it.

Who is building it

I am Emil. I spent eight years in web and digital analytics, then data development and data governance, at a bank. A lot of that job was working out why two numbers that should match do not. I also make iOS apps.

Knowing the rules was never the hard part. Holding them while shipping at speed is. I build fast, much of the code is AI-written, and that produces instrumentation which looks correct and quietly is not: a plausible event name off the convention, a screen wired up with the tracking left out. I would spot something odd in a dashboard, make a mental note, and never get back to it, because there was always another feature or another app.

The problem was not expertise, it was that nothing enforced it at the moment the code was written. That is what a linter is for. Nobody argues you can skip the type checker because you know what the types are.

If you have an existing event taxonomy that needs untangling, or you want a naming convention set up properly for your product, that is work I can help with directly. The tool is one way in. A conversation is the other.

Questions

What is analytics instrumentation?

The code in your app that sends analytics events: the calls, the event names they use, and the properties they attach. It sits between what a user does and what your product analytics tool records. Developers write it, which is exactly why it drifts from whatever plan the rest of the team believes exists.

Why do event names become inconsistent?

Because nothing checks them. Every developer picks a verb, and add, create, new and save all end up meaning the same thing. No build fails. The names accumulate release after release until two of them cover one action, and every count built on either one is short.

How do you find screens with missing tracking?

Not in a dashboard. A dashboard only shows what was sent, so tracking you forgot is invisible there by definition. The only way to find it is to compare the screens that exist in your code against the events your code actually sends.

Should events use snake_case or camelCase?

Either, as long as one is applied consistently. snake_case object_action is what most product analytics tools assume; camelCase suits teams already writing everything else that way. The damage comes from mixing them, or from an open-ended verb list where add, create, new and save all mean the same thing. Pick one, close the verb list, and keep detail in properties rather than names.

Should the convention apply to properties too?

No, for a practical reason rather than a stylistic one. Most properties on an event come from the SDK, not from you, and they are multi-word snake_case whatever you do: on one real event of mine, 43 of 71. A convention you can apply to a third of a namespace produces two conventions, not consistency. Properties also become warehouse columns, where some engines lowercase unquoted identifiers and destroy the word boundary you were relying on. So properties get looser rules: one casing, a subject prefix so related ones group, no synonyms, predictable shapes like _count.

When can I use it?

It is in development and running against real shipped apps. Join the waitlist and you will get one email when it opens.