---
name: event-tracking-plan
description: Designs an event tracking plan as a versioned data contract for product and web analytics, so an AI agent names events and properties consistently, types every property, defines identity and consent, and maps each event to a downstream warehouse table. Use when the request mentions an event tracking plan, event taxonomy, data dictionary, event schema, naming conventions for events, "what events should we track", a dataLayer or event spec, Segment Protocols, Amplitude or Mixpanel taxonomy, Avo, Snowplow schemas, or cleaning up duplicate or inconsistent event names. Do NOT use for choosing which events support growth metrics (use the marketing tracking-plan skill), ad-platform conversion tracking (conversion-tracking-capi), campaign URL tagging (utm-campaign-tracking), analysing collected data (funnel-analysis, cohort-retention-analysis), or validating that live tracking fires correctly (analytics-qa-validation).
license: CC BY 4.0. Free to use and adapt with credit to Afeez Adebayo.
metadata:
  author: Afeez Adebayo
  website: https://afeezadebayo.com
  linkedin: https://www.linkedin.com/in/afeez-adebayo/
  source: https://afeezadebayo.com/skills-md/data/analytics-tracking/event-tracking-plan/
  category: data
  subcategory: analytics-tracking
  version: 1.0.0
  created: 5 November 2026
  updated: 5 November 2026
---

# Event tracking plan

An event tracking plan is the contract between the people who fire events and the people who analyse them. When that contract is missing, the same action gets logged as `signup`, `sign_up`, `Signed Up` and `user_registration`, properties change type between releases, and every funnel quietly drops the rows that do not match. This skill makes an AI agent design the plan as a single, versioned, machine-readable specification, so events are named once, every property is typed, identity is defined, and each event has a known home in the warehouse.

It treats the plan as data engineering, not a wishlist. The output is a spec an agent and a human can both read: event name, when it fires, who owns it, each property with a type and allowed values, the identity scope, the consent class, and the downstream table. Analytics platforms treat two spellings or two casings of a name as two different events, so the plan's job is to make one decision per name and hold it.

## When to use this skill, and when not to

Use this skill to design a new tracking plan, to audit and consolidate a messy one, to write a data contract or event schema, to set naming conventions, or to prepare a spec for Segment Protocols, Amplitude, Mixpanel, Avo or Snowplow. It fires on an event taxonomy, a data dictionary, a dataLayer spec, duplicate or inconsistent event names, or "what should we track".

Do not use it to decide which events a growth team should prioritise against a north star metric, which is the marketing `tracking-plan` skill. Do not use it for ad-platform conversions and the Conversions API, which is `conversion-tracking-capi`. Do not use it to tag campaign links, which is the sibling `utm-campaign-tracking`. Do not use it to analyse the data once collected, which is `funnel-analysis` or `cohort-retention-analysis`. Do not use it to confirm that live tracking fires correctly in a browser or in production, which is the sibling `analytics-qa-validation`. When a request spans two of these, design the plan here and hand off the rest by name.

## Inputs the agent must have before starting

Ask for these in one message, and do not invent them:

1. The destinations the data flows to, in order: the collection tool (GA4, Segment, Amplitude, Mixpanel, Snowplow, RudderStack) and the warehouse or lake if there is one. Casing and limits depend on this.
2. The product and the decisions the data must support, in plain words. A plan serves questions, not completeness.
3. Any existing events and their current names, so consolidation can be grounded rather than guessed. A live export beats a description.
4. Who owns instrumentation (usually data or analytics engineering) and how changes get shipped.
5. Whether the business is B2B, because that adds a group or account identity.
6. The consent and privacy regime, so each property can be classed and no personal data is written where it should not be.

If the destination is unknown, stop and ask: the single most consequential rule in the plan, casing, cannot be set without it.

## Workflow

### Step 1. Fix the naming grammar before naming anything

Choose one grammar and one casing, write them at the top of the plan, and apply them to every event without exception.

