saas-entitlements-and-plan-gating

Use when designing the entitlements engine that enforces what each plan/tier/tenant can do — feature flags vs entitlements distinction, limits enforcement (seats, API calls, storage, projects, AI tokens), gate placement (UI, API, worker), upgrade-discovery UX, override mechanisms for enterprise contracts, and the runtime that resolves "can this tenant do X right now?" in a few milliseconds.

Best use case

saas-entitlements-and-plan-gating is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Use when designing the entitlements engine that enforces what each plan/tier/tenant can do — feature flags vs entitlements distinction, limits enforcement (seats, API calls, storage, projects, AI tokens), gate placement (UI, API, worker), upgrade-discovery UX, override mechanisms for enterprise contracts, and the runtime that resolves "can this tenant do X right now?" in a few milliseconds.

Teams using saas-entitlements-and-plan-gating should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/saas-entitlements-and-plan-gating/SKILL.md --create-dirs "https://raw.githubusercontent.com/peterbamuhigire/skills-web-dev/main/skills/saas/saas-entitlements-and-plan-gating/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/saas-entitlements-and-plan-gating/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How saas-entitlements-and-plan-gating Compares

Feature / Agentsaas-entitlements-and-plan-gatingStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Use when designing the entitlements engine that enforces what each plan/tier/tenant can do — feature flags vs entitlements distinction, limits enforcement (seats, API calls, storage, projects, AI tokens), gate placement (UI, API, worker), upgrade-discovery UX, override mechanisms for enterprise contracts, and the runtime that resolves "can this tenant do X right now?" in a few milliseconds.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# SaaS Entitlements and Plan Gating
Acknowledgement: Shared by Peter Bamuhigire, techguypeter.com, +256 784 464178.

<!-- dual-compat-start -->
## Use When

- Designing the runtime that answers "what is this tenant allowed to do?" — every gated feature, every limit-checked action, every plan-tied capability.
- Replacing scattered `if tenant.plan == 'pro'` checks with a single entitlements service.
- Designing the data model for plans, features, limits, overrides, and add-ons.
- Supporting enterprise custom-contract overrides without forking the codebase.
- Pairing in-product upgrade prompts (PLG) with the gate runtime so users hit a wall *and* see the path forward.
- Coordinating entitlements with feature flags (distinct concerns; they overlap dangerously if confused).

## Do Not Use When

- The task is Stripe Billing primitives (Plan / Price / Subscription / Trial) — use `subscription-billing`. This skill consumes those primitives.
- The task is experimentation feature flags (A/B tests, gradual rollout) — feature flags ≠ entitlements; see §3.
- The task is per-tenant data isolation — use `multi-tenant-saas-architecture`.
- The task is enterprise SSO/SCIM — use `saas-sso-scim-enterprise-auth`.

## Required Inputs

- Plan catalogue (from `subscription-billing`): tier names, features per tier, limits per tier.
- Pricing model decisions (per-seat, usage-based, hybrid) from product.
- List of features to gate + list of limits to enforce.
- Override policy: how enterprise contracts customise entitlements without code changes.

## Workflow

1. Read this `SKILL.md` end-to-end.
2. Distinguish **entitlements** (what plan allows) from **feature flags** (rollout/experimentation) — §3.
3. Design the entitlements data model (§4) — plans, features, limits, overrides.
4. Pick the resolution strategy (§5) — pre-resolved on JWT vs runtime lookup vs hybrid.
5. Place the gates (§6) — UI hint, API enforce, worker enforce.
6. Wire the upgrade-discovery UX (§7) — the gate is also a sales surface.
7. Build the override mechanism (§8) for enterprise contracts.
8. Instrument hits + denials (§9) for PLG analytics.

## Quality Standards

- Entitlements resolution < 5ms p99 on the hot path (API request).
- Single source of truth for "which plan has what" — never duplicate in UI code and API code.
- Every denied action emits an event (for PQL scoring + upgrade prompts).
- Overrides logged in the audit log with actor + reason.
- Limit enforcement is **atomic with the action** — checking the limit and then performing the action races; use SELECT FOR UPDATE / Redis atomic counters / DB constraints.

## Anti-Patterns

