← Back to blog

July 9, 2026 · 8 min read

Entitlements as Data, Not Code

Most teams start by hard-coding plan logic — if (plan === 'pro') — scattered through the app. It rots: every pricing change becomes a deploy, and no one can say what a plan includes without grepping the codebase. Entitlements as data moves that decision out of code and into a declarative payload you resolve at runtime. It is the difference between compiling your price list into your product and looking it up. Here is the anti-pattern, the shape that replaces it, and what moving to data actually buys you.

The hard-coded entitlement anti-pattern

The first version of feature gating almost always looks like this, sprinkled wherever a paid capability lives:

if (user.plan === 'pro' || user.plan === 'enterprise') {
  enableExport()
}
// ...and in three other files:
const seatLimit = user.plan === 'enterprise' ? 50 : 5

It works on day one and rots by month three. The rules drift out of sync as they spread; every pricing or packaging change becomes a code change and a deploy; the same limit gets implemented slightly differently in three places, so a paid feature leaks to a free user or a paying customer gets blocked. And you cannot answer a question as basic as "what does Pro include?" without reading source. The commercial model — the thing that changes most often and matters most to revenue — is the thing you have welded most tightly to your release cycle.

The shape that replaces it

Entitlements as data means the plan-to-capability mapping is a structured payload attached to the license, resolved at runtime:

"entitlements": {
  "features": ["export", "api_access", "sso"],
  "seats": 5,
  "api_calls_per_month": 100000,
  "expires_at": "2027-01-01"
}

Your code stops asking "which plan is this?" and starts asking "does this grant include what I am about to do?". The branch on a plan name disappears; a lookup against resolved data takes its place. This is the natural next step once you understand feature entitlements vs a plain license key — that post covers what an entitlement is; this one is about keeping it as data rather than code.

What moving to data buys you

  • Change packaging without a release. Move a feature from Enterprise into Pro, or raise a quota, by editing the plan definition — no code change, no deploy, no QA cycle for a pricing tweak.
  • One source of truth. "What does Pro include?" is answered by reading the plan data, not by grepping. Sales, docs, and the product all reference the same definition.
  • Testable and auditable. A payload is easy to assert against in tests and easy to log — you can prove what a customer was entitled to at the moment of an action, which matters for disputes and audit trails.
  • Per-customer overrides are trivial. Because entitlements are resolved data, a one-off exception is a field override, not a special code path (see below).
  • Introspectable. A support tool or dashboard can render a customer’s exact grant without re-implementing your plan logic.

Resolve once, check at the boundary

The data model only pays off if you stop scattering checks. Resolve the caller’s entitlements once per request — from a signed license or a server-side lookup — then check the specific capability or limit at the point the action happens: is this feature in the grant, are they under their seat count, is this call within the monthly quota? Limits can be hard (block at the ceiling) or soft (allow overage and notify). The rule lives in the data; your code just enforces whatever the resolved entitlement says.

Overrides and grandfathering

This is where hard-coded logic hurts most and data shines. Real businesses accumulate exceptions: one customer negotiated an extra ten seats, a cohort was grandfathered onto an old limit when prices rose, an enterprise deal unlocks a feature no public plan includes. With plan logic in code, each exception is a new conditional — if (customer.id === 4821) — that never gets removed. With entitlements as data, an override is a field applied on top of the plan defaults; no code path knows the customer is special, it simply reads the resolved grant and enforces it.

Where the data lives — and who owns the mapping

Entitlement data lives in one of two places, and good platforms support both. As signed claims inside an Ed25519-signed JWT license, verified offline against a public key with a short TTL — ideal for offline or edge software. Or resolved server-side from an opaque key on each validation call, changeable instantly and revocable centrally — ideal for always-online software. The trade-off is offline verification versus instant revocation; see JWT vs opaque license keys. Either way, the plan-to-entitlement mapping is data you own and edit, not logic you compile.

Build vs. buy

You can build this: a plan-to-entitlement store, a resolver, an override layer, and both signed-claim and server-resolved delivery. It is also exactly the machinery a homegrown "check the plan name" system slowly turns into, one special case at a time. ValidonX treats entitlements as data by default — every validation returns the plan and its resolved entitlement payload, you gate features and enforce seats and quotas from that data, per-customer overrides sit on top of plan defaults, and you get both opaque keys and Ed25519-signed JWT licenses out of the box. It is the same model behind licensing as a service — the plan is data, and your product just reads it.

Frequently asked questions

What does "entitlements as data" mean?

It means the mapping from a plan to what it unlocks — features, seats, quotas, expiry — lives as declarative data attached to the license, not as conditional logic compiled into your application. Instead of "if the plan is Pro, enable export" written in code, your software resolves an entitlement payload at runtime and checks it. The commercial rules become configuration you can read, change, and audit rather than branches scattered through the codebase.

Why shouldn’t you hard-code plan or feature checks in your application?

Because hard-coded checks rot. Plan conditionals spread across the codebase drift out of sync, every pricing or packaging change becomes a code deploy, and no one can answer "what does Pro include?" without grepping. Worse, the same rule gets implemented slightly differently in three places, so paid features leak or paid customers get blocked. Moving the rules into data gives you one source of truth you change without shipping a release.

How do you change what a plan includes without deploying code?

Keep the plan-to-entitlement mapping as data, not code. When entitlements are resolved server-side from an opaque key, you edit the plan definition and the next validation returns the new grant immediately. When they are signed into a JWT license, you issue licenses with the new claims and a short TTL bounds how long the old ones linger. Either way you repackage — move a feature from Enterprise into Pro, raise a quota — by changing data, not by editing and redeploying gate checks.

Where is the entitlement data stored?

Two common places, and good platforms support both: as signed claims inside the license (an Ed25519-signed JWT the software verifies offline against a public key, with a short TTL), or resolved server-side against an opaque key on each validation call (nothing baked into the key, changeable instantly). The trade-off is offline verification versus instant revocation. In both cases the entitlements are structured data your software reads — not characters it parses out of a key string.

How do you handle per-customer entitlement overrides or grandfathering?

Overrides are the clearest payoff of the data model. Because a customer’s entitlements are a resolved payload rather than a plan name hard-coded into checks, you can grant one customer an extra seat, keep a legacy limit for grandfathered accounts, or run a bespoke enterprise deal by overriding specific fields on top of their plan defaults. No code path knows or cares that this customer is special — it just reads the resolved entitlement and enforces it.

Want your plan rules in data, not scattered through your code? Start free — issue a license, attach plan entitlements, and gate a feature from the resolved payload in minutes, no credit card.

We use essential cookies for authentication and session management. Privacy Policy