ProductSubscriptions & Plans

Subscriptions & Plans

One-line: a tenant’s plan decides which modules it can use; RBAC decides which of those a given user can touch. Two independent gates.

What it does

Every school is on a subscription plan. The plan carries a set of enabled feature keys (plans.features_json). A module is available to a tenant only if its key is in that set — regardless of what any user’s role permits. This is how NexSchool packages and prices modules per school.

Who uses it

  • NexSchool operators assign and change a tenant’s plan from the panel.
  • School admins experience the result: modules outside their plan simply don’t appear.

Key concepts

  • Plan — a named bundle with features_json listing enabled feature keys.
  • Plan feature key — a string like attendance, transport, hostel (defined in core/plan_features.py).
  • Subscription status — active / trial / suspended / cancelled; some routes require an active subscription.
  • Two orthogonal gates — plan gate (@require_plan_feature) and permission gate (@require_permission). A user needs both to use a feature.

The feature matrix

Plan-gated and implemented (each key backs a real module):

attendance · fees_management · notifications · schedule_management · student_management · teacher_management · class_management · timetable · transport · hostel

A missing key defaults to enabled, so adding a module and its key is a safe rollout. Always add a key alongside its module.

Not implemented — no working module behind them; intentionally out of scope until the pilot justifies them:

examinations · reports · assignments · library · inventory

These dead placeholder keys were removed from the optional-features list so the panel no longer offers toggles that do nothing.

How it works

1. Operator assigns a plan to the tenant (panel).
2. plans.features_json → the tenant's enabled_features.
3. Login/profile returns enabled_features to the client.
4. Backend: @require_plan_feature('x') blocks routes if 'x' not enabled.
5. Frontend: isFeatureEnabled('x') hides UI for disabled modules.
6. Subscription status may additionally gate routes (@require_active_subscription).

Rules & edge cases

  • Plan gate and RBAC are independent — having a permission doesn’t help if the plan lacks the feature, and vice versa.
  • Missing feature key ⇒ treated as enabled (safe default for rollout).
  • Tenant-scoped; each school’s plan is independent.

Where it lives

  • Backend: server/modules/subscription/, server/core/plan_features.py
  • Panel: plan assignment under the tenant management screens
  • Related gate logic: @require_plan_feature, isFeatureEnabled() / useFeatures()