#Plans
A plan is what can be bought. It is not a pricing card. It's the definition behind a relationship:
UserOrganizationSubscriptionPlanPlan limitsPlan featuresActual usage
#What V7 sells (reference only, do not hard-code)
These values show the shape the V8 API must return. They are V7 code constants, not V8 decisions.
| Concept | V7 values |
|---|---|
| Tiers | Free, Newbie, Pro, Master, Business (+ Legacy). In V7, restructured users only saw Newbie (shown as "Managed") and Business (shown as "Self Managed"). V8 (7.14, corrected 2026-10-05): all plans are common to all users; those two become real plans of their own — "Managed" and "Self Managed", each with its own name, price and features — shown only to migrated restructured users V8 requirement. The backend confirmed on 2026-10-05 that these names are only a starting point — not final — and the admin can rename any plan; users on a renamed plan keep the same plan V8 requirement |
| Cycles | Monthly, Yearly (charged as 10 months), Lifetime |
| Plan types | server_based (old: one subscription per server), subscription_based (current: one account subscription), Lifetime |
| Limits | allowed_servers, allowed_applications per plan (e.g. Free 0 servers / 1 app, others 200 apps) |
| Features | Permission rows enabled per tier (<tier>_default = 1) |
Not in V8 — Bhavik, 2026-10-09 Conflict: there is no trial; a new user picks a plan at /billing/plan right after sign-up. The backend's trial rules (7.6, the per-plan switch in 7.1, the admin trial mode in 17.19) are recorded as a conflict, not followed. Previous text: trial_mode + trial_days from settings. Newbie/Free need no payment. V8 (7.6, 2026-10-05): when the trial ends the user must pick a paid plan to continue — a blocking state, not a banner V8 requirement. Whether the Free plan counts as "continuing" is open (D-33) | |
| Discounts | Recurring and lifetime discounts per tier (env config), promo codes, discount-percentage endpoint. Phase 6 (6.4) keeps promo codes on top-ups, and refuses them at or below the minimum amount V8 requirement |
| Rate limits | API rate limit per tier (free 60 … business 180 req/min). V8 (7.2, 2026-10-05): this is now a normal row in the plan feature list, with the V7 numbers as starting values — so it appears in the compare table like any other limit V8 requirement |
#What the plans screen needs from the API
| Data | Why | Status |
|---|---|---|
| Plan catalog: id, name, description, cycles + price per cycle, currency, limits, feature list, trial availability, is-current, can-select | Render plans without hard-coding. No display name — 7.14 removed it on 2026-10-05, so the name is the only name | Missing |
| Current subscription: plan, cycle, status, renews/expires at, cancel-at-period-end | Current-plan card | Missing |
| Usage: servers used / allowed, applications used / allowed | Usage bars, limit warnings | Missing (V7 new-subscription-plan returns serverCount, applicationCount) |
| Upgrade preview: credit coming back, amount to pay, new end date (+ tax, promo) | Checkout summary — required by 7.7 since 2026-10-05, so the dialog's contents are now fixed V8 requirement | endpoint Missing (V7 remaining-credit) |
| Redeem a code: what the code gives (plan or credit), and why it was refused | Redeem dialog (7.23) V8 requirement | Missing |
| Eligibility: which plans this account may choose | Normally all of them (7.14). Only a migrated restructured user gets a shorter list, so the catalogue is per-account and never cached across accounts | Missing |
#Plan screens
- Plans (
/plans): cycle toggle (only cycles the API offers), plan cards built only from API data, the current plan marked, upgrade/downgrade buttons enabled by the API'scan_select, feature comparison table. - Current plan card (dashboard + subscription page): name, cycle, status badge, renewal date, usage bars, "Upgrade" call to action when usage ≥ 80% or a limit is hit.
- Enterprise / contact: V7 has
enterprise-contact-request. Optional. Open question
#Upgrade, downgrade, renewal, cancellation, expiration
| Action | V7 behaviour | V8 |
|---|---|---|
| Upgrade | PATCH change-subscription with plan + cycle. Requires enough credits (shortfall > $1 → "Your credit is not enough"). Charges the difference | Missing |
| Downgrade | Same endpoint. Validity recalculated from remaining credit (remainingPlanCredit). Can't go below current usage Assumption | Missing Open question |
| Renewal | PATCH renew-subscription, paid from credits. If expired 16+ days, the new period starts today. Otherwise the remaining days are added | Auto-renew retries daily until day −15 (7.10), so adding credit renews the plan by itself V8 requirement. Endpoint Missing |
| Cancel | PATCH cancel-subscription sets a cancel flag (subscription_plan_cancel). Lifetime can't be cancelled. resume-subscription undoes it | Missing |
| Expiration | Daily job decreases expires_in_days. Reminders when < 8 days left. Expired (< 0) → server actions blocked except server delete | Specified (7.12, 7.25, 2026-10-05) V8 requirement: a real expiry date with status active / expired / locked, checked on every action; while expired the user can still view, delete a server and add credit / renew. V7's behaviour is confirmed, not replaced |
| Free plan | 0 servers / 1 application in the new standard table. V7's free-forever single server is now an "extra server" on the user's record (7.21, 2026-10-08) — migrated free-forever users get extra servers = 1 | V8 requirement |
| Lifetime | Separate controller: create, upgrade, advanced lifetime | Open question keep for V8? |
#Usage limits
Action that adds usage (add server, create VPS, create app)BE: count usage in organization/accountCompare with plan limitWithin → allow | Over → 403/422 with code PLAN_LIMIT_REACHED + upgrade info
- The backend counts and decides. The frontend only shows the numbers and disables buttons as a hint.
- Applications live on OSS panels, so counting applications needs a call to every server (or a cached count). This is a real cost. Decision D-6: do application limits still apply in V8?
#Rules for the frontend
- Never hard-code prices, limits, features, cycles or currency. Every number comes from the API.
- Format money with next-intl
useFormatter().number(value, {style:'currency', currency})using the API's currency. - Show the plan
namefrom the API. There is no display name in V8 (7.14, corrected 2026-10-05): V7's "Newbie shown as Managed" trick is replaced by real plans called "Managed" and "Self Managed" for migrated users, so there is only ever one name to show.