Feature flags / entitlements
Plan-based gating and quotas resolved from plans + overrides.
Feature flags are the entitlement layer: what an org can do, resolved from its
plan's defaults plus per-org overrides. There is exactly one resolution point —
getEntitlements(orgId) — that everything else (tRPC procedures, the public API,
UI gates) reads through, so capability checks can never drift from the plan
config.
Optional feature
Flags is optional (core: false, requires: ["billing"]). It composes
billing's plan resolution and overlays per-org boolean overrides.
What it does
- Resolution —
getEntitlements(orgId)returns the resolved map: billing's plan defaults (getPlanEntitlements) with per-orgFlagOverridebooleans overlaid. Order is override › adminplanKeyOverride› plan default. - Assertions & quotas —
assertEntitlementandcheckQuotaguard server-side;UNLIMITED(-1) is the quota contract's unlimited sentinel. - Middleware & UI — the
entitled(...)tRPC middleware, the<Entitled>component for conditional UI, and aMemberLimitNoticefor seat limits. - Admin writes —
setFlagOverride/clearFlagOverridelet the admin panel toggle overrides platform-wide (used for rollouts, betas, support exceptions).
Models it adds
| Model | Purpose |
|---|---|
FlagOverride | A per-org boolean capability override, unique per (org, key). Overlays the plan default; booleans only (numeric quotas come from plans). |
Adds the marker-wrapped Organization.flagOverrides relation. createdById is
a plain audit column, not a relation — deleting the admin who set an override
must not cascade the override away.
Env it needs
None.
Composition
requires: ["billing"]— flags reaches billing'sgetPlanEntitlementsthrough the public surface (never a deep import); billing owns plan resolution, flags owns the singlegetEntitlementsresolution point.- Dependents —
usagerequires: ["flags"](quota checks viacheckQuota) andadminrequires: ["flags"](override writes).
What pruning removes
Leaving flags out drops features/flags, the FlagOverride model, and the
Organization.flagOverrides relation. Because usage requires flags,
deselecting flags also removes usage; admin requires flags too, so it goes
as well.