A portable, typed YAML format for declaring product analytics events — what events your app fires, what they carry, what each one means, which experiments they may be exposed to (since 0.2), and what derived metrics they roll up into (since 0.3). Codegen, validation, and agents read this file to understand your event schema without grepping source code.
This package contains:
- The format specification (
SPEC.md) — what anevent-schema.yamllooks like. - A meta-schema (
meta-schema.json) — JSON Schema validating event schemas against the spec. - The reference CLI —
validateandgeneratecommands. - Examples (
examples/) — input YAML + generated TypeScript for the minimal case, a typical SaaS surface, an ecommerce surface with the money type, a schema with experiments declared, and one with derived metrics.
# One-off
npx @clamp-sh/event-schema generate
# Or as a dev dependency
npm install --save-dev @clamp-sh/event-schema
npx event-schema generateAuto-discovers event-schema.yaml from the current directory or any parent and writes the generated types alongside it. See Commands to point elsewhere.
event-schema.yaml:
version: "0.2"
events:
signup_completed:
intent: |
Primary conversion event — account creation succeeded.
properties:
plan:
type: enum
values: [free, pro, growth]
required: true
method:
type: enum
values: [email, github]
required: true
cta_click:
intent: |
Top-of-funnel engagement signal — which CTA earned the click.
properties:
location:
type: string
required: true
examples: [hero_primary, nav_signup, final_cta]
destination:
type: string
required: trueGenerate types:
npx event-schema generateWrites event-schema.d.ts next to the source.
Use them in your code:
import { track } from "@clamp-sh/analytics";
import type { AnalyticsEvents } from "./event-schema";
track<AnalyticsEvents>("signup_completed", { plan: "pro", method: "email" });
// ^ event name + properties type-checked at compile timeAny analytics SDK that exposes a generic on its track function works the same way.
Since 0.2, schemas can declare A/B tests in a top-level experiments: section. Codegen emits typed names and per-experiment variant unions so exposure-event tracking is type-checked the same way regular events are.
experiments:
pricing_redesign_2026:
intent: |
Long-form copy vs short-form with an embedded video on /pricing.
variants: [control, long_copy, short_video]
started: "2026-04-15"Generated:
export type ExperimentName = "pricing_redesign_2026"
export type Variant<E extends ExperimentName> = {
pricing_redesign_2026: "control" | "long_copy" | "short_video"
}[E]Wire those into whichever exposure-event shape your platform uses ($exposure on Amplitude, $experiment_started on Mixpanel, your own canonical event). The spec stays out of runtime concerns: assignment, randomization, holdouts, and result analysis are the experimentation platform's responsibility. See examples/experiments/ for a complete schema with both events and experiments declared.
Since 0.3, schemas can declare derived metrics in a top-level metrics: section — the questions you ask of the event stream, named once and shared across every consumer (codegen, agents, dashboards). Two types: count and ratio.
metrics:
daily_signups:
type: count
intent: New signups per day. Top-of-funnel volume.
event: signup_completed
grain: day
weekly_signup_to_paid_cvr:
type: ratio
intent: % of weekly signups that converted to paid.
grain: week
numerator: { event: subscription_started }
denominator: { event: signup_completed }Generated:
export type MetricName = "daily_signups" | "weekly_signup_to_paid_cvr"
export type MetricSpec<M extends MetricName> = {
daily_signups: { type: "count"; grain: "day" }
weekly_signup_to_paid_cvr: { type: "ratio"; grain: "week" }
}[M]The spec defines the metric; how to compute it at query time is left to the consuming tool. Cohort-windowed metrics ("of users who fired X within 30 days, how many fired Y?") are deliberately out of 0.3 — they need user-journey logic that's a heavier addition. See examples/metrics/ for a full schema with derived metrics.
event-schema validate
event-schema generate
validate checks the schema against the spec's meta-schema. Exits non-zero on errors.
generate emits a TypeScript declaration. Writes event-schema.d.ts next to the source by default.
Both commands auto-discover event-schema.yaml (or .json) by walking up from the current directory. Pass an explicit path to override: event-schema generate path/to/other.yaml.
| Flag | What it does | Default |
|---|---|---|
-o, --output <path> |
Write to a different path. Pass - to write to stdout. |
event-schema.d.ts next to the source |
--type-name <name> |
Name of the exported events type | AnalyticsEvents |
Most product analytics tools treat event schema as tribal knowledge. Property names live in code comments, internal wikis, or Slack threads. AI agents asking "what does cta_click carry?" have to grep your codebase. Renames mean manual sweeps across hundreds of call sites.
A declarative event-schema file fixes all three:
- Discoverable — agents and humans read the file instead of grepping.
- Refactor-safe — rename a property in one place, regenerate types, TypeScript surfaces every broken call site.
- Durable — explanations of why each event exists survive past the conversation that produced them.
Spec version 0.3. The format is in active dogfood; the design may change before reaching 1.0. The reference CLI implements the full spec.
This is the reference implementation. Other tools are encouraged to implement the spec — open an issue to be listed.
MIT.