- `if (user.plan == 'pro') { … }` scattered across the codebase. Fix: single resolver.
- Entitlements stored only in the front-end (the gate is bypassable). Fix: server-side enforcement is primary; UI mirrors.
- Counting limits via SELECT then INSERT (race condition). Fix: atomic counter or DB unique constraint per period.
- Feature flags and entitlements implemented in the same code path (a feature toggle ends up gating purchases). Fix: separate runtimes.
- Hard-coding "Enterprise" overrides as `if tenant.id == 12345` branches in the code. Fix: data-driven override table.
- No upgrade prompt when a gate denies (user hits a wall, doesn't know why). Fix: every denial response carries `upgrade_url` + reason.

## Outputs

- Plan × feature × limit catalogue (single source of truth).
- Entitlements resolver service + API.
- Gate placement map (UI / API / worker).
- Override schema and admin tool.
- Denial-event analytics dashboard (PQL surface).

## Evidence Produced

| Category | Artifact | Format | Example |
|----------|----------|--------|---------|
| Architecture | Entitlements model spec | Markdown doc | `docs/saas/entitlements-model.md` |
| Release evidence | Plan × Feature × Limit matrix | Markdown table | `docs/saas/plan-feature-matrix.md` |
| Operability | Gate-denial analytics dashboard | Dashboard link | `docs/saas/gate-denials-dashboard.md` |

## References

- `references/entitlements-vs-feature-flags.md` — when each applies; how they coexist.
- `references/limit-enforcement-patterns.md` — atomic counters, period rollover, distributed quotas.
- `references/enterprise-override-model.md` — data-driven overrides for custom contracts.
- Companion: `subscription-billing`, `product-led-growth`, `saas-rate-limiting-and-quotas`, `saas-control-plane-engineering`.

<!-- dual-compat-end -->

## §1 What Entitlements Are

An **entitlement** is "this tenant is allowed to do X" — derived from the tenant's plan, contract overrides, add-ons, and trial state.

Examples:
- "Tenant on Pro plan can have up to 10 users."
- "Tenant on Free plan cannot use the API."
- "Tenant on Enterprise plan has unlimited storage."
- "Tenant on Pro+API_addon can call the API 100k times/month."
- "Tenant in trial has access to all Pro features."

The entitlements engine answers two questions:
1. **Boolean**: "Can this tenant access feature X?" (feature gate)
2. **Limit**: "Has this tenant exceeded its quota of Y this period?" (limit gate)

## §2 The Three Inputs to a Resolution

```
plan baseline   →  plan_features (tier 'pro' has feature 'reporting')
                   plan_limits   (tier 'pro' has limit 'users' = 10)

contract        →  tenant_overrides (tenant 123 has feature 'reporting' even on Free)
overrides           tenant_overrides (tenant 123 has limit 'users' = 25 by negotiation)

add-ons         →  tenant_addons (tenant 123 purchased API add-on)
                   addon_features / addon_limits
```

Resolution precedence: **override > add-on > plan baseline**. Trial state is a special case — usually a tenant in trial inherits the target plan's entitlements.

## §3 Entitlements vs Feature Flags

| | Entitlements | Feature Flags |
|---|---|---|
| Purpose | Commercial gating (what plan allows) | Rollout / experimentation (who sees what) |
| Lifetime | Long-lived; tied to plan | Short-lived; removed after rollout |
| Audience | Tenants (by plan) | Users / tenants / cohorts (by criteria) |
| Source of truth | Billing system + entitlements DB | Feature-flag service (LaunchDarkly, Unleash, PostHog) |
| Default for new code | Inherit plan capabilities | Off (gradual rollout) |
| Audited? | Yes — overrides are commercial decisions | Lightly — flag state changes |

**Keep them separate.** Both runtimes can exist in the same app, but their data models, deploy cadence, and ownership differ. Commercial decisions don't belong in the feature-flag tool, and experimentation doesn't belong in the billing-driven entitlements DB.

## §4 Data Model

```sql
-- Catalogue
CREATE TABLE plans (
    code        VARCHAR(32) PRIMARY KEY,    -- 'free', 'starter', 'pro', 'enterprise'
    display_name VARCHAR(64),
    trial_days  INT,
    is_active   BOOLEAN NOT NULL DEFAULT TRUE
);

CREATE TABLE features (
    code        VARCHAR(64) PRIMARY KEY,    -- 'api_access', 'sso_saml', 'reporting_export'
    display_name VARCHAR(128),
    description TEXT
);

CREATE TABLE plan_features (
    plan_code     VARCHAR(32) NOT NULL,
    feature_code  VARCHAR(64) NOT NULL,
    PRIMARY KEY (plan_code, feature_code),
    FOREIGN KEY (plan_code) REFERENCES plans(code),
    FOREIGN KEY (feature_code) REFERENCES features(code)
);

CREATE TABLE limits (
    code        VARCHAR(64) PRIMARY KEY,    -- 'users', 'api_calls_monthly', 'storage_gb', 'projects'
    display_name VARCHAR(128),
    period      ENUM('lifetime','monthly','daily','none') NOT NULL DEFAULT 'lifetime',
    unit        VARCHAR(32)                 -- 'count', 'bytes', 'tokens'
);

CREATE TABLE plan_limits (
    plan_code   VARCHAR(32) NOT NULL,
    limit_code  VARCHAR(64) NOT NULL,
    value       BIGINT,                     -- NULL = unlimited
    PRIMARY KEY (plan_code, limit_code)
);

-- Per-tenant overrides (enterprise contracts)
CREATE TABLE tenant_feature_overrides (
    tenant_id     BIGINT UNSIGNED NOT NULL,
    feature_code  VARCHAR(64) NOT NULL,
    enabled       BOOLEAN NOT NULL,
    reason        VARCHAR(128),
    expires_at    DATETIME,
    granted_by    BIGINT UNSIGNED,
    granted_at    DATETIME NOT NULL,
    PRIMARY KEY (tenant_id, feature_code)
);

CREATE TABLE tenant_limit_overrides (
    tenant_id     BIGINT UNSIGNED NOT NULL,
    limit_code    VARCHAR(64) NOT NULL,
    value         BIGINT,
    reason        VARCHAR(128),
    expires_at    DATETIME,
    granted_by    BIGINT UNSIGNED,
    granted_at    DATETIME NOT NULL,
    PRIMARY KEY (tenant_id, limit_code)
);

-- Add-ons (e.g., extra seats, API package, storage pack)
CREATE TABLE addons (
    code         VARCHAR(64) PRIMARY KEY,
    display_name VARCHAR(128)
);

CREATE TABLE addon_features (
    addon_code   VARCHAR(64) NOT NULL,
    feature_code VARCHAR(64) NOT NULL,
    PRIMARY KEY (addon_code, feature_code)
);

CREATE TABLE addon_limits (
    addon_code   VARCHAR(64) NOT NULL,
    limit_code   VARCHAR(64) NOT NULL,
    value_delta  BIGINT NOT NULL,           -- added to base limit
    PRIMARY KEY (addon_code, limit_code)
);

CREATE TABLE tenant_addons (
    tenant_id   BIGINT UNSIGNED NOT NULL,
    addon_code  VARCHAR(64) NOT NULL,
    quantity    INT NOT NULL DEFAULT 1,
    starts_at   DATETIME NOT NULL,
    ends_at     DATETIME,
    PRIMARY KEY (tenant_id, addon_code)
);

-- Usage counters (for limit checks)
CREATE TABLE tenant_usage_counters (
    tenant_id   BIGINT UNSIGNED NOT NULL,
    limit_code  VARCHAR(64) NOT NULL,
    period_key  VARCHAR(16) NOT NULL,       -- '2026-05' for monthly, '2026-05-11' for daily, 'lifetime'
    used        BIGINT NOT NULL DEFAULT 0,
    updated_at  DATETIME NOT NULL,
    PRIMARY KEY (tenant_id, limit_code, period_key)
);
```

## §5 Resolution Strategies

| Strategy | Latency | Freshness | Use case |
|---|---|---|---|
| **JWT-embedded** | < 0.1ms (no I/O) | Until token refresh (15min) | Boolean feature checks; tier-level limits |
| **Runtime DB lookup** | 1-5ms | Real-time | Limit checks needing current counter; just-changed plans |
| **Redis cache + DB fallback** | < 1ms hit / 5ms miss | Configurable TTL | High-traffic feature checks |
| **Hybrid** | Mixed | Mixed | **Default**: boolean checks in JWT, limit checks against Redis counter |

**Default architecture:**
```
Auth issues JWT with: plan, features[], limits[]  (snapshot at issue)
On every request:
  - Boolean feature check: read JWT (free, instant)
  - Limit check: Redis INCR + compare against JWT limit (atomic, fast)
  - Periodic re-issue (15min) refreshes JWT against DB
On plan change / override:
  - Invalidate user's JWT (force re-issue)
  - Update DB
  - Emit `entitlements.changed` event
```

## §6 Gate Placement

```
UI gates       — Hide / disable features the user can't access. NEVER trust as enforcement.
                Show "Upgrade to Pro" CTAs.
API gates      — Primary enforcement. Reject with 402 Payment Required or 403 Forbidden + upgrade info.
Worker gates   — Background jobs must check too. A bypass of API gates via direct queue insertion is an exploit.
DB constraints — For hard limits (max users per tenant), enforce at DB level too.
```

Pattern for an API gate:
```python
@require_entitlement(feature='api_access')
@require_within_limit(limit='api_calls_monthly')
def api_endpoint(request):
    Entitlements.consume_limit(tenant_id, 'api_calls_monthly', amount=1)
    # ... work
```

Denial response:
```json
{
  "error": {
    "code": "ENTITLEMENT_REQUIRED",
    "feature": "api_access",
    "message": "API access is available on the Pro plan and above.",
    "upgrade_url": "https://app.example.com/billing/upgrade?target=pro&context=api"
  }
}
```

## §7 Upgrade-Discovery UX

The gate is a sales surface, not just a wall. Every denial:
1. **Tells the user what they tried to do** and why it's blocked.
2. **Names the required plan** explicitly.
3. **Links to one-click upgrade** with deep-link context (so post-upgrade they land back where they were).
4. **Emits a `gate.denied` event** for PQL scoring + lifecycle email.

In-product approaching-limit UX:
- 70% of limit: passive indicator (progress bar).
- 80%: in-app banner suggesting upgrade.
- 95%: stronger CTA + email.
- 100%: hard block with one-click upgrade + email.

## §8 Enterprise Override Model

Enterprise contracts often grant custom entitlements. Implement as **data**, never as **code branches**:

```
Admin Console → POST /admin/tenants/123/entitlements
  body: { feature: 'sso_saml', enabled: true, reason: 'Q2 contract Acme', expires_at: '2027-04-01' }
  → writes to tenant_feature_overrides
  → writes audit_log entry
  → emits entitlements.changed event
```

Read-time resolution merges plan + add-ons + overrides automatically.

## §9 Analytics — The Gate Is the PQL Source

Every gate denial is a Product-Qualified-Lead signal. Materialize:
- `gate_denials_per_tenant_per_day` (with `feature`/`limit`, `severity`)
- `tenants_hitting_limit_80pct` (the upgrade pipeline)
- `feature_lock_topN` (which features drive the most upgrade-intent signal)

Feed into `product-led-growth` skill's PQL scoring; coordinate with `saas-lifecycle-email-orchestration` (don't email *and* in-app prompt the same denial — pick one or stagger).

