Designing Plan-Based Feature Restrictions in a SaaS Application

Plan limits look simple in a pricing table and get messy in code. Free vs Pro vs Business is not one if statement โ€” it is an entitlement system that must stay consistent across UI, API, jobs, and support tools.

Define Entitlements as Data, Not Scattered Checks

I keep a single source of truth for what each plan allows:

const PLANS = {
  free: { projects: 1, seats: 1, customDomain: false, export: false },
  pro:  { projects: 10, seats: 5, customDomain: true, export: true },
  business: { projects: Infinity, seats: 50, customDomain: true, export: true }
};

Features read from this map (or a DB table synced from billing) instead of hardcoding plan names in fifty components.

Enforce on the Server โ€” Always

Hiding a button is UX. Blocking the API is security and billing integrity. Every restricted mutation checks entitlements server-side:

if (!entitlements.customDomain) {
  return res.status(402).json({ error: 'upgrade_required', feature: 'customDomain' });
}

UI Should Explain the Wall, Not Pretend It Is Gone

Limits Need Soft and Hard Behaviors

  1. Soft: warn at 80% of seat/project quota
  2. Hard: block create when at limit; allow read/edit of existing items
  3. Grace: optional short overage for annual customers during renewal glitches

Sync Billing Webhooks Carefully

Plan changes arrive asynchronously from Stripe/Paddle/etc. I treat webhook handlers as the authority that updates the local entitlement snapshot, then invalidate sessions or feature caches. Manual admin overrides exist for support, but they are audited.

Test Matrix Worth Automating

Free: cannot create 2nd project
Pro: can enable custom domain
Business: seat invite blocked at limit
Downgrade: excess resources become read-only
Webhook: plan upgrade unlocks API within seconds

Final Thoughts

Plan restrictions are a product surface and a security boundary. Centralize entitlements, enforce on the server, and make upgrade paths honest in the UI.


Written by A.M. Rinas ยท Published September 4, 2026

A.M. Rinas

Written by A.M. Rinas

Full-Stack Software Engineer working on web applications, DevOps automation, and databases. More about me on the about page, or say hi through the contact page and LinkedIn.

← Back to all articles