Overview
plans is a reserved structured attribute on every account. It lets you
natively segment and filter both users and accounts by their commercial plan
(trial, paid tiers, billing cadence) without ad-hoc queries or manual tagging.
Because a user can belong to multiple accounts, plan data is also denormalized
onto each user, so user-level filtering stays fast.
The PlanItem contract
An account’s plans is an array of plan objects:
Plan keys are deduplicated per account — sending the same key twice keeps the
last occurrence. Values are normalized and validated server-side regardless of
the write channel.
A reserved attribute
plans is reserved: it is always written into the typed account.plans field
and never into the generic custom-attributes map. An identify or API
caller cannot smuggle arbitrary structure into it.
plan (singular string) is a legacy field, now deprecated in favour of
plans. It is left untouched for backwards compatibility — new integrations
should use plans only.
Setting plans
Plans can be set through two channels:
- SDK
identify — via the account.plans array. See identify.
- Server API — via the account upsert endpoint, or
PATCH /accounts/:id
from the workspace.
In all cases, sending plans replaces the account’s plan list. An empty
array ([]) clears all plans; omitting plans leaves them unchanged.
Filtering and segmentation
User cache
Each user caches the plan data of every account it belongs to:
cache.accounts[].plans — the full plan objects, per associated account.
cache.planKeys — the flattened, deduplicated union of plan keys across all
of the user’s accounts. This is the field queried for user-level filtering.
Caches are recomputed synchronously on every account write — there is no
background sync, so plan filters never read stale data.