ScaleBun
Skip to article

Event naming & tracking plan

webDeveloper

How to name events, which names the SDK reserves, and how to enforce a typed tracking plan on the client before bad data reaches your warehouse.

Updated Reviewed

The names you choose become your analytics schema, and they are far harder to change later than they are to get right now.

$ is reserved#

Every event the SDK emits itself begins with $. Tracking one of those names yourself collides with the SDK's own event and corrupts both.

The complete list is generated from the source — see the reserved events reference. It covers, broadly:

GroupExamples
Identity$identify · $group · $alias
Autocapture$pageview · $pageleave · $autocapture · $submit · $change · $impression · $outbound_link · $download
Signals$rageclick · $deadclick · $exception · $web_vital · $exit_intent
Forms$form_start · $form_abandon · $form_error
Flags$feature_flag_called
Push$push_opened
Inboxeleven $message_* events
Coachmarkstwelve $coachmark_* events

Internal $ events are never governed by a tracking plan — they are the SDK's, and it is responsible for them.

Naming your own#

Pick one convention and hold it. The SDK does not enforce a style, so consistency has to come from you:

  • object_action, past tensecart_viewed, checkout_started, subscription_cancelled. Reads well when the list is sorted alphabetically, because everything about carts groups together.

  • Lower snake case. Mixed casing produces two events that look identical in a dashboard and are not.

  • Properties for variants, not names. plan_upgraded with { from: 'free', to: 'pro' }, never free_to_pro_upgraded — otherwise every new plan needs a new event and every funnel needs rewriting.

  • Do not put ids in names. They belong in properties.

Web and mobile do not share names#

This is the trap that costs the most time. The web SDK emits $pageview; the mobile SDK emits screen_viewed. The vocabularies are genuinely different, because the platforms are.

Anything that matches on a literal event name is therefore platform-specific. Cross-platform analysis goes through the backend's event-concept map, which is what makes "a view" mean the same thing on both — a dashboard query that hardcodes one platform's name silently reports zero for the other.

Tracking plan#

A typed plan is enforced on the client, before the event is sent, so bad data never leaves the browser:

TypeScript
await ScaleBun.init({  clientKey,  apiBaseUrl,  analytics: {    plan: {      events: {        checkout_started: {          required: ['cart_value', 'currency'],          props: { cart_value: 'number', currency: 'string', coupon: 'string' },        },      },      allowUnknownEvents: true,   // default — warn, never drop      strict: false,              // set true to DROP violations    },  },});

The plan catches three things: an event not declared in events, a missing required property, and a property whose type does not match.

ModeBehaviour
DefaultWarns in the console. Nothing is dropped.
strict: trueDrops the violating event from both lanes.
allowUnknownEvents: falseTreats an undeclared event as a violation.

Super-properties and identity#

Properties registered with register() are merged into every subsequent event, and call-site properties win on conflict. They are persisted, so they survive a reload — and they are cleared by reset(), which you should call on logout so the next user's events are not attributed to the previous one.

TypeScript
ScaleBun.register({ plan_tier: 'enterprise' });ScaleBun.unregister('plan_tier');

Flag exposures are stamped the same way: reading a flag registers $feature/<key>, so every downstream event carries the variant a user saw without a join.

Next#

Event naming & tracking plan · Web SDK · ScaleBun