Work

Multi-tenant school ERP

A school management system sold as a platform: a console for onboarding and billing schools, and a full ERP inside each school. The demo tenants are “Generic Public School” and “Another Generic School”.

The problem

Schools need one place for the daily work: who is enrolled, who teaches what and when, who was present, and who still owes fees. Each school’s data has to stay completely separate from every other school’s.

The platform side needs to onboard a new school and bill it without anyone touching a database by hand.

What I built

  • A Laravel 13 API with a separate database for each tenant, and a React 19 + Vite frontend.
  • A platform console for school onboarding, billing and invoices.
  • Per school: students, staff, classes and sections, timetable, attendance, fees with overdue tracking, and a helpdesk.

Key decisions

A database per tenant
Separate databases make isolation structural rather than a matter of remembering a where-clause. Backups, restores and exports also work per school.
API plus a separate frontend
Unlike the CMS, the ERP is an application people use all day. A standalone React frontend over a clean API keeps the door open for a mobile app later.
Onboarding as code
Creating a school provisions its database, runs migrations and seeds defaults in one step from the console, so a new tenant never needs manual setup.

Stack

Laravel 13, React 19, Vite, Multi-tenant

Results

  • 540 tests passing.
  • Two demo tenants running side by side on one platform.
  • Fees with overdue tracking, so the office sees who owes what without a spreadsheet.

Screenshots

Platform console: onboarding a school
Timetable for a class section
Fees with overdue tracking

What I would do differently

I would settle how a request finds its tenant (subdomain, header or path) on day one and write it down before building screens on top of it.

Attendance would get an offline-first mode early. Classrooms do not always have good connectivity.