Skip to content
Data
Skill

/event-schema-author

Author and maintain an event-schema.yaml file. A portable, typed declaration of every product analytics event the codebase fires (event names, properties, types, intent). The CLI generates a TypeScript type from it so tracking calls are autocompleted and type-checked at build

From plugin
analytics-skills
813 skills
Install
$ npx -y skills add clamp-sh/analytics-skills --skill event-schema-author --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/event-schema-author

Context preview

The summary Claude sees to decide when to auto-load this skill.

Author and maintain an event-schema.yaml file. A portable, typed declaration of every product analytics event the codebase fires (event names, properties, types, intent). The CLI generates a TypeScript type from it so tracking calls are autocompleted and type-checked at build

SKILL.md

event-schema-author.SKILL.md
name: event-schema-author
description: Author and maintain an event-schema.yaml file. A portable, typed declaration of every product analytics event the codebase fires (event names, properties, types, intent). The CLI generates a TypeScript type from it so tracking calls are autocompleted and type-checked at build time. Vendor-neutral; works with any analytics SDK (Clamp, GA4, Mixpanel, Amplitude, PostHog, Segment).
when_to_use: When the user says "declare my events", "set up typed events", "make tracking type-safe", "audit what we're tracking", "stop typo'd event names", or equivalent. Also a natural follow-up to `analytics-profile-setup` for any project that has more than a handful of `track()` calls.

Event schema author

A repo without a declared event schema is one where the source of truth for "what events do we fire and what do they carry" is grep. That breaks two things: (1) typo'd event names ship to production and silently fragment your data; (2) AI agents and new teammates have to reverse-engineer intent from call sites.

This skill produces (or updates) `event-schema.yaml` at the repo root, runs the CLI to generate a TypeScript type, and wires it into the tracking call sites. The format is an open spec ([clamp-sh/event-schema](https://github.com/clamp-sh/event-schema)), and the generated type works with any analytics SDK.

When NOT to run this

  • The codebase has zero `track()`-style calls. There's nothing to declare. Nudge the user to instrument first.
  • The user fires exactly one event. Hand-rolling a 3-line `type Events = { ... }` at the call site is fine. Schema is for projects with growth ahead, not toy projects.
  • The codebase already has an `event-schema.yaml` and the user is asking a different question (e.g. "what should I track next?"). Don't re-author from scratch; answer the actual question.

Method

Phase 1. Discover what's already tracked

Before writing anything, build a picture of the existing tracking surface. Run searches that catch the common patterns:

# Generic; most analytics SDKs expose a track() function
rg -n "track\(" --type ts --type tsx --type js --type jsx

# Common SDK-specific shapes (broaden as needed)
rg -n "analytics\.track\(|posthog\.capture\(|mixpanel\.track\(|amplitude\.track\(|gtag\(|window\._mtm" --type ts --type tsx --type js

Build a table in scratch:

| Event name | Call sites | Properties seen | Required? (in every call) | |---|---|---|---| | `signup` | 2 | `plan`, `source` | both | | `cta_click` | 5 | `location`, `destination`, `variant?` | first two only |

Two important judgments here:

  • **Required vs optional**: a property is *required* only if every call site passes it. If 4/5 do, it's optional with examples. Don't mark something required that the codebase doesn't actually guarantee.
  • **Property type**: infer from the observed values. `"pro"` is a `string`. `5` is a `number`. `{ amount, currency }` is `money`. A small, fixed set of values like `"free" | "pro" | "growth"` is an `enum`. Don't invent enum values you didn't see.

Also check for an existing schema file (don't overwrite blindly):

ls event-schema.yaml event-schema.json 2>/dev/null

If one exists, treat this as an *update* pass (Phase 2 reads from it instead of starting blank). When the user's intent is "audit our tracking" rather than "declare new events", the audit *is* the run; everything below applies, but the diff in the next paragraph is the headline output, not a side-effect.

If Clamp MCP is connected, also call `events.observed_schema` for the project; it returns what's *actually firing into ingest* with per-property type observations. Three kinds of drift surface immediately by diffing the observed signature against the local YAML:

  • Events declared in the YAML but missing from the observed signature → dead instrumentation. Either remove the declaration, or find the regression that stopped firing it.
  • Events firing but not declared → unauthored events. Either declare them (most common), or find the SDK call site that sneaked in.
  • Properties on a declared event where `observed.properties[key].length > 1` → silent type drift. One call site is sending the wrong type (e.g. `count: "5"` as string instead of `count: 5` as number). Fix at the call site, or relax the schema if the inconsistency is intentional.

Drift detection is most useful as a periodic check, not just on initial authoring. Worth flagging to the user: "want to re-run drift detection every month?"

Phase 2. Draft the schema

Group events into the YAML shape. The format is small: one `version`, a map of `events`, each with optional `intent` and required `properties`:

version: "0.1"  # the version string MUST be quoted; unquoted 0.1 parses as a number

events:
  signup:
    intent: |
      Account creation succeeded. Numerator of every funnel that ends at "real user".
    properties:
      plan:
        type: enum
        values: [free, pro, growth]
        required: true
      method:
        type: enum
        values: [email, github, google]
        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: true

Property types: `string`, `number`, `boolean`, `enum` (with `values: [...]`), `money` (a `{ amount, currency }` pair). Each property may also declare `description` (carried into the generated JSDoc) and `examples: [...]` (sample values, surfaced as `@example`, purely informational).

Phase 3. The intent question (do not skip)

You can infer almost everything from the codebase except **intent**. Intent is one sentence on what the event is *for*: the decision it informs, the funnel it belongs to, why it exists. Without it, the schema is just a typed dictionary; with it, the schema is documentation a new

Read more
Ships withanalytics-skills

Analytics skills for Claude, Cursor, and other AI agents. Read web analytics like a senior analyst: diagnose traffic changes, judge channel quality, read funnels, declare typed events, and read A/B tests without the usual rookie mistakes.

Get the whole plugin

Other skills on analytics-skills.