| name | event-schemas |
| description | Define versioned, validated analytics-event schemas with type-safe wrappers so clients and warehouses agree on shape. Use when formalizing or migrating an existing tracking plan. |
Versioned Event Schemas
Instructions
Events evolve. Without a schema and versioning, every analytics downstream becomes a minefield of optional fields and silent breaking changes. Treat events like API contracts: schemas, validation, typed wrappers, semver.
1. Schema Format
Use JSON Schema (draft 2020-12) or Protobuf. JSON Schema is more readable for data teams; Protobuf is more robust if your pipeline already speaks it. Example JSON Schema entry:
events:
checkout_started:
version: 2
owner: growth
pii: false
description: "User started the checkout flow."
properties:
cart_value_cents:
type: integer
minimum: 0
currency:
type: string
enum: [USD, EUR, GBP, JPY]
item_count:
type: integer
minimum: 1
source:
type: string
enum: [cart, deeplink, push, widget]
shipping_method:
type: string
enum: [standard, express, pickup]
required: [cart_value_cents, currency, item_count, source]
additionalProperties: false
2. Versioning Rules
- Additive changes (new optional property, new enum value not strictly required): bump patch.
- Required additions or property type changes: bump major; emit under a new event name or with a
schema_version property. Keep the old version valid for at least one release cycle.
- Every event carries
schema_version so the warehouse can route to the correct view.
Never silently drop a property; mark it deprecated: true in the schema and keep accepting it at ingest for one cycle before removal.
3. Validation Pipeline
Three validation boundaries:
- Compile-time (client): typed wrappers generated from the registry (see
product-analytics). Typos or missing required fields fail the build.
- Runtime (client): a lightweight validator in debug builds asserts the event matches the schema; in release it logs a meta-event
analytics_invalid_event and drops the event.
- Ingest (server): full JSON Schema validation at the collector; invalid events go to a dead-letter topic and fire a ticket-level alert.
fun track(event: AnalyticsEvent) {
if (BuildConfig.DEBUG) require(SchemaValidator.validate(event.name, event.properties)) {
"Invalid analytics event: ${event.name}"
}
sink.enqueue(event)
}
4. Code Generation
Generate wrappers per platform from the single analytics-registry.yaml:
- Kotlin: KSP or a small Gradle task emits sealed classes into
generated/analytics.
- Swift: a
Sources/AnalyticsGenerated/ folder produced via a SwiftPM plugin.
- Dart: a
build_runner generator producing lib/analytics/generated/events.dart.
- TypeScript:
json-schema-to-typescript or quicktype emits src/analytics/generated.ts.
Generated code is checked in so reviews can see diffs, and is regenerated in CI so drift fails the build.
5. Enum Governance
- Enum values are registry-owned. Adding a new value requires a registry PR with an owner and a description.
- A forbidden enum value (e.g.
source="unknown" when unexpected) must come from a shared UnknownEnum policy: either log and drop, or coerce to a sentinel other and open a ticket.
- Warehouse dashboards filter known enum values only; unknown values go to a monitoring query.
6. Nested Data
- Avoid nested objects in analytics events -- most warehouses want flat columns.
- When you must nest (e.g.
items[]), keep it shallow and document the cardinality cap (maxItems: 10). Emit a summary event first (checkout_started with item_count) and a per-item event (cart_item_added) if granularity is needed.
- Never put arrays of PII (e.g.
phone_numbers: []).
7. Schema Registry and CI
jobs:
validate-analytics:
steps:
- run: bun run generate:analytics
- run: git diff --exit-code
- run: bun run validate:registry
- run: bun run lint:callsites
8. Type-Safe Wrappers (Reminder)
From product-analytics, reject any emission that doesn't go through the typed wrapper. Add a custom lint rule:
- Kotlin:
@RequiresAnalyticsWrapper + Detekt rule that bans direct mixpanel.track("...").
- Swift: SwiftLint custom rule forbidding
Analytics.shared.track(.
- Dart: custom analyzer plugin.
- TS: ESLint rule banning
segment.track.
9. Deprecation Workflow
- Mark event/property deprecated in registry with
deprecated_in: "2026.04".
- Generated wrappers emit
@Deprecated / @deprecated / // ignore: deprecated_member_use.
- Warehouse queries migrate to the replacement.
- After one release cycle, remove the deprecated property from the registry; the wrappers drop it; the ingest schema stops accepting it.
10. Disaster Drills
- Quarterly: purposely break the registry (remove a required field) and verify the typed wrappers fail the build and the ingest validator drops events into the dead-letter topic.
- Verify the dead-letter topic has an alert and a runbook.
Checklist