Subscription Plan Design for SaaS for Technical Founders
Subscription plan design is where product strategy and system architecture intersect. For technical founders, getting the architecture wrong early is expensive — a plan structure that made sense at 50 customers becomes a database migration nightmare at 5,000. Getting the pricing strategy wrong is equally expensive but in the opposite direction: ship too few tiers and you leave upgrade revenue on the table; ship too many and you confuse prospects and stall conversion.
This guide treats subscription plan design as an engineering problem with a business constraint. It covers how to model subscription tiers architecturally, which features to gate and why, how to implement usage limits without breaking your system under load, and how to build an entitlement layer that evolves without constant rewrites.
🏗️ Four Pricing Model Architectures
Before you write a line of entitlement code, you need to choose a pricing model. The choice determines your data model, your billing infrastructure, and how complex your upgrade logic will be.
| Model | How Billing Works | Architectural Complexity | Best Fit |
|---|---|---|---|
| Flat Rate | Fixed monthly fee per account | Low — one price, one entitlement set | Simple tools, narrow use cases |
| Tiered Plans | Fixed fee per tier (Starter / Growth / Pro) | Medium — plan-level feature gates | Most B2B SaaS at seed to Series A |
| Usage-Based | Per unit consumed (API calls, seats, GB) | High — metering, aggregation, overage logic | Infrastructure, AI/ML, developer APIs |
| Hybrid | Base subscription plus usage charges | High — combines tier gates and metering | Mature products with diverse usage patterns |
Flat Rate
Flat rate is the simplest to implement and the easiest for customers to understand. Entitlement logic is binary: the customer either has access to everything or they do not. The main architectural benefit is that you can skip the entitlement layer entirely at the start and use a single boolean check against subscription status.
The problem with flat rate is it limits your revenue ceiling. There is no upgrade path short of a price increase, and large customers paying the same as small customers creates LTV compression. Most flat-rate products eventually migrate to tiered — plan for that migration early.
Tiered Plans
Tiered plans (typically three tiers) are the most common structure for B2B SaaS. Each tier is a bundle of features and limits. Architecturally, tiers require a plan-to-feature mapping and a way to evaluate that mapping at request time. This is the entitlement layer.
The key design principle: tiers should be defined in data, not in code. Hardcoding if plan === 'pro' conditions throughout your codebase is what creates the migration nightmare. Instead, define plan capabilities in a configuration object or database table, and evaluate them through a single entitlement service.
Usage-Based Pricing
Usage-based pricing (UBP) charges customers based on consumption. This requires a metering system: you must accurately count events, aggregate them over billing periods, and pass totals to your billing provider. The architecture is materially more complex than tiered plans because every usage event must be captured, stored, and reconciled with billing.
UBP also creates collection risk: customers who consume unexpectedly large amounts may receive invoice shock, leading to disputes and churn. Implement usage alerts and spending caps before you launch UBP to any significant customer base.
Feature Gating vs. Usage Gating
Two distinct gating mechanisms exist in subscription plan design, and they require different implementations.
Feature gating controls whether a capability is available at all. A customer on the Starter plan cannot access the API. A customer on the Growth plan can. The gate is binary: yes or no. Technically, this is a lookup against the entitlement table for the customer's current plan.
Usage gating controls how much of a capability a customer can consume. A customer on Growth can make 1,000 API calls per month. After 1,000, requests are either blocked (hard limit) or charged at an overage rate (soft limit). Technically, this requires a usage counter maintained at the account level, updated on every qualifying event, and checked against the plan's limit before serving the request.
| Gate Type | Implementation | Database Pattern | Failure Mode to Guard |
|---|---|---|---|
| Feature gate | Entitlement lookup per request | plan_features join table | Cache staleness after plan change |
| Usage gate (hard) | Counter check before action | usage_counters, reset on billing cycle | Race conditions on concurrent requests |
| Usage gate (soft) | Counter track + overage calculation | usage_events append log + aggregation | Aggregation lag causing billing errors |
| Seat gate | Active member count vs. plan limit | team_members count vs. plan.max_seats | Stale count cache during invite flow |
Handling Race Conditions on Usage Limits
The classic failure mode for usage gating is the race condition: two concurrent requests both read the counter at 999 (limit: 1,000), both determine they are within the limit, both execute, and the counter ends at 1,001. For low-stakes limits this may be acceptable. For billing-critical limits, use atomic increment operations (Redis INCR or database row-level locking) and check the returned post-increment value against the limit.
Designing an Entitlement System
The entitlement system is the layer that answers: given the current account and its active subscription, what is this account allowed to do? A well-designed entitlement system has three properties: it is fast to evaluate, it is easy to change, and it is decoupled from billing logic.
Schema Pattern
A minimal entitlement schema has four components:
- → A
planstable defining available plans and their display metadata - → A
plan_featurestable mapping plans to feature keys with optional limit values - → A
subscriptionstable linking accounts to their current plan - → An
account_feature_overridestable for manual grants to specific accounts
The override table is critical. Every SaaS product eventually needs to give a specific customer access to a feature outside their plan — for a pilot, a beta program, or a negotiated deal. Without the override table, you end up hardcoding exceptions in the application layer.
Entitlement Resolution Order
When resolving whether an account can use a feature, evaluate in this order:
- Check account-level overrides first — these take precedence over plan rules
- Check the account's active subscription plan against the plan_features table
- If no match, deny access (fail closed)
Fail closed is the correct default. If your entitlement system encounters an unexpected state — no active subscription, missing plan data — it should deny access, not grant it. Granting on uncertainty creates unpredictable free access.
Caching Strategy
Entitlement checks can happen dozens of times per request. A cold database query per check will kill your p99 latency. Cache the resolved entitlement set for each account with a short TTL (60-300 seconds). On plan change events, invalidate the cache immediately. Use a cache-aside pattern: check cache first, fall through to database on miss, populate cache on return.
Upgrade Logic and Instrumentation
Upgrade logic is the set of rules and signals that determine when to show a customer an upgrade prompt. Technically, this means evaluating whether a customer has hit a limit or attempted to use a gated feature, and surfacing that moment in the UI.
Upgrade Trigger Points
The highest-converting upgrade prompts appear at the exact moment a customer tries to do something they cannot. The technical implementation:
- → When a feature gate check returns false, return a response that signals the reason (
{ allowed: false, reason: 'plan_limit', upgrade_to: 'growth' }) - → When a usage gate check returns false (limit reached), return the same structured response with current usage and limit included
- → The frontend renders an upgrade modal or inline prompt using that response data
Returning structured denial reasons rather than generic 403 errors is the architectural decision that enables contextual upgrade prompts. If your API returns 403 Forbidden with no body, your frontend has to guess why the request failed and cannot show a useful upgrade prompt.
Instrumentation
Track these events for every plan-related action:
| Event | Properties to Capture | Used For |
|---|---|---|
| feature_gate_blocked | account_id, feature_key, current_plan | Identifying which features drive upgrades |
| usage_limit_reached | account_id, metric, current_value, limit | Identifying which limits cause friction |
| upgrade_prompt_shown | account_id, trigger, from_plan, to_plan | Measuring upgrade prompt effectiveness |
| plan_upgraded | account_id, from_plan, to_plan, trigger | Attribution of upgrades to trigger points |
The trigger property on plan_upgraded closes the loop: it tells you which specific feature gate or usage limit was the last thing the customer hit before upgrading. This data tells you which gates are actually driving revenue, which lets you make data-driven decisions about where to move the gates on future pricing iterations.
Plan Architecture Mistakes to Avoid
Several common architectural decisions in subscription plan design cause significant rework later.
Hardcoding Plan Names in Application Logic
Checking if account.plan === 'starter' directly in application code couples your business logic to specific plan names. When you rename, restructure, or add plans — which you will — every hardcoded check becomes a potential bug. Always go through the entitlement system, never check plan names directly.
Binding Entitlements to Billing Events
Entitlement changes (what the account can access) and billing events (what charge was processed) are related but not identical. An account can be on a paid plan but in a grace period, on a trial, or have a manually extended access window. If you tie entitlements directly to payment success webhooks, you lose the flexibility to handle these edge cases. Model subscription state separately from payment state.
Not Planning for Grandfathering
When you change plan pricing or feature sets, existing customers often need to stay on their current terms for a period. This requires a mechanism for accounts to be on a plan version that no longer exists in your current plan catalog. The solution: store the plan configuration (feature set and limits) at subscription creation time, or maintain versioned plan records. Do not assume you can always read current plan config and apply it to all subscriptions.