Skip to content
Education SaaS · Platform

Multi-tenant school ERP

A SaaS ERP platform rather than one school's system. The platform operator reviews school applications, provisions a tenant, sets a subscription plan and invoices the school. Each school then gets its own portal on its own subdomain, where administrators run campuses, academic structure, admissions, students, staff, attendance, examinations and fees, and parents track applications and fee vouchers.
In developmentNestJS 11TypeScriptPostgreSQL 17
70
database tables
219
API operations
107k
lines of source
13
roles across both sides

Built and running on a staging environment, in active development rather than general release. Figures above were counted from the codebase.

The problem

Multi-tenancy is where school platforms usually leak. One school seeing another school's students is not a bug you recover from commercially, so isolation could not depend on remembering a WHERE clause.

What was built

The working parts

Each of these exists in the delivered system, not on a roadmap.

01

Tenant isolation in the database

PostgreSQL row-level security is enabled and forced on every tenant table, and the application connects as a role that cannot bypass it. A missed filter in application code cannot leak another school's data.

02

Operator console

Onboarding request review, tenant provisioning, subscription plans, module entitlements, platform invoicing, operator team management and an audit trail.

03

School administration

Campuses and branches, academic years, terms, classes, sections, subjects and per-school branding, with a campus scope selector that switches the whole navigation rail.

04

Admissions pipeline

Configurable stages with stage history and events, guardian records, offers, and a public application form per branch that needs no login.

05

Student and staff records

Enrollment, guardians, employee records and Elasticsearch-backed search, with CSV and Excel export.

06

Attendance, timetable and examinations

Separate student and employee attendance with holidays, timetable periods and slots, class and teacher subject mapping, exams, grading scales and results.

07

Fees and vouchers

Fee heads, fee structures, voucher templates and generation, payments, and a payment-proof review flow. Money is stored as integer minor units with an explicit currency, never as a float.

08

Dynamic form builder

Admission, student and teacher forms are configurable records with typed fields and stored submissions, so a school changes a form without a release.

09

Permissions as data

64 wildcard-capable permission codes resolved per user at login, so changing what a role can do is a data change rather than a redeploy.

10

Background jobs and exports

A separate worker process on BullMQ handles queued work, with Excel and PDF generation off the request path.

Screens

Where people actually work.

  • 01Operator overview and school directory
  • 02Onboarding request review
  • 03Subscription plans and platform invoices
  • 04School admin dashboard with campus scope
  • 05Academic structure and campus subjects
  • 06Admissions pipeline and application detail
  • 07Students, teachers and attendance
  • 08Fee setup and voucher generation
  • 09Public admission form per branch
  • 10Parent portal: applications and vouchers
Stack
NestJS 11TypeScriptPostgreSQL 17Prisma 6Redis 7BullMQElasticsearch 8React 19Vite 6Redux ToolkitTailwind CSS 4Dockernginx
Integrations
  • SMTP email with templated notifications
  • Elasticsearch for student and staff search
  • Redis and BullMQ for queued work
  • Cloudflare DNS with a wildcard TLS certificate
  • GitHub Actions deploying to a Docker Compose host
Design decisions

The calls that matter later.

The choices that separate software people tolerate from software they rely on.

01

Row-level security instead of a trusted query layer

Tenant context is set as a transaction-local Postgres setting and policies are forced, so isolation holds even if a developer writes a raw query. It costs a little migration discipline and removes an entire class of catastrophic bug.

02

One source tree, two builds

The operator console and the school portal ship from the same repository with a build-time surface flag, so the other surface's code is tree-shaken out. Shared components stay shared without shipping the operator's endpoints to a school.

03

Money as integer minor units

Every amount is a bigint of the smallest currency unit plus an explicit three-letter currency. Floating-point money in a fees system produces disputes that cost more than the feature.

Want one built around your operation?

Discovery takes one to two weeks and produces a process map, wireframes and a fixed quote. Useful documents even if you take them somewhere else.

We reply to every enquiry within one business day.