- Grammar: `object` then `past-tense action`, from the user's point of view. `order_completed`, not `complete_order` or `order` or `checkout_success`. Describe what happened, not what the system did (`message_sent`, not `message_delivered`), unless the system event is the thing you measure.
- Casing: pick one per destination and never mix, because every major tool treats `Sign Up`, `sign_up` and `signUp` as three distinct events. GA4 requires `snake_case` for event and parameter names. Segment's convention is Title Case event names with `snake_case` properties. Amplitude's default is Title Case. Mixpanel, Snowplow and warehouse-first stacks favour `snake_case` throughout. If the data lands in a warehouse, prefer `snake_case` end to end, because it survives SQL without quoting. Record the decision as a one-line rule.
- One concept, one name, everywhere. If `order_id` identifies an order on one event, it is `order_id` on every event, never `orderId` or `transaction_id` elsewhere.
- Distinct concepts get distinct names. `login_method` and `payment_method` are not both `method`; `item_type` and `payment_type` are not both `type`.

### Step 2. Choose the events, and prefer standard events

Track the actions tied to the decisions from the inputs, not every click. Reach for standard events first, because they switch on built-in reports.

- In GA4, use the recommended events where one fits: `login`, `sign_up`, `search` (which needs a `search_term` parameter), `select_content`, `share`, and the ecommerce set (`add_to_cart`, `purchase` and so on). Ecommerce events need `currency`, `value` and an `items` array, each item carrying `item_id` or `item_name`, with `value` equal to the sum of price times quantity, excluding shipping and tax. Verify the current recommended-event list and its required parameters in the GA4 docs before relying on specific names.
- In Segment, reuse the ecommerce spec reserved names such as `Order Completed` and `Product Added`.
- Keep events generic and let properties carry the detail. One `page_viewed` with a `page_name` property beats `home_page_viewed` and `pricing_page_viewed` as separate events. One `error_encountered` with an `error_type` beats an event per error.
- A/B test assignment is a user property, not an event.

### Step 3. Type every property and define allowed values

A property without a type is a future bug. For each property set four things: name, type, whether it is required, and the allowed values or range.

- IDs are strings, always. `"12345"`, not `12345`, so a leading zero or a non-numeric id never breaks the column.
- Counts and amounts are numbers; flags are booleans; enumerations are strings with a listed set of allowed values.
- Timestamps are ISO 8601 strings, and time-of-event properties use an `_at` suffix (`created_at`, `completed_at`).
- Include units in the name: `price_usd`, `video_duration_seconds`, `file_size_bytes`. A bare `duration` is unreadable six months later.
- Decide null handling once and apply it to every property: either omit the key when there is no value, or send an explicit `null`, or send a single sentinel such as `"unknown"`. Never mix the three. Inconsistent null handling is one of the most common causes of broken filters and funnels.
- Never generate property keys dynamically. `feature_1`, `feature_2`, `feature_3` each become a new warehouse column; use one `feature_name` property with multiple events or an array instead.
- Classify each property for privacy: is it personal data, pseudonymous, or neutral. Personal data such as email, full name, card number or raw IP does not belong in event properties; use a hashed or opaque id and record the class in the plan.

### Step 4. Write the identity contract

Identity is part of the plan, not an afterthought, because a broken identity join silently splits one person into many.

- Anonymous visitors carry only an anonymous or device id. Never set a `user_id` before the person authenticates.
- On sign-up or login, issue the identify-and-merge call so the pre-login anonymous activity joins to the stable `user_id`.
- Use the same `user_id` across devices, and reset it on logout so the next user on a shared device is not merged in.
- For server-side events, include a deduplication id (an `insert_id`, or a shared key such as `order_id` used by both browser and server) so the same event counted twice is collapsed. Record the dedup window your tool uses.
- For B2B, add a group or account identity (`account_id` or `org_id`) and send it on the events where it matters.

### Step 5. Map each event to its downstream table and respect the limits

Record, for every event, the warehouse table or model it lands in. One event is usually one table; this is what makes the plan a data contract rather than a naming list.

Hold the plan inside the collection tool's hard limits, which are version-dependent, so verify the current numbers before relying on them. As last checked on 5 November 2026: GA4 caps event names at 40 characters, parameter names at 40, most parameter values at 100 (with `page_location` up to 1,000, `page_referrer` up to 420, `page_title` up to 300), 25 parameters per event, and 25 user properties (name up to 24, value up to 36). GA4 app streams cap distinct event names at 500 per user; web streams have no such cap. Reserved prefixes such as `ga_`, `google_` and `firebase_` cannot be used; confirm the current reserved list in the GA4 docs. Mixpanel has a soft limit near 5,000 distinct event names and allows up to 255 properties per event, with names and values up to 255 characters. Design inside these, not up to them.

