Analytics instrumentation linter
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.
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
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.
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.
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.
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.
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
Send a shown and dismissed pair, so the coachmark can be judged rather than assumed.
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
Register the property before SDK setup completes, or filter installs on the SDK's own app property instead.
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_viewed | paywallViewed |
paywall_view |
| An alarm is created, from the editor | alarm_created | alarmCreated |
alarmEditor_create |
| An onboarding step is finished | onboarding_step_completed | onboardingStepCompleted |
onboardingStep_complete |
| The app comes to the foreground | app_opened | appOpened |
app_open |
| A background calendar sync fails | calendar_sync_failed | calendarSyncFailed |
appCalendarSync_fail |
All three work. The failure mode is never the convention you picked, it is having three of them at once.
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.
onboardingStep_complete splits into exactly two parts, so the name is parseable rather than only readable. Hence camelCase for the location: it keeps a multi-word location in one field._count and has_.app-prefixed location, so there is never a second underscore: app_open, appCalendarSync_fail.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.
If nobody on your team looks at the analytics, none of this costs you anything, and you can safely ignore 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.
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.
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.
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.
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.
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.
It is in development and running against real shipped apps. Join the waitlist and you will get one email when it opens.
No newsletter, no drip sequence, and your address goes nowhere else.