Skip to content

Roles and permissions

Account roles today

Every user has one account role. It decides routing and most access.

Role Who Lands on
platform_admin Kwilo staff /admin
org_admin Runs an organisation /admin
unit_manager Runs one institution or department /admin
instructor Faculty / trainer /teach
learner Institutional learner /learn
guardian Parent of an institutional learner parent home
external_educator Marketplace creator /creator/dashboard
b2c_user Individual on Kwilo Learner /learn

Institutional accounts are admin-provisioned. There's no self-serve institutional signup.

ERP permissions

Inside Campus, access is checked in two steps, in this order:

  1. Module gate. Is the module entitled for this institution? If not, 404.
  2. Permission. Does the user hold the permission at this exact institution? If not, 403.

Permissions are module.action, with three actions per module: view, write, manage. manage is the privileged tier: refunds, waivers, publishing, schema edits.

Modules with permissions: students, fees, admissions, timetable, attendance, exam_ops, hr, library, payroll, helpdesk, id_cards, forms.

Roles that carry permissions

Role Grants
org_admin Everything, assigned at each institution in the organisation
principal Everything, at their own institution
<module>_viewer view
<module>_editor view, write
<module>_admin view, write, manage
hod, faculty Labels only, no permissions yet

Granting a staff member access to an ERP module assigns the matching editor or admin role. A staff member with no assigned roles gets only self-service: their own leave, payslips and library loans.

There's no org-wide wildcard. A permission applies only at the institution it was assigned for, which matches how database row-level security isolates each institution.

Known limits

  • Enforcement is server-side only. The web sidebar and buttons still follow the account role, so a viewer may see a button the API will refuse.
  • There's no UI to assign authority roles like principal.
  • manage can't separate duties inside a module. Someone who can refund can also waive.
  • Attendance permissions exist but aren't checked, because attendance is a core route, not an ERP router.

Where it lives

  • apps/backend/src/core/authorization/ (catalog, roles service, dependencies)
  • Each ERP module's router under apps/backend/src/erp/
  • apps/backend/src/erp/framework/role_sync.py