## §10 Anti-Patterns

- **Single big `plans.json` file in the repo** consumed by both UI and API at compile time — drift, redeploys to change limits.
- **No override audit trail** — "who gave Acme unlimited seats?" unanswerable. Fix: every override writes audit_log.
- **Limit check via `SELECT COUNT(*)`** on every request — slow + race-condition prone. Fix: maintained counter + atomic INCR.
- **No expiry on overrides** — a promo grant becomes permanent by accident. Fix: `expires_at` mandatory for promo overrides.
- **No "upgrade context" preserved through the upgrade flow** — user clicks upgrade, lands on pricing page, forgets why. Fix: `upgrade_url` carries `context=` param; post-upgrade redirects back to original action.

## §11 Read Next

- `subscription-billing` — plan / price / subscription primitives.
- `product-led-growth` — PQL + activation; coordinate with gates.
- `saas-rate-limiting-and-quotas` — runtime quota enforcement.
- `saas-control-plane-engineering` — the admin tool that issues overrides.
- `saas-transactional-email-infrastructure` + `saas-lifecycle-email-orchestration` — coordinate gate denials with upgrade email sequences.

## AI Entitlements Addendum

When the SaaS includes AI features, the entitlement catalogue extends with AI-specific keys (model tier, context length, generations/day, tools allow-list, KB size, agent steps, BYOK, fine-tune). The mapping and gateway-enforcement contract live in `ai-entitlements-and-feature-gating`.

