Install any skill in seconds. Free to start, no credit card required.
Get Started Free →PostHog event tracking standards for Supabase Studio. Use when adding useTrack() calls, defining events in packages/common/telemetry-constants.ts, implementing tracking for a new feature, or reviewing PRs for telemetry compliance. Covers event naming, property conventions, approved patterns, and implementation guide.
.claude/skills/supabase-telemetry-standards/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-02 | ✗→✓ | ▲ Improved | 90% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 24% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 26% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 41% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 98% | 0% |
Standards for PostHog event tracking in apps/studio/. Apply these when reviewing PRs that touch tracking or when implementing new tracking.
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:
click_product_card → should be product_card_clickedproductCardClicked → should be product_card_clickedGood examples:
product_card_clickedbackup_button_clickedsql_query_submittedCommon mistakes with corrections:
database_saved → save_button_clicked or database_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 interactionCasing: 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:
label, value, name, dataassistantType in one event, aiType in a related event)dashboard_viewed, sidebar_appeared, page_loaded)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.
Import useTrack from @/lib/telemetry/track (within apps/studio/).
typescriptimport { useTrack } from '@/lib/telemetry/track' const MyComponent = () => { const track = useTrack() const handleClick = () => { track('product_card_clicked', { productType: 'database', planTier: 'pro', source: 'dashboard', }) } return <button onClick={handleClick}>Click me</button> }
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.
usePHFlag, or PostHog-backed hooks such as useDataApiRevokeOnCreateDefaultEnabled): capture the flag value in a relevant track() call.useFlag from common) 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:
typescriptconst 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.
All events must be defined as TypeScript interfaces in packages/common/telemetry-constants.ts:
typescript/** * [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.
When reviewing a PR, flag these as required changes:
[object]_[verb] snake_case, or using an unapproved verb@source/@page descriptions that don't match the actual implementationtrack() call, or there is no outcome tracking for the gated behaviorWhen a PR adds user-facing interactions (buttons, forms, toggles, modals) without tracking, suggest:
[object]_[verb] conventionuseTrack() call with suggested propertiesWhen checking property consistency, search packages/common/telemetry-constants.ts for similar events and verify property names match.
From the actual codebase:
typescript// 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', })
To add tracking for a user action:
[object]_[verb] using approved verbs onlypackages/common/telemetry-constants.ts for similar events and match their property names and casing@group Events and @source JSDoc (plus @page when page-specific), add to the TelemetryEvent union typeimport { useTrack } from '@/lib/telemetry/track', call track('event_name', { properties })[object]_[verb] with approved verb@group Events, @source, and (if page-specific) @pageuseTrack hook| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→fail | 19,230 | 6,855 | -64% | 1 | 1 | 0% | 3,176 | 2,923 | -8% | 0 | 0 | — |
case-02 | fail→pass | 10,948 | 10,613 | -3% | 1 | 1 | 0% | 2,114 | 4,025 | +90% | 0 | 0 | — |
case-03 | fail→pass | 21,685 | 15,777 | -27% | 1 | 1 | 0% | 3,800 | 4,704 | +24% | 0 | 0 | — |
case-04 | pass→fail | 13,451 | 10,383 | -23% | 1 | 1 | 0% | 2,570 | 3,691 | +44% | 0 | 0 | — |
case-05 | pass→pass | 14,603 | 14,849 | +2% | 1 | 1 | 0% | 2,770 | 4,636 | +67% | 0 | 0 | — |
case-06 | fail→pass | 17,410 | 11,231 | -35% | 1 | 1 | 0% | 2,985 | 3,773 | +26% | 0 | 0 | — |
case-07 | fail→pass | 12,176 | 4,387 | -64% | 1 | 1 | 0% | 1,865 | 2,630 | +41% | 0 | 0 | — |
case-08 | fail→pass | 10,068 | 4,882 | -52% | 1 | 1 | 0% | 1,312 | 2,593 | +98% | 0 | 0 | — |
case-09 | fail→pass | 12,766 | 5,397 | -58% | 1 | 1 | 0% | 1,789 | 2,928 | +64% | 0 | 0 | — |
case-10 | fail→pass | 16,367 | 5,177 | -68% | 1 | 1 | 0% | 2,188 | 2,661 | +22% | 0 | 0 | — |
case-11 | fail→pass | 10,152 | 7,941 | -22% | 1 | 1 | 0% | 1,589 | 3,180 | +100% | 0 | 0 | — |
case-12 | pass→pass | 9,718 | 3,067 | -68% | 1 | 1 | 0% | 1,283 | 2,279 | +78% | 0 | 0 | — |
case-13 | pass→pass | 12,620 | 7,439 | -41% | 1 | 1 | 0% | 2,068 | 3,131 | +51% | 0 | 0 | — |
case-14 | pass→pass | 14,335 | 6,278 | -56% | 1 | 1 | 0% | 1,971 | 2,800 | +42% | 0 | 0 | — |
case-15 | fail→pass | 14,790 | 4,761 | -68% | 1 | 1 | 0% | 2,056 | 2,494 | +21% | 0 | 0 | — |
case-16 | fail→pass | 12,788 | 4,911 | -62% | 1 | 1 | 0% | 1,911 | 2,639 | +38% | 0 | 0 | — |
case-17 | pass→fail | 8,359 | 3,016 | -64% | 1 | 1 | 0% | 1,351 | 2,231 | +65% | 0 | 0 | — |
case-18 | pass→pass | 10,033 | 4,020 | -60% | 1 | 1 | 0% | 1,380 | 2,442 | +77% | 0 | 0 | — |
case-19 | pass→pass | 10,876 | 5,879 | -46% | 1 | 1 | 0% | 1,328 | 2,952 | +122% | 0 | 0 | — |
case-20 | pass→pass | 11,828 | 4,644 | -61% | 1 | 1 | 0% | 1,622 | 2,609 | +61% | 0 | 0 | — |
case-21 | pass→pass | 10,061 | 4,808 | -52% | 1 | 1 | 0% | 1,555 | 2,740 | +76% | 0 | 0 | — |
case-22 | pass→pass | 13,328 | 5,506 | -59% | 1 | 1 | 0% | 1,745 | 2,731 | +57% | 0 | 0 | — |
case-23 | fail→pass | 8,824 | 2,699 | -69% | 1 | 1 | 0% | 1,397 | 2,220 | +59% | 0 | 0 | — |
case-24 | fail→pass | 11,362 | 4,972 | -56% | 1 | 1 | 0% | 1,898 | 2,653 | +40% | 0 | 0 | — |
case-25 | pass→pass | 8,643 | 3,817 | -56% | 1 | 1 | 0% | 1,486 | 2,357 | +59% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 25 cases were attempted. The headline lift of +40 percentage points is the difference between those two pass rates over the 25 comparable cases. 2 cases got worse with the skill loaded, and they are included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.