Lesson 22 / 32
Plans and Entitlements
Model plans, per-tenant overrides and limits as data, and check entitlements in one place before allowing a feature or a new seat.
By Rahul Agarwal · 15 years in web development
What a tenant is allowed to do
An entitlement is a right a tenant has bought or been granted: a feature (SSO, API access, audit logs), a limit (25 seats, 100 GB storage) or a level of service. Plans bundle entitlements, but real businesses also need overrides: a sales deal that adds API access to a smaller plan, a beta flag for a few tenants, a temporary limit raise. Model all of this as data owned by the control plane, and expose one function or service that answers "is tenant X entitled to Y?". Feature code calls that check instead of comparing plan names, because if plan == 'pro' scattered across the code breaks the day marketing renames a plan or adds a new one. Entitlements differ from feature flags used for gradual rollouts: a flag says whether code is switched on yet, an entitlement says whether this customer has paid for it. Many systems evaluate both.
From plan to permitted action
A tenant's plan supplies default entitlements, overrides adjust them, the entitlement check combines both, and the feature is allowed or denied.
One place to ask
Plans and overrides are plain data. The free tenant zen gets API access through an override but still cannot use SSO.
PLANS = {
"free": {"seats": 3, "features": {"projects"}},
"pro": {"seats": 25, "features": {"projects", "sso", "api"}},
}
# Per-tenant overrides (a sales deal, a beta) win over the plan default
OVERRIDES = {"zen": {"features": {"api"}}}
def entitled(tenant, plan, feature):
extra = OVERRIDES.get(tenant, {}).get("features", set())
return feature in PLANS[plan]["features"] | extra
def can_add_seat(plan, used):
return used < PLANS[plan]["seats"]
print("zen api: ", entitled("zen", "free", "api"))
print("zen sso: ", entitled("zen", "free", "sso"))
print("acme sso:", entitled("acme", "pro", "sso"))
print("zen 4th seat:", can_add_seat("free", 3))
Output:
zen api: True zen sso: False acme sso: True zen 4th seat: False
Enforce limits on the server
Hiding the "add user" button when the seat limit is reached is good UX, not enforcement. The API that creates the membership must run the same entitlement check, or a direct API call bypasses the limit.
Quick check: Why is `if tenant.plan == 'pro':` in feature code a poor way to gate features?
- Python cannot compare strings
- Plans should never have names
- It ignores overrides and breaks when plans are renamed or added; a central entitlement check avoids both
- It is slower than a database query
Answer
It ignores overrides and breaks when plans are renamed or added; a central entitlement check avoids both — Plan-name checks hard-code commercial packaging into product code. A central entitlement check handles overrides and new plans without hunting through the codebase.