#FE: Plans & subscription screens (from backend Phase 7)
Frontend spec for backend Phase 7: Plans (Subscription) — requirements complete 2026-10-03, not built. V8 requirement This phase finally settles how plans are sold, so it answers three questions the frontend had been blocked on: who owns the subscription (D-3), whether the credit wallet stays (D-4) and whether plan limits still apply (D-6).
i18n namespaces: plans.*, plans.admin.*, plans.change.*, plans.limits.*, plans.expiry.*, plans.coupon.*, plans.redeem.*.
#Two kinds of plan — system and custom (new 2026-10-06)
| Default / system plans | Custom plans | |
|---|---|---|
| Created by | Seeders — there from the start | The admin, in the admin panel |
| Which | Free, Newbie, Pro, Master, Business · Managed, Self Managed (V7 restructured users only, 7.14) · Legacy per-server · Lifetime | Anything new — a promo or partner plan |
| Identity | A fixed key (free, pro…) used by the backend and the V7 migration. The key never changes | No migration role |
| Admin may | Edit name, prices, limits, features, add-ons, visibility | Everything, plus trial on/off and "no downgrade" |
| Admin may not | Delete them — archive or hide only. Free cannot even be archived (free and free-forever users need it) | Delete only if no user has ever been on it; otherwise archive (7.19) |
Re-running the seeder only adds missing plans — it never overwrites the admin's changes.
Three things the frontend takes from this:
- The admin plan list needs to show which kind a plan is. A system plan's Delete is permanently off with the reason, and on Free so is Archive. A custom plan's Delete is off as soon as anyone has ever been on it — "archive instead". Those are three different disabled states, each with its own sentence.
- A fixed key exists, but it is not an invitation to branch on it. This spec has said all along that a plan name is data and never an identifier; that still holds. The key is the backend's identity for migration, so the frontend keeps rendering from data and does not special-case
freeorpro. The one honest use is showing the key in the admin list, so an admin can tell a system plan from a look-alike custom one. - The seeded list explains things this spec already describes. Managed and Self Managed being seeded plans is why 7.14 can show them only to migrated users; Legacy and Lifetime being seeded is why those existing-user states have somewhere to live. Nothing changes in those screens — it just confirms them.
#A. Admin: managing plans
| # | Feature | Screen / control | Rules from the backend doc | Frontend notes |
|---|---|---|---|---|
| 7.1 | Plans | Admin plans list + create/edit page | Create, edit, hide / show, archive a plan: name, prices, limits, allowed features, trial on / off | Full page, not a modal — there are many fields. Status is three-state (visible / hidden / archived), so show it as a badge and never silently drop archived plans that users still hold |
| 7.2 | Feature list | Checklist inside the plan editor | One list of all plan features (servers, apps, team members, share server, API rate limit per minute, and features from later phases). Each plan turns them on / off or sets a limit. Added 2026-10-05: V7's API limits are the starting values (Free 60 … Business 180) | The list comes from the API and will grow with every later phase, so render it generically: group → feature → on/off → optional number. Never a hand-written list of features — the API rate limit arriving as just another row is proof the generic rendering was the right call. On the user side it is one more line in the compare table; the frontend does not enforce it, it only reports the 429 the API returns |
| 7.24 | Plan overview | Admin dashboard page | New 2026-10-05: users per plan, upcoming renewals, and accounts close to being locked | Three lists on one page, each a link into the matching user. "Close to being locked" is the useful one — it is the day −7 to day −15 window from 7.12, so it needs the date and days left per account, not just a count |
| 7.13 | Coupons | Admin coupons screen | Codes with percent off, which plans, first payment or recurring, expiry | On the user side: one coupon field at pay time, with the discount shown before confirming |
| 7.14 | Plan visibility | A user list on the plan (migration only) | Rewritten 2026-10-05: all plans are shown to all users — new users all see the same common plans. A plan may carry a list of users who can see it for one purpose only: V7 → V8 migration. V7 "restructured" users get two plans of their own, "Managed" and "Self Managed", each with its own name, price and features, and they see only those, as in V7. The old "Master / Business only for allowed users" rule and the per-plan display name are both gone | Two corrections to earlier assumptions here. First, the plans page is the same for everyone by default, so visibility is no longer a general feature to design around — but the catalogue still must not be cached across accounts, because a migrated user legitimately sees a different list. Second, there is no display name any more: "Managed" and "Self Managed" are real plans with real names, not labels over Newbie and Business, so the frontend shows name and nothing else. Anything that was going to prefer a display name over the name should just use the name |
| 7.18 | Feature / limit change | Confirm dialog on Save | New 2026-10-05: when the admin turns a feature off or lowers a limit, users on that plan are warned by email first and the change applies from their next renewal | The Save confirm must list what will change for existing users and say "from each user's next renewal", not "now". On the user side the plan page needs a pending-change note (see 7.18 in §B). D-34: nothing in the doc stops the admin lowering a limit below what a user is already using, which the user-initiated downgrade check (7.8) would refuse — so the editor should at least show how many users would be over the new limit |
| 7.19 | Archive plan | Status on the plan | New 2026-10-05: users already on an archived plan keep it until they change plan; new users can't pick it | Added 2026-10-06: Free can never be archived (free and free-forever users need it), a system plan can never be deleted at all, and a custom plan can be deleted only if nobody has ever been on it. Confirms the 7.1 rule: archived plans must still render as somebody's current plan even though they are missing from the catalogue. Make the one-way door clear in the admin list ("N users on it, they keep it until they move") and on the user side (changing away from an archived plan cannot be undone) |
| 7.20 | One user's plan | Admin → user → "Custom plan" page | New 2026-10-05: the admin can give one user their own plan: custom price, server / app limits, cycle and expiry days (as in V7) | A per-user form, reached from the user's admin record, not from the plans list. The big frontend consequence is on the user side: the current plan may be a plan that appears in no card and no compare table, so the plan page must render the current plan from its own data and never by looking it up in the catalogue |
| 7.21 | Free forever | Toggle on the admin's user record | New 2026-10-05: the admin can mark a user free forever (as in V7) | On the user side this behaves like lifetime for the screens: no renewal date, no renew price, no expiry or credit banners — but it is a different label, so don't reuse the lifetime badge. Keep the plans list visible so they can still upgrade |
| 7.23 | Redeem-code batches | Admin codes page | New 2026-10-05: create a batch of codes giving a plan or credit: quantity, expiry, on / off, download the list, and see who used each code | A batch is the record, not the code: list batches, open one to see its codes with used / unused and by whom. Download is a file the backend produces. Switching a batch off must stop redemption without deleting history, so it is a status, not a delete |
| 7.10–7.12 | Expiry timing | Admin settings page | Added 2026-10-05: the admin sets the reminder start (default 7 days before), the warning day (default −7), the lock day (default −15), and the reminder weekdays (default Monday + Thursday) | Four settings on one page. The important consequence is on the user side: none of those numbers may be written into a screen or a translation string, because an admin can change them. Every user-facing message carries a date from the API, which also means the strings need a date placeholder, not a hard-coded "15 days" |
| 7.26 | Add-ons in a plan | Add-ons picker in the plan editor | New 2026-10-06: the admin can select and assign any available add-on to a plan (examples renamed 2026-10-06: Log Monitoring (V7 InsightHub / Monitoring Panel) — which now has its own phase too, Phase 13 (2026-10-07), and is the opposite of White label: 13.1 makes it tied to the plan, so a downgrade switches it off and removes it from every server — White label — which now has its own phase, Phase 12, and from **2026-10-06 is the one lifetime exception: when the user takes and pays a plan that carries it, White label is added to the account as purchased and stays there for life, even if they change plan later — and 12.11's latest wording (11:37) says there is no on / off switch at all, as in V7 (12.7, 12.11) — Reseller panel (V7 self-hosted panel) — now backend Phase 15 (2026-10-07), and a lifetime licence like White label (FE: Reseller panel) — and add-ons from later phases); users on that plan get them included. Not assignable: WP Toolkit — which now explicitly includes Object Cache Pro — and Premium Hosting Care, both handled separately — and Phase 14 (2026-10-07) explains the second one: Premium Hosting Care is bought per account with its own cycle and expiry date, so a plan has nothing to grant. The downgrade check (7.8) also covers add-ons in use — except White label, which a downgrade can never take away | The picker must be built from the API's add-on list, like the feature list (7.2), because later phases add add-ons. Two consequences elsewhere: the add-ons page needs an "Included with your plan" card state distinct from "Buy", and the downgrade block must list an add-on in use as a reason, not just servers and apps. Note this confirms the frontend's existing rule rather than breaking it — Premium Hosting Care is shown to every user precisely because it is never plan-assigned. It also draws a useful line for the add-ons page: a plan can include an add-on (Reseller panel, Log Monitoring → card reads Included; White label is different — its card reads Purchased, because the plan grants it once and for ever) or merely gate who sees it (WP Toolkit → always bought, never included). Object Cache Pro is not a card at all — it rides inside WP Toolkit |
| 7.1a | Plan names | Name field in the plan editor | Added 2026-10-05: the starting plans use the V7 names for now — not final — and the admin can rename any plan. Renaming only changes the name users see; users on a renamed plan keep the same plan | Reinforces the dynamic-plans rule: a plan name is data, never an identifier. Nothing may key off a name — not a card layout, not a feature check, not a test fixture, not an i18n key. A rename must never look to the user like a plan change |
#B. User: the plan screens
| # | Feature | Screen / control | Rules | Frontend notes |
|---|---|---|---|---|
| 7.3 | Plan owner | Shown on the plan page | One plan per owner, and the limits count across all their organizations | Say this plainly on the page ("this plan covers all your organizations"), because a member of someone else's organization will see no plan of their own. Members see the owner's limits, not a buy button |
| 7.4 | View plan | Plan page header | Current plan, cycle, days left, renew price, servers / apps used vs allowed | Two usage bars (servers, apps) with the exact numbers, plus days left as a relative line and the exact date on hover |
| 7.5 | Pay from credit | Confirm-and-pay dialog | Plans are paid from the user's credit balance, tax added, and every payment is saved as a charge | The dialog shows price, tax, credit used and the remaining balance before the button. No card form here — paying means having credit |
| 7.6 | Trial | Banner on the plan page, then a blocking choose-plan state | New users get a trial; the admin sets the number of days. New 2026-10-05: when the trial ends, the user must pick a paid plan to continue | Show days left, never a fixed number. The end of a trial is now a gate, not a banner: the app stops at the plans page until a paid plan is bought. It needs its own state — different from expired (which follows a paid plan) and from locked (day −15) — with plain copy about what still works. D-33: the catalogue contains a Free plan, yet 7.6 says the user must pick a paid one, so it is unclear whether Free may be chosen at the end of a trial |
| 7.7 | Choose / change plan | Plans page → confirm-and-pay | Monthly or yearly. Unused days of the old plan come back as credit, and the new plan starts a fresh 30 / 365 days. Not enough credit → "Add $X credit first". Added 2026-10-05: before paying the user sees a preview — credit back, amount to pay, new end date | The dialog must show the credit coming back and the amount due, so a change never looks like a double charge. The backend now requires the new end date in that preview too, which settles what the dialog contains: credit back · amount to pay · new end date. "Add $X credit first" links straight to adding credit and returns to this dialog. The preview is a backend quote — the frontend never works out the end date itself |
| 7.8 | Downgrade checks | Blocking state on the plan card | Blocked if servers / apps are over the new plan's limits, or a feature in use (team members, shared servers…) is off in the new plan. Added 2026-10-06 (7.26): an add-on in use that the new plan does not include also blocks it — with one exception, White label, which is lifetime and survives any plan change (12.11). Log Monitoring is the clearest case of the rule (13.1, 2026-10-07): a downgrade removes it from every server — and 13.7, answered the same day, adds that every collected report is deleted with it. So the block, or at least the warning, must name those servers and say the reports go, because there is no export and reinstalling starts from zero. The admin can also mark a plan "no downgrade" | Check before the final button and list exactly what is in the way ("3 servers over the limit", "shared servers are not in this plan", "Log Monitoring is not included in that plan — 3 servers would stop being monitored"), each with a link to fix it. A "no downgrade" plan shows the reason instead of a dead button |
| 7.9 | Cancel / Resume | Cancel dialog · Resume action | Cancel stops auto-renew; resume turns it back on | Make clear that cancelling does not end the plan today: it runs to the end of the period. The plan page then shows "ends on |
| 7.10 | Auto-renew | Banner / plan page line | On the expiry day, renewed from credit if there is enough. Changed 2026-10-05: if there isn't, Central tries again every day until the lock day (default −15), so the plan renews as soon as credit is added (as in V7) | Show "renews on |
| 7.11 | Reminders | Admin settings + the matching in-app banner | Emails before expiry if credit is short, and after expiry. Added 2026-10-05: the admin picks which weekdays reminders go out on (default Monday + Thursday) and how many days before expiry they start (default 7) | The frontend shows the matching in-app banner so the screen and the email agree. New admin screen work: a weekday picker with those two ticked, plus the reminder-start number. The user-facing banner must not copy the email's schedule — it is shown whenever the plan is in that state, not on Mondays and Thursdays |
| 7.12 | After expiry | Banners, then a locked account | Reminders → warning day (default −7): managed servers are powered off at the provider → lock day (default −15): managed servers are deleted at the provider, self-managed servers are only removed from Central (not touched at the provider), and the account is locked. The owner's team members are locked too, and while expired every server action is blocked with "Plan has expired. Please renew it" — though the user can still view their data, delete a server, and add credit / renew. All three day numbers are set by the admin (reminder start, warning day, lock day) | This is the harshest rule in the product, so the warning must be unmissable: a persistent banner with the exact date, what will happen, and "Add credit". Rewritten 2026-10-05 — the outcome now depends on the server type, which answers D-31: a managed server is powered off, then destroyed at the provider; a self-managed server only loses its Central link and keeps running. Those are completely different losses, so the banner must name the servers and say which happens to which — the same pattern the managed-server overdue warning already uses (6.12). Two dates matter now, not one: the power-off date and the lock date. Because the days are admin settings, every screen and translation string must take the date from the API — never "15 days" or "7 days" written into the UI, and never a day count worked out in the browser. The locked state needs its own screen — billing reachable, everything else closed — and it must say servers still exist at the provider. Members need their own version of that screen: they cannot pay, so it names the owner and says "ask them to add credit", with no pay button and no billing tab. Members should also see the day −7 warning before it happens, or being locked out will arrive with no notice. The expired window is now specified, so it is a real UI state, not a guess: every screen stays readable, action buttons are disabled with that one message, and exactly two things stay live — Delete server and Add credit. That is the opposite of the usual pattern, so don't grey out whole pages: keep the data visible and disable the controls |
| 7.25 | Expiry date & status | Everywhere a plan date or state is shown | New 2026-10-05: a plan stores a real expiry date (not V7's day counter); days left are worked out from it. Plan status: active / expired / locked. Central checks the expiry date on every action, so a missed nightly job cannot keep an expired plan working | Settles two things the frontend had to guess. First, status is now a field — the screens read active / expired / locked instead of deciding from a day count, which closes the old gap on Subscriptions. Second, days left are derived from a date, so always show the exact date and treat "N days left" as a label on top of it — and never count days in the browser, because a plan can expire mid-session and the next action will be refused |
| 7.18 | Pending plan change | Note on the current-plan card | New 2026-10-05: a feature turned off or a limit lowered by the admin applies from the user's next renewal (warned by email first) | A pending-change note next to the plan, with the date and exactly what changes ("from 12 Nov: 25 servers instead of 50"). Nothing changes on screen before that date, so the usage bars keep today's limit. If the user is already over the coming limit, say so and link to what to remove — the backend states no check for this case (D-34) |
| 7.22 | Unlock | Button on the locked screen | New 2026-10-05: a locked account unlocks automatically once the user adds credit and pays the plan. Servers removed at day −15 stay removed | So the locked screen is not a dead end: it shows exactly two steps — add credit, then pay the plan — and unlocking needs no support ticket. It must be honest that nothing comes back on its own, and — after the 7.12 rewrite — honest per server type: a self-managed server is still at the provider and has to be connected again from Add server → Connect a panel, but a managed server was deleted at the provider and is simply gone. So the page must never show one blanket "your servers are safe" line. The backend doc does not spell out the reconnect path, so don't promise more than "they are still at your provider" for the self-managed ones |
| 7.15 | Enterprise | Contact form | Contact form for custom plans | A short form, not a mailto link, with a clear "we'll get back to you" state |
| 7.23 | Redeem a code | Redeem code dialog | New 2026-10-05: the admin creates batches of codes that give a plan or credit (AppSumo-style deals). Users redeem a code from their account, and each code works once | One short dialog: paste the code → it says what it gives before the user confirms, because a plan code and a credit code have very different results. Then the right success state: a plan code lands on the plan page, a credit code on the wallet. The failure cases all need their own plain message — unknown, already used, expired, switched off, and not valid for this account — never one "invalid code". Where the entry point sits is a product choice: this spec puts it on the plan page and in Billing → Wallet, since those are the two things a code can give; if Bhavik would rather have it in the user menu it is a one-line move |
| 7.16 | Activity log | Nothing to build here | Every plan change, renew, cancel and resume is in the activity log | Rendered on the Audit log page |
| 7.17 | Price change | Line on the plan page + renewal banner | New 2026-10-05: when the admin changes a plan's price, users already on that plan are warned by email first, and the new price starts at their next renewal | The plan page must show the price that will actually be charged next, not the price that was paid: "renews on <date> for <new price>", with the old price struck through while a change is pending. Never show a changed price as if it applied today. Because the renew amount can move, the "not enough credit to renew" warning (7.10) must be recalculated against the new price |
#C. Plan limits everywhere else
Phase 7 says Central checks the plan before an action, and a feature that is not in the plan shows "Upgrade your plan to use this". For the frontend that means one shared pattern rather than per-screen guesses:
- One place asks "may I do this?" and returns either yes, or a reason: not in your plan, limit reached, or no permission (that one comes from the organization role, Phase 4).
- The three reasons look different on purpose: upgrade → plan page, limit → usage, permission → ask an admin.
- Buttons stay visible but disabled with the reason, so people can see what a bigger plan adds.
- The exact feature names come from the API's feature list (7.2) — the frontend never hard-codes which plan has what.
#Existing users
| Case | Rule from the backend doc | What the screens must handle |
|---|---|---|
| Normal users | Keep their current plan, cycle and days left — and V7's days-left counter becomes an expiry date at migration (added 2026-10-05) | Nothing special beyond showing the imported values. Because the counter becomes a date, a migrated plan shows the same exact date as a new one, so no screen needs a special case |
| "Restructured" V7 users | Added 2026-10-05 (7.14): they get two plans of their own — "Managed" and "Self Managed", each with its own name, price and features — and see only those, as in V7 | The one case where the plans page is not the common catalogue. Nothing special to build: the page renders whatever plans the API returns for that account, which is why the catalogue is never cached or shared between accounts. These are ordinary plans, so no "migrated plan" badge and no second code path |
| Lifetime owners | Keep their lifetime plan and extra servers, including those still paying in installments (V7 allowed 1 or 3 payments). Whether new users can buy lifetime, and how installments work in V8, is decided later (added 2026-10-05) | A plan page with no renewal date and no renew price, and no "lifetime" entry in the plans list unless the API offers one. A migrated owner can be part-way through installments, so the lifetime state needs a "paid 1 of 3" line and must not show a plan as fully paid when it isn't. No installment screens are built until the backend decides |
| Legacy per-server plans | Keep working, with an option to convert to a normal plan | A clearly-labelled legacy state plus a convert flow showing what changes. Don't force it |
| Add-ons | WP Toolkit and managed hosting come in their own phases. Premium Hosting Care now has one — backend Phase 14 (2026-10-07), which confirms why 7.26 calls it not assignable: it is bought per account with its own expiry, never included in a plan (FE: Premium Hosting Care) | No add-on screens in this phase |
#What this phase answers
| Was open | Now |
|---|---|
| D-3 Subscription per user or per organization? | Per owner (7.3), with limits pooled across all their organizations — the V7 model, not the per-organization one the earlier product request implied. See the conflict note on Subscriptions |
| D-4 Keep the credit wallet? Auto-renew from credit? Lifetime? Legacy plans? | Yes to all: plans are paid from credit, auto-renew takes credit on the last day, lifetime owners keep theirs (new sales undecided), legacy per-server plans keep working with a convert option |
| D-6 Do application limits still apply? | Yes — plans carry server and app limits, the plan page shows used vs allowed, and downgrades are blocked when usage is over the limit |
#Still open after this phase
Which payment gateway tops up the credit balance (D-5)— answered by backend Phase 6 on 2026-10-05: Stripe, PayPal and Instamojo, with the admin enabling each one. See FE: Billing screens.- Currency and tax rules (D-22): the doc says "tax added" but not how it is calculated or shown. Phase 6 adds "tax as V7 (Instamojo, or Stripe for India)" — a trigger, still not a rule.
- D-32 (new, from Phase 6): every successful credit payment adds +3 to the server limit (6.4), so the usage bar in 7.4 cannot simply show the plan's
allowed_servers— the API has to return the real allowance. - D-33 (new): the trial ends and the user "must pick a paid plan" (7.6), but the catalogue has a Free plan — may they choose it, or is Free only for accounts that never had a trial?
- D-34 (new): the admin can lower a limit on a plan (7.18) with no stated check, while a user's own downgrade is blocked when usage is over the limit (7.8). What happens at renewal to a user who is now over the new limit is undefined.
- Lifetime plans for new users — the backend parked this decision, and on 2026-10-05 parked lifetime installments (V7: 1 or 3 payments) with it. Migrated owners can be mid-installment, so the screens must show that without a payment flow existing.
- How app and server usage is counted across organizations (the old counting worry in D-6) is not described; the frontend just shows the numbers the API returns.