# Property naming and types

Event names say what happened. Properties carry everything that varies. A clean event list with drifting property names and types still fragments every breakdown.

## Contents

- Name rules
- Suffixes that carry meaning
- One name, one type, one meaning
- Enumerations and missing values
- Event-time context versus current profile state
- Context properties that keep one event per fact
- Names to avoid per destination
- Personal data

## Name rules

- Lowercase ASCII snake_case, starting with a letter, the same pattern as event names.
- Event properties: 40 characters at most (GA4 parameter name limit). User properties: 24 characters at most (GA4 user property name limit).
- Name the attribute, not the event: `export_format`, not `report_export_succeeded_format`. The same property name then works on every event that has that attribute.
- Shape: object then attribute (`plan_code`, `item_price`), which matches PostHog's `object_adjective` guidance and reads well in every tool.
- No dynamic keys. `feature_1: true`, `feature_2: true` becomes one property per feature; send `feature_key: "bulk_edit"` or an array instead. Segment's guidance warns against dynamic property keys for the same reason.
- No values in names: `price_usd` is acceptable only when the property is always US dollars; otherwise send `price` and `currency`.

## Suffixes that carry meaning

Pick these once, write them into `naming-rules.md`, and lint for them:

- `_id`: an identifier, always a string, even when the source is numeric. Leading zeros and 64-bit integers survive as strings.
- `_at`: a timestamp in UTC, ISO 8601 in the payload unless a destination requires epoch milliseconds (map that in the destination block, not in the name). Type it as `timestamp` in the plan even when it travels as a string.
- `_date`: a calendar date without time.
- `is_` or `has_` prefix: a boolean. Values are real booleans, never `"true"`, `"yes"` or `1`.
- `_count`: a non-negative integer.
- Duration and size units in the name: `_ms`, `_seconds`, `_bytes`. A duration without a unit is ambiguous by a factor of 1,000.
- Money: a numeric amount plus a separate `currency` with an ISO 4217 code. State in the property definition whether the amount is in major units (12.50) or minor units (1250), and whether it is before or after tax, discount and refunds. GA4 and Segment both treat `currency` as the currency of `value` or `revenue`.

## One name, one type, one meaning

- One property name has one type across every event and every producer. `plan` as a string on one event and an object on another splits or breaks the property in every tool that infers types from data.
- One property name has one meaning. `source` meaning acquisition channel on one event and data connector on another is two properties: `acquisition_source`, `connector_type`.
- A property used by more than one event is defined once in `martech-context/data-dictionary.md` and referenced from each event.
- Booleans in one tool and strings in another (because a connector stringified them) are a mapping defect. Check a received payload in each destination.

## Enumerations and missing values

- Enumerated values are lowercase snake_case codes (`pdf`, `csv`, `template_gallery`), listed in the plan. Display labels belong in the reporting layer.
- A new value goes through the same review as a new event when reports or workflows branch on it.
- Distinguish unknown from false or zero. A missing eligibility flag is not `false`; a missing plan is not `free`. Send `unknown` as an allowed value, or leave the property null and document what null means.
- Never map an unrecognised value silently to an existing one. It produces convincing, wrong reports.
- Keep the number of distinct values bounded. Amplitude dropdowns show the first 1,000 values of a property and GA4 warns against high-cardinality custom dimensions. IDs and free text are not dimensions.

## Event-time context versus current profile state

- Event properties record the state at the moment of the event. Profile properties behave differently per tool:
  - Mixpanel stores only the latest profile value and joins it onto every past event at query time. Only historical profile properties imported through a warehouse connector are picked by event time.
  - Amplitude records user properties at the time each event fires, so a grouped chart can show values that are no longer current.
  - The same breakdown by a user-level `plan` can therefore answer two different questions in two tools.
- When the same concept exists on the profile and on the event, give the event copy an explicit name (`plan_at_activation`, `role_at_action`) so a breakdown cannot silently use today's value for last quarter's events.
- Capture account or workspace context on the event at event time. Reconstructing it later from the user's current workspace assigns history to the wrong account after a team switch.

## Context properties that keep one event per fact

- `actor_type` (`customer`, `admin`, `support_impersonation`, `system`): an admin or a support agent acting as the customer records the same fact, but must not inflate customer activation. Filter on the property, or use a distinct event when the fact differs.
- `interaction_method` (`button`, `keyboard_shortcut`, `api`): a shortcut and a button that commit the same change are one event.
- `locale` or `ui_language`: the interface language is a property. Event names never come from UI copy or a translated label.

## Names to avoid per destination

- GA4 event parameters: do not use `cid`, `customer_id`, `customerid`, `dclid`, `gclid`, `session_id`, `sessionid`, `sfmc_id`, `sid`, `srsltid`, `uid`, `user_id`, `userid` for custom data, and nothing starting with `_`, `firebase_`, `ga_`, `google_` or `gtag.`. Use the platform's identity fields for identity.
- GA4 user properties: the reserved list in `platform-constraints.md`, including `first_open_time`, `session_number` and `user_id`.
- Mixpanel: never `bucket`; do not reuse `token`, `ip`, `distinct_id`, `time` or `alias` for anything except Mixpanel's own meaning; `$` names are Mixpanel's.
- Amplitude: do not name custom properties after top-level HTTP fields (`session_id`, `insert_id`, `time`). The HTTP V2 page says `session_id` must sit at the top level or Amplitude cannot map it; it does not say how a same-named key inside `event_properties` behaves, so avoid the names rather than test them.
- Segment: `revenue`, `currency` and `value` only with Segment's reserved meanings.
- A name that passes GA4 is not automatically safe elsewhere: `bucket`, `token`, `time`, `ip`, `alias` and `distinct_id` are valid GA4 parameter names and Mixpanel reserved properties. Run P03, P06 and P07 on every property list.
- Check the live reserved lists before approving; the copies in `platform-constraints.md` are dated.

## Personal data

- No email addresses, names, phone numbers, street addresses, passwords, tokens, free text or URLs with query strings in property names or values sent to analytics tools. Moving a value from the event name into a property does not make it acceptable.
- Use a stable internal ID instead of an email as the user identifier.
- When a field holds personal data by design (a CRM sync, a user-provided data feature), it belongs to that destination's documented field and the privacy owner's approval, not to a general event property.