### Step 6. Version the plan and set the change process

- Store the plan as a machine-readable spec, not a slide. A JSON Schema per event (the Snowplow and Iglu model, versioned as `1-0-0`), a Segment Protocols tracking plan, an Avo workspace, or a schema file in git are all valid. A versioned file in git with CI validation is the free-stack equivalent of a paid governance tool.
- Add new properties as optional; never change a property's type or an event's meaning in place. When a change is breaking, ship a new version of the event and sunset the old one with a deprecation window.
- Deprecate an event only when it has no recent volume and no recent queries together. A safe window is six to twelve months of silence.
- Run every change through one process: request, review, implement, QA (hand to `analytics-qa-validation`), publish the plan, then monitor. Deleting or blocking an event changes what data exists going forward and cannot be undone for the gap it creates, so treat deletion and hard blocks as irreversible and get explicit human confirmation before either. Default to proposing the plan as a draft.

## Worked example

A B2B SaaS team sends an export with 410 distinct event names across GA4 and the warehouse. The obvious move is to rename everything to one casing and move on. That misses the real problems.

Running the workflow: Step 1 sets `snake_case` end to end, because the data lands in BigQuery. Step 2 finds `signup`, `sign_up`, `SignUp`, `user_registered` and `account_created` all firing on the same action; they collapse to one `sign_up` with a `method` property. Near-duplicate `home_viewed`, `pricing_viewed` and `docs_viewed` collapse to `page_viewed` with `page_name`. Step 3 catches `plan_price` sent as `"29.00"` on web and `29` on server; it is fixed to a number named `plan_price_usd`, and `account_id` is retyped from number to string. A column `signup_source` mixing `null`, empty string and `"none"` is standardised to omit-when-absent. Step 4 finds the app set `user_id` to the anonymous id before login, splitting each user in two; the contract moves the `user_id` to the first authenticated event and adds an identify-and-merge. Step 5 maps the 62 surviving events to tables and flags that `search` was missing its `search_term`, so the GA4 search report was empty. Step 6 writes the lot to a versioned JSON Schema in git with a CI check.

The result is 62 events from 410, every property typed, identity fixed, and a spec the next engineer cannot drift from without the CI failing. The count fell because duplicates were merged, not because data was thrown away.

## Templates you can use verbatim

Event contract row (one per event):

```
event: order_completed
fires_when: the payment provider confirms a successful charge
owner: checkout squad
identity: user_id required; account_id for B2B
consent_class: analytics_storage
destination_table: analytics.fct_order_completed
properties:
  - name: order_id         type: string   required: yes  notes: joins to orders table
  - name: revenue_usd       type: number   required: yes  notes: excludes tax and shipping
  - name: currency          type: string   required: yes  allowed: ISO 4217 codes
  - name: item_count        type: integer  required: yes
  - name: coupon_code       type: string   required: no   null_rule: omit when absent
  - name: payment_method    type: string   required: yes  allowed: card, paypal, bank_transfer
```

Naming rules block to paste at the top of any plan:

```
Grammar: object_then_past_tense_action, from the user's point of view.
Casing: snake_case for event and property names (warehouse-first). One casing, no exceptions.
IDs: strings. Amounts: numbers with a unit in the name (revenue_usd). Timestamps: ISO 8601, _at suffix.
Nulls: omit the key when there is no value. Never send empty string or a mix.
One concept = one name everywhere. Distinct concepts = distinct names.
No dynamic values in names. No dynamically generated property keys.
```

Prompt to start a plan: "Design an event tracking plan for [product] flowing to [tool] then [warehouse]. Here are the decisions it must support and the current events: [paste]. Return a versioned contract with typed properties, the identity rules and the destination tables."

## Failure modes and anti-patterns

