webtracking.org
concepts

Events and schemas: the data model under every analytics tool

Events, properties, and schemas — what an analytics event actually is, how property scoping works, and why a tracking plan is the only durable defense against taxonomy drift.

last verified 2026-07-11

Strip away the dashboards and every analytics tool — GA4, Adobe, Amplitude, Mixpanel, PostHog, your warehouse — stores essentially the same thing: a timestamped record that someone did something, with context. That record is an event. Get the event model right and every downstream system inherits clean data; get it wrong and no amount of dashboard work recovers it. This guide covers the model itself: events versus pageviews, property scoping, naming taxonomies, and why a tracking plan exists at all.

Everything is an event now

Web analytics started pageview-centric. Log-file analyzers counted requests; Urchin and Universal Analytics organized everything around the pageview, with “events” bolted on as a separate hit type carrying a rigid category/action/label triple. Product analytics tools (Amplitude, Mixpanel) inverted this from the start: everything is a named event with arbitrary properties, and a pageview is just one event among many. GA4 completed the convergence — page_view is an ordinary event with parameters, not a privileged hit type.

Adobe Analytics speaks a different dialect over the same idea: a hit (image request) carries props, eVars, and numbered success events. The vocabulary differs, but the underlying record — name, timestamp, identifiers, properties — is the same, which is why a well-designed event model ports across vendors and a poorly designed one locks you in.

Anatomy of an event

Every event decomposes into four parts:

{
  "event": "add_to_cart",
  "timestamp": "2026-07-11T14:32:07.221Z",
  "identity": { "device_id": "1f3b9c…", "user_id": null },
  "properties": {
    "currency": "USD",
    "value": 49.0,
    "items": [{ "item_id": "WP-1", "item_name": "Widget Pro", "quantity": 1 }]
  },
  "context": {
    "page": { "path": "/p/widget-pro", "type": "product" },
    "consent": { "analytics_storage": "granted", "ad_storage": "denied" }
  }
}
  1. Name — the verb. The single most important governance surface, because names are how humans and queries find events.
  2. Timestamp — when it happened, ideally captured client-side at the moment of action and recorded again at collection time (the two diverge with offline queues and batching).
  3. Identity — the device/cookie identifier and, where known, a durable user ID. Identity is what lets events become journeys.
  4. Properties and context — everything else. Properties describe the action; context describes the state of the world when it happened (page, session, device, consent).

Events versus pageviews

The interesting distinction is not “pageview vs event” — a pageview is an event — it is state versus action. A pageview asserts state (“the user is now looking at X”); most other events assert actions (“the user did Y”). Two practical consequences:

  • Single-page apps break the state assertion. A full page load fires page_view naturally; a client-side route change does not. You must emit a virtual pageview on history changes and — the classic bug — re-populate page context before it fires, or every SPA pageview reports the previous page’s metadata.
  • Don’t encode state changes as actions. viewed_pricing_page as a custom event duplicates what page_view + a page.type property already expresses, and now two sources of truth drift independently.

Property scoping

Properties have a scope, and scoping bugs are the most common silent data-quality failure:

  • Event-scoped — true at the moment of the event (value, search_term, error_code). GA4 calls these event parameters; Adobe calls them props (hit-scoped).
  • User-scoped — persists across events (plan_tier, crm_id_hashed). GA4 user properties, Amplitude/Mixpanel user profiles. Set on change, not on every event.
  • Item-scoped — properties of things inside the event, like the items array in GA4 ecommerce. One event, many items, each with its own attributes.
  • Persisted/allocated — Adobe’s eVars are the explicit version: a value set once persists across subsequent hits until a configured expiration (visit, 30 days, a purchase event). GA4 does something similar implicitly with session-scoped traffic attribution.

The two classic failures: putting user state into event properties (it goes stale and disagrees with itself across events), and putting per-event data into user scope (each event overwrites the last, and history is destroyed).

Naming and taxonomy

A taxonomy is a small set of rules that make names predictable before you look them up:

  • One grammar. object_action (cart_add, signup_complete) or action_object (add_to_cart, complete_signup) — either works; mixing them guarantees synonyms. The dataLayer & Event Taxonomy Spec standardizes on GA4-compatible action_object names matching ^[a-z][a-z0-9_]{1,49}$.
  • snake_case everywhere, enforced by regex, not convention. addToCart, Add to Cart, and add_to_cart are three different events to every system that will ever query them.
  • Enumerate property values. page.type should draw from a closed list (home, product, category, checkout…), not free text.
  • Keep identifiers out of names. clicked_banner_1234 creates unbounded cardinality; the banner ID belongs in a property.
  • No raw PII in names or values — hash upstream or pass opaque IDs. The spec’s schema actively rejects user.email and friends for exactly this reason.

Schemas make the contract enforceable

A naming convention in a wiki is a suggestion. A schema is a contract you can enforce. The practical form is JSON Schema: each event’s required properties, types, enums, and conditional rules (purchase requires transaction_id, value, currency, items) written down once and validated in three places — in the browser during QA (the upcoming dataLayer Validator will run the published schema client-side; until it ships, Ajv in the console does the same job), in CI against fixtures on every PR that touches tracking, and at the edge before events are forwarded to your warehouse or server-side container. Ajv plus a fixtures directory is the cheapest tracking-QA setup that exists:

ajv validate -s datalayer.schema.json -d "fixtures/*.json"

Fail the build on a violation. A schema that isn’t enforced anywhere is documentation, and documentation drifts.

Why a tracking plan exists

The tracking plan is the human-readable projection of the schema: every event, its trigger, its properties, its owner, and the question it exists to answer. Teams that skip it converge on the same failure state — sign_up, signup, Sign Up, and registration_complete all live in production, each implemented by a different developer in a different quarter, and every analysis starts with archaeology. A tracking plan exists because:

  • It is the shared vocabulary between engineering, analytics, and marketing. Without it, each group invents names independently and reconciliation becomes a permanent tax.
  • It forces the “why” question. An event with no owner and no question attached is cost without value — collection, storage, consent surface, and maintenance for nothing.
  • It makes migration survivable. Vendors change; a documented, vendor-neutral event model is what you re-map onto the next tool. Undocumented tribal knowledge is what you lose.
  • It bounds drift. Every unreviewed event added “just for this campaign” is future archaeology. The plan is where additions get named consistently before they ship.

The governance loop

The model only stays clean if there is a loop, not a document: design (propose the event in the plan, name per taxonomy) → review (does an existing event already cover it?) → implement (against the schema) → validate (CI + browser validation before release) → monitor (alert on unknown event names and schema violations in production). Version the plan like code — the spec carries a specVersion for precisely this reason — and treat an unreviewed event name in production the way you treat a failing test.

Events are cheap to emit and expensive to un-emit. The model described here — one grammar, explicit scoping, an enforced schema, a living plan — is what keeps the cheap part cheap.