Why feature flags beat hardcoded role checks
Tiered access control looks simple in the first version: check the user's plan, show or hide the feature. The problem shows up later, once that check has been copy-pasted into a dozen components, and someone needs to add a new tier, or run a promotion, or grandfather in early users at their old pricing.
Feature flags solve this by inverting the question. Instead of scattering "is this user an admin" checks through the codebase, you ask "does this user have this specific entitlement" from a single source of truth, and let that source of truth be driven by whatever business logic actually applies — plan tier, promotional override, grandfathering, an admin's manual grant. The UI code never needs to know why someone has access, only that they do.
The extra abstraction costs a little more setup on day one. It pays for itself the first time a pricing model changes and the fix is one line in a rules engine, not a search-and-replace across forty files.
What does an entitlement check look like in code?
Instead of checking roles and plans throughout the interface:
// Scattered, and wrong the day pricing changes
if (user.plan === "pro" || user.role === "admin") {
showExportButton();
}
the interface asks one question:
// One question, answered in one place
if (can(user, "export.csv")) {
showExportButton();
}
The can() function reads from a single set of rules: which plans include which entitlements, which users have manual grants, which promotions are active, and which early customers keep their original features.
What is the difference between feature flags and entitlements?
They are related but not the same:
- Release flags turn unfinished or risky features on and off, often for a small group first. They are temporary and should be removed once the feature is fully live.
- Entitlements decide what a customer is allowed to use, based on what they pay for. They are permanent business rules.
- Operational flags act as kill switches, letting you turn off an expensive or failing feature without a deploy.
Keeping them separate stops temporary release flags from turning into permanent pricing logic.
Where should the check run?
Everywhere access matters, from the same rules:
- In the interface, to show or hide features and explain upgrades.
- On the server, on every request that uses the feature. Hiding a button is not security; the API must check too.
- In background jobs, so scheduled work respects the same limits.
What goes wrong without this?
- A new plan tier means editing dozens of files and missing some.
- A customer sees a feature in the interface that the server then rejects.
- A promotion ends and nobody can find everywhere it was hard-coded.
- Support cannot grant a one-off exception without a code change.
We used this pattern on Subme, a tiered subscription platform for creators, where each content tier is unlocked by a flag rather than a hard-coded role check, so upgrades take effect immediately. It is part of how we build custom web apps and SaaS products, alongside the case for boring architecture.
Building something this applies to?
Book a discovery call→