Cross-references:
- `ai-entitlements-and-feature-gating` — AI-specific entitlement keys + gateway enforcement.
- `ai-usage-metering-and-billing` — what is gated vs metered.
- `ai-model-gateway` — runtime enforcement chokepoint.
- `ai-on-saas-architecture` — control-plane positioning.

Related Skills

saas-tenant-data-portability-and-erasure

8
from peterbamuhigire/skills-web-dev

Use when designing the GDPR/POPIA/CCPA-compliant data export (right to portability) and erasure (right to be forgotten) workflows for a multi-tenant SaaS — cascade through every data store including warehouse/backups, retention policy, requester verification, audit trail, multi-tenant nuances of erasing one tenant's data without affecting others, and the engineering for African market regulations (Uganda DPPA, Kenya DPA, POPIA).

saas-sso-scim-enterprise-auth

8
from peterbamuhigire/skills-web-dev

Use when implementing enterprise auth on a multi-tenant SaaS — SAML 2.0 and OIDC SSO with per-tenant IdP configuration, SCIM 2.0 user provisioning/deprovisioning, custom-domain support with automated TLS, IP allowlists per tenant, audit-log API, and the migration from email-password tenants to IdP-enforced tenants. The price of entry for enterprise SaaS.

saas-seeder

8
from peterbamuhigire/skills-web-dev

