Plans
The Plans view is where operators define the subscription tiers Lumio offers — their pricing, the numeric limits they impose, and the feature grid that maps plan tier to feature availability. Every account is tied to exactly one plan; the plan's defaults cascade through the system unless an account has an override (see Accounts).
Where to find it
Admin sidebar → Plans (/plans). Detail pages live at /plans/\{id\}.
Quick start
- Open Admin → Plans to see the list.
- Click a plan (e.g.
pro) to open its detail page. - Review pricing, numeric limits, and the Feature Grid at the bottom.
- Pick a category tab in the Feature Grid and flip the switch on any feature to allow or disallow it for this plan.
- Each switch saves on click — there is no separate Save button. The change propagates to every account on the plan that does not have a per-account override.
Detailed walkthrough
Plans list (/plans)
Columns:
- Name — display name (e.g. "Pro").
- Slug — machine identifier (
free,pro, …). - Monthly / Yearly — configured prices.
- Overlays — max overlays allowed.
- Integrations — max integrations allowed.
- Features — count of features allowed on the plan.
- Visibility — Public or Hidden.
- Actions — View / Edit / Delete, each gated on the matching
plans:*permission.
A New Plan button sits above the table, gated on plans:create. Sort order is a plan field but is not a list column.
Plan detail (/plans/[id])
Multiple sections:
- Slug / Description — plan identity.
- Price (Monthly) / Price (Yearly) — Stripe-linked prices.
- Plan Limits — Max Overlays, Max Widgets, Max Storage, Max Upload Size, Max Integrations, Max Accounts, and Chat Retention (rendered as "Unlimited" when
0). The plan API additionally returnsmaxCommands(the bot-command cap,0= unlimited) — a canonicalplanscolumn seeded per tier (Free 25, Pro 200) and enforced by the bot-command module. It is exposed read-only on the plan API today and adjusted via seed/migration rather than the editor form. - Feature Grid — "Toggle which features are allowed for accounts on this plan. Global kill-switches and account defaults are managed in Feature Flags." It contains:
- A search box filtering on label and key, and an "{allowed} of {total} allowed" summary.
- One tab per feature-flag category, each with an
allowed/totalbadge. - Inside a tab, one row per flag: the label, the key in monospace, and a switch.
Each row also carries a state badge:
- Globally off (amber) when the flag's global kill-switch is off. The switch is disabled — a plan override cannot re-enable a globally disabled feature.
- Default when the plan has no explicit
plan_featuresrow and the flag'sdefault_for_accountsvalue is being inherited.
Flipping a switch writes immediately via PUT /api/plans/\{id\}/features. The plan detail page reads feature-flag detail (GET /api/feature-flags/detail) to render the "Globally off" state and the inherited default, but it cannot change them — global kill-switches and account defaults live on the Feature Flags page.
Admins without plans:edit see a plain Allowed / Not allowed label instead of a switch.
Visibility
Plans with visibility = hidden are not shown on the public pricing page but can still be assigned via the admin Accounts view. Typical uses: grandfathered legacy plans, enterprise-only tiers negotiated per-customer.
Common scenarios
- Raise the upload limit for Pro — open
pro, edit the plan, change Max Upload Size, save. Every Pro account without a per-account override picks up the new cap. - Ship a beta feature to one tier only — enable it in that plan's Feature Grid, leave it off on the others, and make sure the flag is not globally disabled on Feature Flags.
- Sunset a plan — set visibility to Hidden; existing accounts stay, pricing page no longer offers it.
- Apply a global kill-switch — toggle the flag off on Feature Flags; every plan's switch for that feature turns amber ("Globally off") and disables.
Permissions
| Action | Permission |
|---|---|
| View plan list / detail | plans:read |
| Create a plan | plans:create |
| Edit plan fields / feature assignments | plans:edit |
| Delete a plan | plans:delete |
| Read feature-flag detail for the grid | features:read |
Dashboard entry requires admin:access. System admins implicitly hold all of the above.
API
| UI action | GraphQL | REST |
|---|---|---|
| List plans | adminPlans | GET /v1/plans (public) |
| Create plan | adminCreatePlan | POST /v1/admin/plans |
| Update plan | adminUpdatePlan | PATCH /v1/admin/plans/\{id\} |
| Delete plan | adminDeletePlan | DELETE /v1/admin/plans/\{id\} |
| Update plan features | adminSetPlanFeature | PUT /v1/admin/plans/\{id\}/features |
| Feature-flag detail list (read-only here) | adminFeatureFlagsDetail | GET /v1/admin/feature-flags/detail |
There is no GET /v1/admin/plans REST route. The admin list is served by the adminPlans GraphQL query through the app's own /api/plans proxy route; GET /v1/plans is the public, unauthenticated pricing list.
Plan CRUD
The admin UI supports the full create / edit / delete lifecycle for plans, in addition to editing the seeded tiers. Every operation is exposed via both GraphQL and REST; see GraphQL and REST for full schemas.
Permissions
| Action | Permission |
|---|---|
| List / view plans | plans:read |
| Create a plan | plans:create |
| Edit a plan | plans:edit |
| Delete a plan | plans:delete |
System admins implicitly hold all four. The admin UI hides every action whose permission the caller does not hold.
Create flow
Use Plans → New Plan to open the create dialog.
- Slug is required and immutable. It must match the regex
^[a-z0-9]+(?:-[a-z0-9]+)*$(lowercase letters, digits, single dashes between segments) and be 2 to 40 characters long. Examples:free,pro,enterprise-legacy. Slugs must be globally unique — duplicates return409 Conflict(REST) or a"Plan slug already in use"GraphQL error. - Name, description, prices (monthly / yearly / currency), numeric limits (Max Overlays, Max Widgets, Max Integrations, Max Accounts, Max Storage, Max Upload Size, Chat Retention, Max Sounds, Max Sound File Size, Max Sound Storage), Public visibility, and sort order all mirror the editable fields on the detail page.
- Stripe IDs (
stripe_product_id,stripe_monthly_price_id,stripe_yearly_price_id) are optional. Lumio does not auto-create Stripe products. Create the Stripe product and prices manually in your Stripe dashboard, then paste the IDs into these fields so that billing flows can reference them. - On success, the new plan is created with an empty feature assignment set. Use the Feature Grid on the detail page to enable features for the new plan.
Edit flow
Open the plan detail page at /plans/\{id\} and edit any field except the slug.
- The slug is immutable after creation. If you need a different slug, create a new plan and migrate accounts to it, then delete the old one.
- Saving the form calls
adminUpdatePlan/PATCH /v1/admin/plans/\{id\}, which rewrites all editable fields and then invalidates the feature cache for every account currently on the plan. The new limits and prices take effect on the next request from those accounts — no restart required. - Changes to Stripe IDs here only update the reference in Lumio. Price changes in Lumio do NOT update Stripe. If you change a price, update the Stripe product/price in your Stripe dashboard and paste the new price ID into the corresponding field.
Delete flow
From the plan detail page (or the list row), use Delete Plan.
- Deleting a plan is blocked when any accounts still reference it. The API returns
409 Conflictwith the message"Cannot delete plan: N account(s) still reference it. Migrate them to a different plan first."Move those accounts to another plan (see Accounts) and retry. - When the plan has no accounts attached, deletion cascades through the
plan_featuresforeign key and removes every per-plan feature assignment automatically. No other data is affected. - Deletion is permanent. Slugs of deleted plans can be reused by new plans.
Stripe integration
Lumio treats Stripe IDs as manually managed references. The admin UI does not call the Stripe API to create or mutate products or prices.
- Create products and prices in your Stripe dashboard first.
- Paste the Stripe
productID intostripe_product_idand thepriceIDs (monthly + yearly) into the matching fields on the plan. - Price changes in Lumio (
price_monthly,price_yearly) only update the display price shown on marketing pages. They do not reprice any Stripe subscription. To change what subscribers pay, update the price in Stripe, create a new Stripe price if needed, and paste the new price ID into the plan. - Removing a Stripe ID from a plan (setting it to empty) disables checkout flows that depend on it until a new ID is pasted back.
Tips & gotchas
- Effective feature state for an account is the AND of: global flag on × allowed on the plan × not-explicit-account-override-off. If any of those are off, the feature is off for the account. A flag with no
plan_featuresrow for the plan falls back to the flag'sdefault_for_accountsvalue, and the public pricing matrix treats a missing row as disabled. - Stripe prices are maintained in Stripe; this page only references them. Creating a brand-new paid plan tier requires an accompanying Stripe product + price.
- Chat Retention is in days.
0means "keep forever" and renders as "Unlimited" — confirm with the data-retention policy before setting that. - Hidden plans still appear in Subscriptions and Accounts.
Related
- Feature Flags — global kill-switches
- Accounts — per-account plan and limit overrides
- Subscriptions — which accounts are on which plan
- Coupons — discounts against these plans