1. Naming decided per event instead of once. You spot it when `method`, `type` and `source` mean different things on different events. Fix it by setting the grammar and casing in Step 1 and applying them everywhere before a single event is named.
2. Casing mixed across a destination. You spot it in the tool as `Sign Up`, `sign_up` and `signUp` reported as three events with split numbers. Fix it by choosing one casing per destination and consolidating; in a warehouse, lowercase everything.
3. Properties untyped or null-handled inconsistently. You spot it when a filter returns far fewer rows than expected or a funnel breaks at one step. Fix it by typing every property and choosing one null rule in Step 3.
4. Identity set too early. You spot it when user counts are implausibly high and one person appears as many. Fix it by never setting `user_id` before authentication and adding an identify-and-merge at login.
5. Events added forever with no governance. You spot it as event-name sprawl and duplicate meanings creeping back within three months. Fix it with the versioned spec and the request-review-implement-QA-publish-monitor process in Step 6.

## Quality bar for the plan

Before handing over, confirm every item:

- One grammar and one casing per destination are stated in writing and hold for every event.
- Every property has a name, a type, a required flag, and allowed values or a range.
- Every property has a null rule, and the rule is the same across the plan.
- Identity is defined: anonymous, authenticated, cross-device, logout reset, server dedup, and group for B2B.
- Each event names its downstream table.
- Standard or recommended events are reused wherever one fits, with their required parameters present.
- The plan sits inside the tool's current limits, and any version-dependent number was verified, not assumed.
- The plan is a versioned, machine-readable file with a change process, and no deletion or hard block ships without human confirmation.
- No event property carries personal data; privacy class is recorded for each property.

## Tool-specific appendix

```
GA4
- snake_case required for event and parameter names. Limits as last checked 5 Nov 2026: event name <=40,
  param name <=40, param value <=100 (page_location <=1000, page_referrer <=420, page_title <=300),
  25 params per event, 25 user properties. App streams cap distinct event names at 500 per user; web has no cap.
- Reserved prefixes ga_, google_, firebase_ cannot be used. Verify the reserved-name list in the GA4 docs.
- Reuse recommended events (login, sign_up, search with search_term, purchase with currency/value/items).

Segment
- Convention: Title Case event names, snake_case properties. Reuse ecommerce spec reserved names.
- Protocols enforces the plan: non-conforming events raise violations; strict controls block and can quarantine
  them to a separate source; transformations rename without code changes. Business-tier add-on.

Amplitude
- Default Title Case, [Object] [Past-Tense Verb]. Different casings are different events. IDs as strings.
  Govern tracks event states (Planned, Live, Blocked, Unexpected, Deleted). Blocking reduces volume but not
  the distinct-type count; only deletion reduces the type count.

Mixpanel
- snake_case recommended for warehouse export. Case-sensitive. Soft limit ~5,000 distinct event names,
  255 properties per event, names and values up to 255 chars. Lexicon renames are display-only.

Snowplow
- Self-describing JSON Schema per event, Iglu-versioned (1-0-0); invalid events are routed to failed events,
  so bad data never reaches the warehouse. Strongest option when validation-before-load matters.

Avo
- Enforces naming: blocks events differing only by casing, warns on similar names, keeps a global property
  namespace, and forbids an event property sharing a name with a user property. Inspector flags shipped
  events that do not match the plan.
```

## Sources

- Amplitude, Data planning playbook and taxonomy agent skill (naming, property types, event states, volume vs type count). Last verified 5 November 2026.
- Segment (Twilio) Protocols and tracking-plan best practices (object-action, Title Case events, violations, block, quarantine, transformations). Last verified 5 November 2026.
- Mixpanel, Events and properties (snake_case, case sensitivity, limits, super properties). Last verified 5 November 2026.
- Avo, Naming conventions (casing enforcement, global namespace, controlled vocabulary). Last verified 5 November 2026.
- Snowplow, Schemas and Iglu (self-describing JSON Schema, versioning, failed events). Last verified 5 November 2026.
- Google Analytics 4, Event collection limits and Recommended events references (name and parameter limits, recommended and ecommerce events). Last verified 5 November 2026; limits and reserved names are version-dependent, so re-check the GA4 docs before relying on specific numbers.
- Related skills: marketing tracking-plan (growth-metric selection), conversion-tracking-capi (ad conversions), and the sibling skills utm-campaign-tracking and analytics-qa-validation.