Bootstrap a new SaaS from the SaaS Seeder Template: setup database, configure environment, create super admin user, and verify three-tier panel structure. Use when initializing a new multi-tenant SaaS project from this template.

saas-sales-organization

8
from peterbamuhigire/skills-web-dev

Use when designing or scaling a SaaS sales organisation — sales motions, roles (SDR/BDR/AE/CSM/SE), pipeline stages, lead-to-cash, territory design, quota/commission, sales ops fundamentals, onboarding/ramp, and hiring rubrics. Sourced from "Blueprints for a SaaS Sales Organization" (van der Kooij, Pizarro).

saas-rate-limiting-and-quotas

8
from peterbamuhigire/skills-web-dev

Use when designing per-tenant rate limits and quotas — algorithm choice (token bucket, sliding window, leaky bucket, fixed window), where to enforce (edge, gateway, service, DB connection pool, queue), per-plan / per-tier limits, soft vs hard caps, fair-queueing for noisy neighbors, headers and error responses, and how to expose quota usage to the tenant.

saas-lifecycle-email-orchestration

8
from peterbamuhigire/skills-web-dev

Use when designing the six core lifecycle email sequences (welcome/onboarding, behavioral/feature-discovery, upgrade/upsell, retention, reactivation, referral) — trigger contracts, branched automation, suppression rules, PQL/churn-risk triggers, and revenue attribution. Built on top of `saas-transactional-email-infrastructure`. Distinct from `tabler-email-templates` (HTML) and `subscription-billing` (raw billing events).

saas-erp-system-design

8
from peterbamuhigire/skills-web-dev

Use when designing configurable SaaS or ERP platforms with multi-step business workflows, domain modules, approvals, auditability, pricing and entitlements, operational reporting, and tenant-specific variation. Covers domain boundaries, workflow states, extension points, and control design.

saas-business-metrics

8
from peterbamuhigire/skills-web-dev

Complete SaaS metrics framework covering revenue (MRR/ARR/ARPU), growth (CAC/LTV/payback), retention (churn/NRR/GRR), engagement, customer satisfaction (NPS/CSAT/CES), unit economics, the Rule of 40, and SaaS finance basics. Use when measuring...

saas-architecture-strategy

8
from peterbamuhigire/skills-web-dev

Use when architecting or evaluating a cloud SaaS product — including choosing multi-tenant patterns, mapping deployment to IaaS, planning scaling and blast-radius isolation, aligning architecture to business capabilities, and reconciling multi-enterprise consumption requirements with operating-model constraints.

saas-admin-backoffice-tooling

8
from peterbamuhigire/skills-web-dev

Use when designing the internal admin / back-office console of a multi-tenant SaaS — tenant impersonation (audited, time-boxed), tenant lifecycle controls (suspend/restore/archive/hard-delete), billing operations (refunds, credits, plan overrides), feature-flag overrides per tenant, bulk actions (mass invite, plan migration, region migration), and the audit-log spine that backs all of it. Distinct from the customer-facing super-admin panel in `multi-tenant-saas-architecture`.

saas-accounting-system

8
from peterbamuhigire/skills-web-dev

Implement a complete double-entry accounting system inside any SaaS app. Users enter transactions naturally (sales, expenses, inventory) while the system auto-posts journal entries under the hood. Produces both user-friendly reports and technical...

multi-tenant-saas-architecture

8
from peterbamuhigire/skills-web-dev

Use when designing or reviewing a multi-tenant SaaS platform — tenant isolation model, three-panel separation (super admin, franchise admin, end user), zero-trust enforcement, audit trails, and per-tenant permission overrides. Unlike `modular-saas-architecture` which focuses on pluggable business modules, this skill defines the tenancy and auth boundaries that every module inherits.