Skip to main content

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

  1. Open Admin → Plans to see the list.
  2. Click a plan (e.g. pro) to open its detail page.
  3. Review pricing, numeric limits, and the Feature Grid at the bottom.
  4. Pick a category tab in the Feature Grid and flip the switch on any feature to allow or disallow it for this plan.
  5. 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:

  1. Slug / Description — plan identity.
  2. Price (Monthly) / Price (Yearly) — Stripe-linked prices.
  3. 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 returns maxCommands (the bot-command cap, 0 = unlimited) — a canonical plans column 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.
  4. 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/total badge.
    • 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_features row and the flag's default_for_accounts value 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

ActionPermission
View plan list / detailplans:read
Create a planplans:create
Edit plan fields / feature assignmentsplans:edit
Delete a planplans:delete
Read feature-flag detail for the gridfeatures:read

Dashboard entry requires admin:access. System admins implicitly hold all of the above.

API

UI actionGraphQLREST
List plansadminPlansGET /v1/plans (public)
Create planadminCreatePlanPOST /v1/admin/plans
Update planadminUpdatePlanPATCH /v1/admin/plans/\{id\}
Delete planadminDeletePlanDELETE /v1/admin/plans/\{id\}
Update plan featuresadminSetPlanFeaturePUT /v1/admin/plans/\{id\}/features
Feature-flag detail list (read-only here)adminFeatureFlagsDetailGET /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

ActionPermission
List / view plansplans:read
Create a planplans:create
Edit a planplans:edit
Delete a planplans: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 return 409 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 Conflict with 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_features foreign 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 product ID into stripe_product_id and the price IDs (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_features row for the plan falls back to the flag's default_for_accounts value, 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. 0 means "keep forever" and renders as "Unlimited" — confirm with the data-retention policy before setting that.
  • Hidden plans still appear in Subscriptions and Accounts.