Overview#
SchoolOS is a multi-tenant school management platform. One codebase serves many schools (tenants); each school sees only its own students, staff, fees, and analytics. The platform gates functionality per-school by pricing plan, so a Basic-tier school sees a different sidebar than a Premium Enterprise one.
It ships with:
- Five paid plans (Basic → Premium Enterprise), three one-time tiers, and two source-code licences.
- 7 user roles from
SUPER_ADMINdown toSTUDENT. - ~25 first-class modules: students, attendance, grading, fees, quizzes, timetable, admissions, discipline, inventory, communications, duty roster, report cards, etc.
- A REST API with per-school API keys (Premium Plus+).
- Google SSO with email-domain restriction (Premium Plus+).
- SLA uptime monitoring widget (Premium Enterprise).
Getting Started#
For a new school
- Visit /#pricing and pick a plan.
- Click Get started or Start 14-day free trial (available on Standard and Premium). You'll be taken to
/contactwith the plan pre-selected. - Submit the form. Your inquiry becomes a sales lead tagged HIGH priority.
- A super admin converts the lead into a real school workspace at
/admin/leads/[id]/convert. The conversion page pre-fills everything from the lead and, on submit, creates the school on the correct plan plus a principal account. - The super admin shares the credentials card (school code + email + generated password + login URL). The principal signs in and begins onboarding staff/students.
Free trials
Standard and Premium plans include a 14-day free trial. Trial workspaces are identical to paid workspaces and can be upgraded / downgraded at any time from the super admin panel. No credit card is required to request a trial.
Annual billing
All monthly plans offer a 15% discount when billed annually. The setup fee (KES 20–50k depending on tier) is also waived. Toggle the Monthly / Annual switch on the pricing page to see the effective monthly price and annual total.
Roles & Permissions#
Seven roles. Each user belongs to exactly one role per school (SUPER_ADMIN has no school).
| Role | Scope | Sees |
|---|---|---|
SUPER_ADMIN | Platform-wide | All schools, plans, users, testimonials, sales leads, analytics |
PRINCIPAL | One school | Full access to every module the school's plan allows |
DEPUTY | One school | Same as principal minus school-level settings / user-creation |
TEACHER | One school | Students, grades (entry + submit), attendance, lessons, duty roster |
STAFF | One school | Staff directory, calendar, duty roster, announcements, messages |
PARENT | One school | Only their child's grades, fees, attendance, report cards, calendar |
STUDENT | One school | Only their own grades, assignments, quizzes, timetable, report cards |
Plans & Pricing#
| Plan | Price | Annual | Setup | Trial | Max admins |
|---|---|---|---|---|---|
| 🟢 Basic | KES 5,000 /mo | KES 51,000 | KES 20,000 | — | 1 |
| 🔵 Standard ⭐ | KES 10,000 /mo | KES 102,000 | KES 30,000 | 14 days | 5 |
| 🟣 Premium | KES 20,000 /mo | KES 204,000 | KES 50,000 | 14 days | Unlimited |
| 🟣 Premium Plus | KES 30,000 /mo | KES 306,000 | KES 50,000 | — | Unlimited |
| 🟣 Premium Enterprise | KES 40,000 /mo | KES 408,000 | KES 50,000 | — | Unlimited |
| 🟠 Buy — Standard | KES 250,000 | one-time | incl. | — | Unlimited |
| 🟠 Buy — Professional | KES 325,000 | one-time | incl. | — | Unlimited |
| 🟠 Buy — Enterprise | KES 400,000 | one-time | incl. | — | Unlimited |
| 🔴 Source — Single | KES 800,000 | one-time | incl. | — | Unlimited |
| 🔴 Source — Unlimited | KES 3,500,000+ | one-time | incl. | — | Unlimited |
Setup fees on monthly plans are waived when you choose annual billing. Annual billing also applies the 15% discount. Cancellation terms: monthly plans — 30 days notice; annual plans — pro-rata refund within 90 days.
Features by Plan#
| Capability | Basic | Standard | Premium | Plus | Ent. |
|---|---|---|---|---|---|
| Students · Attendance · Calendar · Website | ✅ | ✅ | ✅ | ✅ | ✅ |
| Basic report cards | ✅ | ✅ | ✅ | ✅ | ✅ |
| Exams · Grading · Quizzes · Lessons | ❌ | ✅ | ✅ | ✅ | ✅ |
| Fee management · Invoices · Receipts | ❌ | ✅ | ✅ | ✅ | ✅ |
| Timetable · Duty Roster · Inventory · Health | ❌ | ✅ | ✅ | ✅ | ✅ |
| SMS / Email notifications | ❌ | ✅ | ✅ | ✅ | ✅ |
| Parent / Student / Teacher portals | ❌ | ❌ | ✅ | ✅ | ✅ |
| Advanced analytics · Mobile app | ❌ | ❌ | ✅ | ✅ | ✅ |
| Admissions · Discipline · Messaging · Bulk docs | ❌ | ❌ | ✅ | ✅ | ✅ |
| Priority support (same-day) | ❌ | ❌ | ✅ | ✅ | ✅ |
| Custom branding | ❌ | ❌ | ❌ | ✅ | ✅ |
| Single Sign-On (Google / Microsoft) | ❌ | ❌ | ❌ | ✅ | ✅ |
| REST API access | ❌ | ❌ | ❌ | ✅ | ✅ |
| Multi-campus | ❌ | ❌ | ❌ | ❌ | ✅ |
| Dedicated account manager | ❌ | ❌ | ❌ | ❌ | ✅ |
| 99.9% uptime SLA | ❌ | ❌ | ❌ | ❌ | ✅ |
Logging In#
School workspace
At /login, keep the tab on School workspace and enter:
- School code — the slug issued to the school (e.g.
school-local,greenfield) - Email — user's email address
- Password — set when the account was created
Platform admin
Switch to the Platform admin tab. The school-code field hides. Enter super-admin email and password. Redirects to /admin on success.
Google SSO
When the server has a GOOGLE_CLIENT_ID and the school's plan includes SSO, a Sign in with Google button appears below the main form. The user enters their school code first, then clicks the button. The SSO flow never creates accounts — the user must already exist in the school. See Single Sign-On below for setup steps.
Core Workflows#
Each module lives at a predictable URL under /dashboard:
| Module | URL | What it does |
|---|---|---|
| Home | /dashboard | KPIs, recent activity, stat cards; SLA widget for Enterprise |
| Students | /dashboard/students | Register / list / view students (tenant-scoped) |
| Teachers | /dashboard/teachers | Teaching staff directory |
| Parents | /dashboard/parents | Parent directory linked to student records |
| Attendance | /dashboard/attendance | Daily register (PRESENT / LATE / ABSENT) |
| Classes · Subjects | /dashboard/classes · /subjects | Taxonomy for the rest of the system |
| Timetable | /dashboard/timetable | Weekly class / subject / teacher grid |
| Lessons · Exams · Assignments · Quizzes | /dashboard/lessons etc. | Academic content + online quizzes with autograding |
| Grades · Results · Report Cards | /dashboard/grades etc. | Entry, approval, PDF publication |
| Fees | /dashboard/fees | Invoices, receipts, M-Pesa/Paystack collection |
| Admissions | /dashboard/admissions | Applicant pipeline from intake to enrolment |
| Discipline | /dashboard/discipline | Incident log + parent notifications |
| Inventory | /dashboard/inventory | Assets, books, lab equipment, stock |
| Duty Roster | /dashboard/duty-roster | Gate duty / bus duty / meal supervision |
| Messages · Bulk · Announcements | /dashboard/messages etc. | 1-to-1, bulk SMS/email, school-wide notices |
| Testimonials | /dashboard/testimonials | Principal-moderated reviews shown on landing page |
Super Admin Panel#
Accessible at /admin for users whose role is SUPER_ADMIN.
- Overview — cross-tenant analytics (schools, students, revenue).
- Schools — CRUD schools, toggle active state, manage logos/colours.
- Plans & Billing — assign a plan to any school. Feature access updates immediately on next request.
- Sales Leads — see every /contact submission (department=SALES), filter by status, search, convert a lead into a real school + principal in one form.
- Users — global directory of every user across every school; create, disable, or delete.
- Testimonials — approve / reject / delete / revert / send back to pending; filter by school; search.
- Live Monitor — SSE stream of significant events across tenants.
Converting a sales lead to a school
- Open
/admin/leads— new submissions appear at the top, HIGH-priority ones highlighted. - Click Convert to school on a lead.
- Form pre-fills: school name (from lead), school code (auto-slugged), plan (detected from the
[Plan]subject prefix), principal email + full name. - Optionally supply a password, or leave blank to auto-generate a 10-char strong one.
- Submit. Backend creates the school with the correct plan + billing type + max admins, creates the principal, marks the lead CONVERTED, and returns credentials.
- Copy the Login URL · School code · Email · Password off the success card and hand them off. The password is only visible here.
REST API & Keys#
Available on Premium Plus and Premium Enterprise plans only (feature flag API_ACCESS).
Generate a key
- Principal or deputy opens
/dashboard/api-keys. - Gives the key a name (e.g. "Payroll sync", "Zapier").
- Clicks Generate key.
- Copies the plaintext (format:
sk_live_xxxxxxxxxxxxxxxxxxxx). This is the only time the full value is shown.
Use a key
Send it as either header — both work:
# X-API-Key header
curl https://api.schoolos.geenjoroge.org/api/students \
-H "X-API-Key: sk_live_xxxxxxxxxxxx"
# Authorization header
curl https://api.schoolos.geenjoroge.org/api/fees \
-H "Authorization: Bearer sk_live_xxxxxxxxxxxx"Every request is automatically scoped to the school that issued the key. Plan-gated endpoints still respect @RequireFeature — e.g. the key will hit 403 on /api/quizzes if the school's plan doesn't include QUIZZES_ASSIGNMENTS (though Premium Plus has it).
Revoke a key
From the same /dashboard/api-keys page, click Revoke to disable it immediately (keeps the audit trail) or the trash icon to delete entirely.
Common endpoints
| Method | Path | Returns |
|---|---|---|
| GET | /api/students | List of students |
| GET | /api/students/:id | One student |
| POST | /api/students | Register student |
| GET | /api/attendance | Attendance records |
| GET | /api/grades | Grade entries |
| GET | /api/fees | Fee records |
| GET | /api/fees/summary | Totals + collection rate |
| GET | /api/quizzes | Quizzes |
| GET | /api/calendar-events | School events |
| GET | /api/admissions | Admissions pipeline |
Single Sign-On#
Google Workspace SSO is available on Premium Plus and Premium Enterprise.
Server setup (one time)
- Go to Google Cloud Console → APIs & Services → Credentials.
- Create an OAuth 2.0 Client ID of type Web application.
- Under Authorized JavaScript origins, add your frontend URL (e.g.
https://schoolos.geenjoroge.org). - Copy the client ID.
- Add
GOOGLE_CLIENT_ID=xxxxxx.apps.googleusercontent.comto backend.env. - Restart the backend.
School-level setup
- Principal opens
/dashboard/sso. - Checks Enable Google Sign-In for this school.
- Optionally adds Allowed domains (comma-separated, e.g.
yourschool.ac.ke, staff.yourschool.ac.ke). Leave empty to allow any domain. - Saves.
User flow
- User opens
/login, enters the school code. - Clicks Sign in with Google.
- Google returns an ID token. Backend verifies it, checks plan + domain allowlist, looks up the user by email within the school.
- If a matching active user exists, a session is issued and the user is redirected to the dashboard.
SLA & Uptime#
Premium Enterprise includes a 99.9% uptime commitment. The dashboard home page shows a live uptime widget with:
- Current SLA % for the last 30 days
- Target (99.9%) and whether we're meeting it
- Process uptime in days
- Database connection status
- Last-incident timestamp
Backend endpoint: GET /api/sla/uptime — requires the SLA_UPTIME feature flag.
Multi-tenancy Model#
One Postgres database, one codebase, many schools.
- Every tenant row carries a
schoolIdcolumn (foreign key toschools.id). - The
TenantMiddlewareresolves each request'sschoolIdfrom the JWT orX-School-Codeheader (SUPER_ADMIN can switch; everyone else is locked to their own school). - Every service method receives
schoolIdas its first parameter via a parameter decorator and puts it into every query. - Email uniqueness is partial:
(email, schoolId)is unique for regular users;(email)alone is unique for SUPER_ADMIN.
Schools cannot see each other's data. Even a principal calling /api/students directly gets only students from their own school.
Data & Security#
- Kenya Data Protection Act (2019) compliant. DPA available on request.
- Passwords hashed with bcrypt (10 rounds).
- JWTs signed with HS256; 7-day expiry; secret in env
JWT_SECRET. - API keys stored only as bcrypt hashes. The plaintext is shown once at generation and never again.
- SSO tokens verified server-side with Google's public keys.
- Rate limiting: 200 requests / 15 min per IP.
- Helmet enforces a strict CSP, HSTS, no inline scripts.
- Backups: automated nightly Postgres dumps on subscription plans.
- Data portability: full CSV + PDF export on any plan.
Deployment#
The platform splits into two repositories:
schoolos-backend— NestJS on Node 18+, Postgres 14+schoolos-frontend— Next.js 15 (App Router) on Node 18+
Backend checklist
- Copy
.env.example→.env, fill inDB_*,JWT_SECRET,CORS_ORIGINS. - Ensure
uuid-osspPostgres extension is available. npm install && npm run build.npm run migration:run(applies 0001 → 0005).npm run seed:demo— optional comprehensive demo data.- Run with
pm2 start dist/main.js --name schoolos-backend.
Frontend checklist
- Set
NEXT_PUBLIC_API_URLin.env.localto the backend's public URL. npm install && npm run build.pm2 start "npm run start" --name schoolos-frontend.
Nginx (recommended)
server {
server_name schoolos.example.com;
location / { proxy_pass http://localhost:3000; }
}
server {
server_name api.schoolos.example.com;
location / { proxy_pass http://localhost:4010; }
}FAQ & Troubleshooting#
Login returns "Invalid email or password"
Check the school code matches exactly (school-local not schoollocal). For super admin, switch to the Platform admin tab — super admin has no school code.
Sidebar is missing modules I expect
The school's plan doesn't include them. Go to /admin/plans as super admin and bump the plan — changes take effect immediately.
API returns 403 "Your current plan does not include…"
Same reason — plan gating. Either upgrade the plan or use a different endpoint that's covered by the current tier.
CORS errors on the frontend
The frontend origin isn't in CORS_ORIGINS. Add it (including the port in dev). Restart the backend.
Migration fails with "relation already exists"
All migrations are idempotent after 0002. If you see this on 0001, the schema was built via synchronize — safe to ignore; subsequent migrations will short-circuit.
Google SSO button doesn't appear
Four requirements, all must hold: (1) GOOGLE_CLIENT_ID set on the server, (2) the school's plan includes SSO, (3) the school has ssoEnabled: true, (4) the user is on the School workspace tab (not Platform admin).
How do I reset a lost password?
Currently a super admin can recreate the user from /admin/users (delete + recreate with a new password) or open the DB and update the passwordHash column directly with a bcrypt hash (10 rounds). A self-service password reset flow is planned.