Plans, Limits, and Entitlements
Turn the shipped Free, Plus, and Max catalog into your pricing model without scattering tier checks through the application.
Verified against starter commit
2a1a04a.
Start here when replacing the demo pricing with your own. Your job is to decide which customer actions each plan allows, which quantities it limits, and what happens during payment trouble or downgrade. The starter supplies one catalog and one enforcement boundary so those decisions remain consistent.
One Entitlement Door
No route, component, or domain service imports src/config/plans.ts or compares tier === "max". Callers ask for a capability or limit through src/services/entitlements.ts:
await requireEntitlement(orgUuid, "tasks.text_to_video");
await requireEntitlement(orgUuid, "tasks.image_generation");
await enforceLimit(orgUuid, "storage.totalMb", { current, adding });
const maximum = await limitOf(orgUuid, "storage.maxFileMb");This keeps authorization stable when a tier is renamed or a fourth tier is added. Architecture tests enforce the boundary.
Default Catalog
These are working defaults, not a pricing recommendation. Rename the tiers, change the values, or add a tier before launch; keep every capability defined for every tier.
| Capability or limit | Free | Plus | Max |
|---|---|---|---|
| Organization members | 1 | 5 | 20 |
| Storage upload | Yes | Yes | Yes |
| Maximum file | 5 MB | 25 MB | 200 MB |
| Total storage | 100 MB | 5,000 MB | 50,000 MB |
| Text-to-video | No | Yes | Yes |
| Image generation | Yes | Yes | Yes |
| Tasks per month | 10 | 50 | Unlimited |
| Included monthly credits | 0 | 500 | 2,500 |
null means unlimited. Signup credits are a separate one-time grant; Free has no recurring allowance job.
The reference image workflow is enabled for every tier because credits meter its fixed five-credit cost. This lets a Free account spend the one-time signup grant while still passing through the entitlement boundary.
How the Effective Plan Is Resolved
Plans belong to the organization. subscriptions stores current Stripe or manual subscription state, while orders remains the immutable financial history.
The resolver selects the highest-ranked currently entitling subscription. active and trialing entitle; past_due receives a seven-day grace period; canceled, unpaid, incomplete, paused, or expired periods do not.
An organization may hold multiple subscriptions. All their successful period grants remain additive, but only the highest effective tier supplies feature limits. The billing screen shows every current subscription so parallel charges are not hidden.
Customize the Catalog
- Add feature and limit names to
src/types/plan.ts. - Define a value for every tier in
src/config/plans.ts. - Put commercial amounts, intervals, credit grants, currencies, and Price mappings in
src/config/billing.ts. - Guard the server operation with
requireEntitlementorenforceLimit. - Expose the serialized
PlanSnapshotto client UI through the existing provider.
Make three product decisions explicitly:
- Packaging: which capabilities are plan gates, and which are usage metered with credits.
- Payment failure: keep the shipped seven-day
past_duegrace period or choose another customer policy. - Plan changes: keep independent stacked subscriptions, or design tested upgrade, proration, and credit-adjustment rules before enabling in-place changes.
New checkout uses server-only STRIPE_PRICE_* environment variables. Legacy public aliases remain read for existing deployments and grandfathered subscriptions.
Downgrades and Overrides
Downgrades never delete data or members. Creation-time checks block new uploads, tasks, or invitations until usage falls within the new limit.
Support can set an audited organization seat override without changing billing. The precedence is:
active organization override → effective plan limit → catalog defaultManual comps are normal subscriptions rows with source = "manual"; they follow the same resolver as paid subscriptions.
Credits meter consumption after a capability is allowed. Entitlements decide whether an action is available; the credit ledger records how much it consumed.
You are ready to publish pricing when the public pricing cards, Stripe Prices, billing configuration, entitlement tests, and downgrade behavior all describe the same offer. Verify at least Free, each paid tier, past_due inside and outside grace, a manual comp, and an organization above its downgraded limit.
Next: Connect the catalog to Stripe Billing, then verify every included grant and metered action against the Organization Credit Ledger.