Telemetry Standards for Supabase Studio
Standards for PostHog event tracking in apps/studio/. Apply these when
reviewing PRs that touch tracking or when implementing new tracking.
Event Naming
Format: [object]_[verb] in snake_case
Approved verbs only (canonical list — derived from packages/common/telemetry-constants.ts):
opened, clicked, submitted, created, removed, updated, intended, evaluated, added,
enabled, disabled, copied, exposed, failed, converted, closed, completed, applied, sent, moved
Flag these:
- Unapproved verbs (saved, viewed, seen, pressed, etc.)
- Wrong order:
click_product_card→ should beproduct_card_clicked - Wrong casing:
productCardClicked→ should beproduct_card_clicked
Good examples:
product_card_clickedbackup_button_clickedsql_query_submitted
Common mistakes with corrections:
database_saved→save_button_clickedordatabase_updated(unapproved verb)click_backup_button→backup_button_clicked(wrong order)dashboardViewed→ don't track passive views on page loadcomponent_rendered→ don't track — no user interaction
Property Standards
Casing: camelCase preferred for new events. The codebase has existing snake_case properties (e.g., schema_name, table_name) — when adding properties to an existing event, match its established convention.
Names must be self-explanatory:
{ productType: 'database', planTier: 'pro' }{ assistantType: 'sql', suggestionType: 'optimization' }
Flag these:
- Generic names:
label,value,name,data - PascalCase properties
- Inconsistent names across similar events (e.g.,
assistantTypein one event,aiTypein a related event) - Mixing camelCase and snake_case within the same event
What NOT to Track
- Passive views/renders on page load (
dashboard_viewed,sidebar_appeared,page_loaded) - Component appearances without user interaction
- Generic "viewed" or "seen" events — already captured by pageview events
DO track: user clicks, form submissions, explicit opens/closes, user-initiated actions.
Exception: _exposed events for A/B experiment exposure tracking are valid even though they fire on render.
Never track PII (emails, names, IPs, etc.) in event properties.
Required Pattern
Import useTrack from @/lib/telemetry/track (within apps/studio/).
import { useTrack } from '@/lib/telemetry/track'
const MyComponent = () => {
const track = useTrack()
const handleClick = () => {
track('product_card_clicked', {
productType: 'database',
planTier: 'pro',
source: 'dashboard',
})
}
return <button me</button>
}
Feature Flag Measurement
A feature flag that gates behavior needs telemetry on both the flag state and how users respond to the new behavior (toggle clicks, opt-in actions), so the rollout can be measured.
- PostHog flags (
usePHFlag, or PostHog-backed hooks such asuseDataApiRevokeOnCreateDefaultEnabled): capture the flag value in a relevanttrack()call. - ConfigCat flags (
useFlagfromcommon) are a different system — this pattern does not apply to them.
usePHFlag returns undefined while the PostHog store is still loading. Read the raw flag via usePHFlag('flagName'), not through wrapper hooks that coerce undefined to false, and use a conditional spread so the property is omitted (not false) until the flag has resolved:
As always, track() runs inside the user-action handler — never in the component body or an effect:
const track = useTrack()
const flagValue = usePHFlag<boolean>('myBooleanFlag') // for boolean flags
const handleSubmit = () => {
track('event_name', {
...(flagValue !== undefined && { myFlagEnabled: flagValue }),
})
}
For string-valued flags (e.g. experiment variants), use usePHFlag<string>('flagName'); a flag that may be migrated from boolean to multivariate is typed usePHFlag<boolean | string>. ProjectCreationForm.tsx (dataApiRevokeOnCreateDefault) is the canonical example.
Event Definitions
All events must be defined as TypeScript interfaces in packages/common/telemetry-constants.ts:
/**
* [Event description]
*
* @group Events
* @source [what triggers this event]
*/
export interface MyFeatureClickedEvent {
action: 'my_feature_clicked'
properties: {
/** Description of property */
featureType: string
}
groups: TelemetryGroups
}
Add the new interface to the TelemetryEvent union type so useTrack picks it up.
@group Events and @source are required on every event; add @page when the event fires from a specific page. All three must be accurate.
Review Rules
When reviewing a PR, flag these as required changes:
- Naming violations — event not following
[object]_[verb]snake_case, or using an unapproved verb - Property violations — not camelCase, generic names, or inconsistent with similar events
- Unnecessary view tracking — events that fire on page load without user interaction
- Inaccurate docs —
@source/@pagedescriptions that don't match the actual implementation - Unmeasured feature flags — a PostHog flag gates new behavior but its value is not captured in any
track()call, or there is no outcome tracking for the gated behavior
When a PR adds user-facing interactions (buttons, forms, toggles, modals) without tracking, suggest:
- "This adds a user interaction that may benefit from tracking."
- Propose the event name following
[object]_[verb]convention - Propose the
useTrack()call with suggested properties
When checking property consistency, search packages/common/telemetry-constants.ts for similar events and verify property names match.
Well-Formed Event Examples
From the actual codebase:
// User copies a connection string
track('connection_string_copied', {
connectionType: 'psql',
connectionMethod: 'transaction_pooler',
connectionTab: 'Connection String',
})
// User enables a feature preview
track('feature_preview_enabled', {
feature: 'realtime_inspector',
})
// User clicks a banner CTA
track('index_advisor_banner_dismiss_button_clicked')
// Experiment exposure (fires on render — valid exception)
track('home_new_experiment_exposed', {
variant: 'treatment',
})
Implementing New Tracking
To add tracking for a user action:
- Name the event —
[object]_[verb]using approved verbs only - Choose properties — camelCase preferred for new events; check
packages/common/telemetry-constants.tsfor similar events and match their property names and casing - Add interface to telemetry-constants.ts — with
@group Eventsand@sourceJSDoc (plus@pagewhen page-specific), add to theTelemetryEventunion type - Add to component —
import { useTrack } from '@/lib/telemetry/track', calltrack('event_name', { properties })
Verification checklist
- Event name follows
[object]_[verb]with approved verb - Event name is snake_case
- Properties are camelCase and self-explanatory
- Event defined in telemetry-constants.ts with accurate
@group Events,@source, and (if page-specific)@page - Using the
useTrackhook - Not tracking passive views/appearances
- No PII in event properties (emails, names, IPs, etc.)
- Property names consistent with similar events