School Management System
One platform for the whole school — a Laravel 12 API, a React web portal, and a React Native mobile app that share the same live data. This guide takes you from an empty server to a running school, then explains every module, the WhatsApp integration, and how to build and ship the mobile app.
01 · Install first
What you downloaded
The package contains three separate applications plus this documentation. They are independent codebases that talk to each other over a REST API, which means you can host them together or apart.
Backend — the API
Every rule lives here: permissions, fees, attendance, exams, WhatsApp. Both the web portal and the mobile app are just clients of this API. Install this first.
Frontend — the web portal
A single-page React app. It compiles to plain static files (HTML, JS, CSS) that any web server can serve — no Node required in production.
mobile — the app
One codebase for Android and iOS. Build an installable APK with Expo's cloud service, no Android Studio needed.
Read this section order Install the backend first and confirm it answers on its URL. Nothing else can work until it does — the web portal and the mobile app are both empty shells without it.
02 · Install first
Server requirements
Any standard shared-hosting plan with cPanel meets these. You do not need a VPS, root access, or Docker.
For the backend (required)
| Requirement | Minimum | Notes |
|---|---|---|
| PHP | 8.2 | 8.3 recommended. Set it in cPanel under MultiPHP Manager. |
| MySQL / MariaDB | 5.7 / 10.3 | MySQL 8 works. About 60 MB for a school of 1,000 students. |
| Web server | Apache or Nginx | Apache with mod_rewrite enabled — it ships with the required .htaccess. |
| Composer | 2.x | Only needed if you install dependencies yourself. See the note below. |
| Disk space | 500 MB | More if you upload lecture videos. |
Required PHP extensions
All of these are enabled by default on virtually every host. Check them in cPanel under Select PHP Version → Extensions.
bcmath ctype curl dom fileinfo gd json mbstring openssl pcre pdo_mysql tokenizer xml zip
gd and zip are not optional
gd renders student photos onto ID cards and certificates; zip drives the Excel exports. If either is missing those features fail with a 500 error and nothing else explains why.
Only for building (your own computer, not the server)
| Tool | Version | Needed for |
|---|---|---|
| Node.js | 20 LTS or newer | Compiling the web portal, and running the mobile app. |
| npm | 10+ | Ships with Node. |
| Expo CLI | latest | Mobile app only. Run via npx, no global install needed. |
| Android Studio | optional | Only if you want an emulator or a local APK build. EAS Build does not need it. |
03 · Install first
Where the files go on the server
This is the step most installations get wrong, so it is worth understanding before you upload anything. The rule is simple: the web portal goes in your public folder, and the Laravel API goes in a subfolder of it.
The layout you are aiming for
With that layout in place your two URLs are:
| What | URL |
|---|---|
| Web portal (what people visit) | https://yourschool.com/ |
| API base URL (what the apps call) | https://yourschool.com/apis/public/api/v1 |
Never put the whole Laravel folder in public_html
Only Laravel's public/ directory is meant to be reachable from the internet. If you copy the backend so that .env sits directly in public_html, anyone can download your database password by visiting yourschool.com/.env. Keeping the app in apis/ and serving apis/public is what prevents that.
Prefer a cleaner URL?
If your host lets you set the document root of a subdomain, point api.yourschool.com straight at /home/youruser/apis/public and keep the Laravel folder completely outside public_html. That is the tidiest and safest arrangement. Your API base URL then becomes https://api.yourschool.com/api/v1.
Where uploaded files are stored
Everything users upload — student photos, lecture videos, syllabus PDFs, fee receipts — is written to apis/storage/app/public/, organised in folders:
| Folder | Holds | Publicly readable? |
|---|---|---|
student-pics/, profile-pics/, students/photos/ | Student and staff photographs | No — signed links only |
study_materials/, syllabuses/, lectures/, assignments/ | Teaching material | No — signed links only |
diaries/, messages/ | Diary and message attachments | No |
expenses/, incomes/ | Accounting documents | No |
branch-logos/, system/ | School branding, shown on the sign-in page | Yes, by design |
The .htaccess shipped in apis/public/ enforces that table. Do not remove those rules — see Security for why.
05 · Install first
Installing on a VPS
If you have root access the whole install is a handful of commands. This assumes Ubuntu with Nginx, but Apache works the same way.
Backend
cd /var/www
# upload and extract the package here first
cd /var/www/school/backend
cp .env.example .env
nano .env # set APP_URL, FRONTEND_URL and the DB_* values
composer install --no-dev --optimize-autoloader
php artisan key:generate
php artisan migrate --seed
php artisan storage:link
chown -R www-data:www-data storage bootstrap/cache
chmod -R 775 storage bootstrap/cache
Nginx server block
Point the root at Laravel's public directory — never at the app folder itself.
server {
listen 80;
server_name api.yourschool.com;
root /var/www/school/backend/public;
index index.php;
client_max_body_size 64M; # lecture video uploads
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
}
# The .htaccess rules do not apply under Nginx — replicate them:
location ~ ^/storage/(student-pics|profile-pics|students/photos|diaries|messages|expenses|incomes|study_materials|syllabuses|lectures|lecture-thumbnails|assignments)/ {
deny all;
}
location ~ /\.(?!well-known) { deny all; }
}
Nginx users must copy those deny rules
The protection for student photographs and private documents ships as Apache .htaccess rules, and Nginx ignores .htaccess entirely. Without the location block above, every student photo on your server is downloadable by anyone who guesses a filename.
Frontend
cd /var/www/school/frontend
echo "VITE_API_BASE_URL=https://api.yourschool.com/api/v1" > .env.production
npm install
npm run build
cp -r dist/* /var/www/school-web/
Then a second server block for the portal, with the SPA fallback:
server {
listen 80;
server_name yourschool.com;
root /var/www/school-web;
index index.html;
location / {
try_files $uri $uri/ /index.html; # SPA routing
}
location ~* \.(js|css|woff2?|png|jpe?g|svg|webp)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
location = /index.html {
add_header Cache-Control "no-cache, no-store, must-revalidate";
}
}
Cron
crontab -e -u www-data
# add this single line:
* * * * * cd /var/www/school/backend && php artisan schedule:run >> /dev/null 2>&1
HTTPS
apt install certbot python3-certbot-nginx
certbot --nginx -d yourschool.com -d api.yourschool.com
06 · Install first
Running it locally for development
Useful for evaluating the system, customising it, or developing against it before you buy hosting.
1. Backend
cd backend
composer install
cp .env.example .env
php artisan key:generate
# create a database called sms locally, then set DB_* in .env
php artisan migrate --seed
php artisan storage:link
php artisan serve # http://127.0.0.1:8000
2. Frontend
cd frontend
npm install
echo "VITE_API_BASE_URL=http://127.0.0.1:8000/api/v1" > .env.local
npm run dev # http://localhost:5173
3. Mobile app
Covered in full from Setting up Expo onward.
Testing on a real phone
localhost means the phone itself, so it will never reach your computer. Use your machine's LAN address (http://192.168.1.20:8000/api/v1) in both the frontend and mobile config, and start Laravel with php artisan serve --host=0.0.0.0 so it accepts connections from the network.
07 · Install first
First login and setup checklist
Default credentials
A fresh install creates one account — the Super Admin who sets up everything else.
| Role | Password | |
|---|---|---|
| Super Admin | superadmin@sms.com | password123 |
The four demo logins
If you want to look around as each kind of user before setting up your own school, the package can create one account per role. They are linked to each other on purpose — the teacher takes the class the student sits in, and the parent is that student's father — so signing in as each shows the same school from four sides rather than four empty dashboards.
| Role | Password | What they see | |
|---|---|---|---|
| Branch Admin | admin@demo.com | Demo@2026 | Full control of one campus. |
| Teacher | teacher@demo.com | Demo@2026 | Their own classes, attendance and marks entry. |
| Parent | parent@demo.com | Demo@2026 | Their child's attendance, fees and results. |
| Student | student@demo.com | Demo@2026 | Their own records, timetable and lectures. |
They are created only when you ask for them. Add this to .env and migrate:
ALLOW_DEMO_SEED=true
php artisan migrate
Never set ALLOW_DEMO_SEED on a real school
These four passwords are published in this documentation, so anyone who reads it can sign in. The flag exists so that demo accounts can never appear by accident on a live installation — leave it out of .env entirely, and they are never created.
If you switched it on to explore and now want to go live, remove the line and run php artisan migrate:rollback --step=1 to delete the four accounts, or delete them from the Users screen.
Change this before you go live Every copy of this system sold ships with the same seeded password, and it is published in this documentation. Sign in, open Profile → Change Password, and set a real one as your very first action.
Then work through this order
The system is hierarchical — classes need a branch, sections need a class, students need a section. Setting things up out of order means going back and redoing them.
- Branches — even a single-campus school needs one branch record. Everything else hangs off it.
- System Settings — school name, logo, currency symbol, address. These appear on vouchers, certificates and the sign-in page.
- Academic Session — create the current year (e.g. 2026–2027) and mark it active.
- Education Levels — Primary, Middle, Secondary. Classes are grouped under these.
- Classes and Sections — Grade 1, then its sections A and B.
- Subjects, then assign them to classes.
- Grading Policy — the grade boundaries used to convert marks into A/B/C. Result cards will not render without one.
- Fee Heads and Fee Policies — tuition, transport, admission fee, and which classes pay what.
- Teachers and Staff — the system generates their login passwords automatically.
- Students — added individually, or in bulk from a spreadsheet.
- Roles — review what each role can reach before handing out logins.
Want to explore with sample data first?
php artisan db:seed --class=SchoolDataSeeder fills the system with a realistic demo school — classes, students, fee vouchers, exam results — so you can click through every screen before entering anything real. Run php artisan migrate:fresh --seed to wipe it and start clean.
08 · Configuration
Environment reference
Settings split into two places. Anything that must exist before the database does lives in .env; everything a school administrator might change themselves lives in System Settings inside the app.
Backend — apis/.env
| Key | Example | What it does |
|---|---|---|
APP_NAME | Greenfield School | Used in emails and page titles. |
APP_ENV | production | Set to local only while developing. |
APP_KEY | base64:... | Encryption key. Generated by php artisan key:generate. Never change it after go-live — it invalidates every session. |
APP_DEBUG | false | Must be false in production. |
APP_URL | https://yourschool.com/apis/public | The API's own address. Signed file links and password-reset links are built from it. |
FRONTEND_URL | https://yourschool.com | Where password-reset emails send people back to. |
DB_DATABASE | youruser_sms | Database name. |
DB_USERNAME | youruser_smsuser | Database user. |
DB_PASSWORD | — | Database password. |
SANCTUM_TOKEN_EXPIRATION | 43200 | How long a login lasts, in minutes. Default is 30 days. Lower it for stricter security. |
QUEUE_CONNECTION | database | Leave as database unless you run Redis. |
FILESYSTEM_DISK | public | Where uploads go. Leave as is. |
Frontend — .env.production
A single line, read at build time only:
VITE_API_BASE_URL=https://yourschool.com/apis/public/api/v1
Changing this needs a rebuild
Vite bakes the value into the compiled JavaScript. Editing .env.production on the server does nothing — you must run npm run build again and re-upload dist/.
Settings that live in the app, not in .env
Sign in as Super Admin and open System Settings. These take effect immediately, with no rebuild or file edit:
School identity
Name, logo, address, phone, currency symbol. Used across vouchers, certificates, result cards and the sign-in page.
Theme colour
The primary colour of the whole portal and the mobile app's login screen.
WhatsApp credentials
Phone number ID, permanent token, webhook verify token. See WhatsApp Cloud API.
Twilio credentials
Account SID, auth token, from-number for automated voice calls.
Landing page
Hero style (banner, video or slider) and the order and visibility of each section on the public home page.
Late fee rules
Fixed amount or per-day charge applied automatically once a voucher passes its due date.
09 · Configuration
Cron and the queue
Several features are not triggered by anyone clicking a button — they happen on a schedule. All of them depend on one cron entry.
The one line you need
* * * * * cd /home/youruser/public_html/apis && php artisan schedule:run >> /dev/null 2>&1
It runs every minute, but that does not mean work happens every minute — Laravel checks whether anything is due and usually exits immediately.
What stops working without it
| Feature | Consequence if cron is missing |
|---|---|
| Overdue fee reminders | Parents are never told their fee is late. |
| Late fee calculation | Late charges never appear on vouchers. |
| WhatsApp queue | Messages sit unsent forever. This is the most common support question. |
| Absence alerts | Parents are not notified when a child is marked absent. |
| Scheduled announcements | Anything scheduled for later never goes out. |
Check it is actually running
Open Settings → WhatsApp and look at the queue health readout. If it reports the scheduler as not alive, or the pending count climbs and never falls, your cron entry is wrong — usually the wrong path, or a PHP binary the host does not expose as plain php.
Many cPanel hosts need the full binary path instead: /usr/local/bin/ea-php83 artisan schedule:run.
Queue workers
Messages are queued rather than sent during the web request, so a slow WhatsApp API never makes the page hang. On shared hosting the cron entry above drains the queue for you and nothing further is needed.
On a VPS with heavy traffic, run a dedicated worker under supervisor instead:
php artisan queue:work --sleep=3 --tries=3 --max-time=3600
10 · Configuration
Email is used for password resets and optional notifications. WhatsApp handles the day-to-day parent communication.
Using your host's mail server
MAIL_MAILER=smtp
MAIL_HOST=mail.yourschool.com
MAIL_PORT=587
MAIL_USERNAME=noreply@yourschool.com
MAIL_PASSWORD=your-mailbox-password
MAIL_SCHEME=tls
MAIL_FROM_ADDRESS=noreply@yourschool.com
MAIL_FROM_NAME="${APP_NAME}"
Using Gmail
Set MAIL_HOST=smtp.gmail.com and port 587. You must use a Google App Password, not your normal account password — regular passwords are rejected on accounts with 2-factor authentication.
Test before you rely on it
Trigger a password reset from the sign-in page. If nothing arrives, check apis/storage/logs/laravel.log — SMTP failures are written there in full.
11 · Configuration
Files and permissions
The storage symlink
Laravel keeps uploads outside the public folder and exposes them through a symbolic link. php artisan storage:link creates it. If uploads save successfully but images show as broken, the link is missing or was lost during an FTP copy.
php artisan storage:link
# creates: apis/public/storage → apis/storage/app/public
Some shared hosts block symlink creation. If yours does, see the workaround in step 5 of the cPanel install.
Upload size limits
Lecture videos are the only large uploads. If they fail silently, PHP is refusing them before Laravel ever sees the request. In cPanel's Select PHP Version → Options:
| Setting | Suggested |
|---|---|
upload_max_filesize | 64M |
post_max_size | 64M |
max_execution_time | 300 |
memory_limit | 256M |
Backups
Two things need backing up, and both matter:
- The database — every record in the system. Export from phpMyAdmin, or
mysqldump -u user -p dbname > backup.sql. - apis/storage/app/public — every uploaded photograph, document and video. These are not in the database and cannot be recovered from it.
12 · Configuration
Security
The system ships hardened. This section explains what is already in place so you do not accidentally undo it, and what remains your responsibility.
What is already protected
| Protection | How it works |
|---|---|
| Student photographs are not public | Served only through short-lived signed links that expire after 12 hours. Guessing a filename gets you nothing. |
| Teaching material is not public | Syllabuses, study material and lecture video use the same signed links. |
| Private documents are sealed | Diaries, message attachments and accounting paperwork are unreachable from the web entirely. |
| Every action is permission-checked | Being signed in is never enough on its own. Each endpoint requires the specific permission for that action. |
| Branch isolation | A branch admin cannot see or touch another campus's data, even by editing a URL. |
| Login throttling | Six sign-in attempts per minute, then a lockout — password guessing is impractical. |
| Sessions expire | Logins last 30 days by default, so a token copied from a shared computer does not work forever. |
| Passwords are hashed | Stored with bcrypt and never included in an API response except for the staff who administer that account type. |
Do not delete the .htaccess in apis/public It carries the rules that keep student photographs and private documents off the open internet. Some migration tools strip dotfiles when copying between hosts — after any server move, confirm it is still there.
Running Nginx? Those rules do not apply at all, and you must copy the location block from the VPS section instead.
Your responsibilities
- Change the seeded super admin password. It is public knowledge.
- Install an SSL certificate. Without HTTPS, passwords cross the network in plain text. Most hosts offer free Let's Encrypt certificates in cPanel.
- Set
APP_DEBUG=false. Debug mode prints credentials onto error pages. - Keep the visible-password feature in mind. The system deliberately stores the generated login password in readable form, so the office can tell a parent what it is. Only staff who administer that account type can see it — but decide whether that policy suits your school.
- Take backups. Nothing else on this list matters if the data is gone.
13 · Configuration
Branding and theme
Most rebranding needs no code at all.
Without touching code
In System Settings you can set the school name, upload a logo, and choose the primary colour. The colour flows through the entire web portal, the printed vouchers and certificates, and the mobile app's login screen.
Per-branch branding
A school network can give each campus its own logo and details under Branch Settings, so a voucher printed at one campus carries that campus's identity.
Deeper changes
- Favicon and page title —
frontend/index.html, then rebuild. - Fonts and base styles —
frontend/src/index.css. - Landing page content — editable from System Settings; the layout lives in
src/pages/SchoolLandingPage.jsx. - Printed documents — the certificate and voucher templates are in
backend/.../resources/views/.
14 · Using the system
Roles and permissions
Five kinds of account, each seeing a portal built for what they actually do. What a person can reach is the combination of their account type and the permissions granted to their role.
| Role | Scope | Typical use |
|---|---|---|
| Super Admin | Every branch, every module, every setting | The owner or head of a school network. |
| Branch Admin | Their own campus only — full control within it | A campus principal. Cannot create branches or edit roles. |
| Teacher | Their own classes and students | Marking attendance, entering marks, setting assignments. |
| Student | Their own records only | Attendance, fees, results, timetable, lectures. |
| Parent | All of their children, in one account | Following every child without separate logins. |
Custom roles
Under Roles you can build a role from individual permissions — an accounts clerk who sees fees but not exam results, or a receptionist limited to admissions and visitors. Permissions are grouped by module and split into view, create, update and delete.
Branch scoping is automatic You never have to configure it. Once a user belongs to a branch, every list they open, every report they run and every record they can edit is filtered to that branch by the API itself — not merely hidden in the interface.
What each role sees




15 · Using the system
Dashboard
The first screen after signing in, and different for every role.
The admin dashboard shows live student, teacher and parent counts, fee collection against what was billed, today's attendance percentage, a fee breakdown chart, attendance by class, marks-entry progress across exams, upcoming exam tasks and recent admissions. Nothing here needs generating — it reflects the database as it stands.

16 · Using the system
Students and admissions
Students
Students
The full student directory. Search by name or registration number, filter by class, section or branch, and open any student for their complete history — attendance, fees, results and timetable in one profile. Adding a student creates their portal login automatically.

Admissions
Admissions
The intake pipeline for applicants who are not yet students. Applications arrive from the public form on your website or are entered by the office, move through review, and on approval convert into a fully enrolled student with a registration number — no re-typing.

Bulk Enrollment
Students → Bulk Enrollment
Enroll a whole class from a spreadsheet instead of one at a time. Download the template, fill it in, upload it. Rows that fail validation are reported individually so you can fix just those.

Parents
Parents
The parent directory. One parent account links to any number of children, so a family with three pupils has a single login showing all three. This link is also what decides who receives which WhatsApp alert.

Pre-Eleventh
Pre-Eleventh
A recruitment pipeline for students still at feeder schools who are being approached before 11th grade — school name, principal contact, and each prospect's status. When one enrolls, they convert into a student record in place.

Student Progress
Students → Progress
A running academic picture of one student: attendance trend, exam performance over time, and assignment completion — the screen to open before a parent meeting.

17 · Using the system
Academics
Classes and Sections
Academics → Classes
The academic backbone. Classes sit under education levels; sections divide a class into teachable groups with their own class teacher and capacity.


Subjects
Academics → Subjects
Subjects are defined once, then assigned to the classes that study them, each with its own marks total. That assignment is what drives the timetable, marks entry and result cards.

Academic Sessions
Academics → Academic Sessions
School years such as 2026–2027. Enrollments, fees and results are all scoped to a session, so last year's records stay intact and separate when you roll over.

Timetable
Academics → Timetable
The weekly schedule, period by period. Build it per class and the system checks for teacher clashes as you go. Teachers, students and parents each see the same timetable filtered to themselves.

Syllabus
Academics → Syllabus
The curriculum plan per subject and class, published to students and parents as a downloadable document.

Grading Policy
Academics → Grading Policy
The grade boundaries that turn a mark into a letter, plus the pass mark. Result cards and cumulative results read from here, so this must be set before publishing any result.

18 · Using the system
Attendance
Student Attendance
Attendance → Students
Daily marking, one section at a time, with present, absent, late and leave. The register loads with everyone marked present so a teacher only changes the exceptions. Marking a student absent can trigger an automatic WhatsApp message to the parent.

Attendance Overview
Attendance → Overview
The school-wide view of who has marked their register today and who has not. This is the screen that stops a class being quietly missed.

Teacher Attendance
Attendance → Teachers
The same daily register for teaching staff, feeding leave records and payroll reporting.

19 · Using the system
Examinations
Exam Types and Exams
Exams
Exam types are the categories — Monthly Test, Mid Term, Final Term — and carry a weighting used when combining results. Individual exams are then created per class against a type.


Exam Schedules
Exams → Schedules
The datesheet — which paper is on which date, at what time, in which room. Published to students and parents.

Marks Entry
Exams → Marks Entry
Where teachers enter obtained marks. It opens on an upload-status board showing every class and subject and whether marks have been entered yet, so an administrator can see at a glance what is outstanding. Grades are applied automatically from the grading policy.

Results
Exams → Results
Position, percentage, grade and subject breakdown per student, printable as result cards for a whole class in one run. Cumulative results combine several exams using their type weightings into a term or annual result.


20 · Using the system
Fees and finance
The most involved module. Set it up in the order below and the rest follows automatically.
Fee Heads
Fees → Fee Heads
The individual charges a school levies — tuition, admission, transport, examination, laboratory. Each is defined once and marked as recurring monthly or one-off.

Fee Head Policies
Fees → Fee Head Policies
Which class pays how much for which head. This is what lets Grade 1 pay one tuition rate and Grade 10 another, without touching individual students.

Fee Discounts
Fees → Discounts
Sibling discounts, staff-child concessions, scholarships and hardship waivers — as a percentage or a fixed amount, applied to a student and carried onto every voucher generated for them.

Generating vouchers
Fees → Generate Vouchers
Produce a month's vouchers for a whole class or the whole school in one run. The system applies each student's policies and discounts, assigns a voucher number, sets the due date, and can notify parents on WhatsApp as they are issued.


Recording payments
Fees → Payments
Record full or partial payments against a voucher by cash, bank transfer or online gateway. The outstanding balance updates immediately and a receipt is available to print. Late fees are applied automatically once a voucher passes its due date, using the rule set in System Settings.

Fee Reports
Fees → Reports
Collection against billing by class, section or date range, plus a defaulters list. Everything exports to Excel.

Accounts
Finance → Accounts
General income and expenditure beyond student fees — salaries, utilities, maintenance, donations — so the school's overall financial position is in one place.

21 · Using the system
E-Learning
Assignments
E-Learning → Assignments
Homework set by teachers with a due date and an optional attachment, visible to the class and to parents, with submission tracking.

Study Materials
E-Learning → Study Materials
Notes, worksheets and reference documents shared with a class. Downloads are served through expiring signed links, so material does not leak to the open web.

Lectures and Playlists
E-Learning → Lectures
Recorded video lessons, grouped into playlists by subject and class, with view tracking so a teacher can see who has actually watched.


Online Classes
E-Learning → Online Classes
Scheduled live sessions with a join link, plus an in-class chat. Students see only their own class's sessions.

22 · Using the system
Staff and HR
Teachers
Staff → Teachers
The teaching directory — qualifications, subjects taught, classes assigned and attendance record. Adding a teacher creates their portal login and generates a password the office can read out.

Staff and Departments
Staff
Non-teaching staff — administration, accounts, transport, support — organised into departments, which also drive what each person can reach in the system.


Leaves
HR → Leaves
Leave applications from staff and teachers, with approval or rejection and a running balance per leave type.

23 · Using the system
Communication
Messages
Communication → Messages
Internal messaging between staff, teachers and parents, with attachments. Attachments are stored privately and are not reachable by URL.

Diaries
Communication → Diaries
The daily homework diary, published per class and visible to students and parents — the digital replacement for the notebook that gets left at school.

Events
Communication → Events
The school calendar — sports day, parent evenings, holidays — shown to the audience you choose and optionally announced over WhatsApp.

24 · Using the system
Administration
Branches
Administration → Branches
Campuses. Every record in the system belongs to one, and a Super Admin switches between them from the top bar to work on a single campus at a time.

Users
Administration → Users
Every login in the system, whatever its type, with role assignment and the ability to deactivate an account without deleting its history.

Certificates
Administration → Certificates
Generate school leaving certificates, character certificates and ID cards, rendered with the student's photograph and the branch's own letterhead.

Library
Administration → Library
Book catalogue with issue and return tracking against students and staff.

Inventory and Visitors
Inventory tracks school assets and stock; the visitor log records who came to the school, who they met and when they left.


Activity Logs
Administration → Activity Logs
An audit trail of who changed what and when — the first place to look when a record does not say what someone expected.

25 · WhatsApp & calls
WhatsApp Cloud API
The system talks to parents on WhatsApp directly through Meta's official Cloud API — no third-party reseller, no per-message middleman, and no phone left plugged in at the office. Fee vouchers, payment confirmations, absence alerts and results all go out automatically.
Cost Meta gives every business a free allowance of service conversations each month, after which utility messages are charged per 24-hour conversation window at country rates. For a typical school this is a few dollars a month. You pay Meta directly; the system adds nothing.
What you need before starting
- A Facebook Business Manager account.
- A phone number that is not currently registered on WhatsApp — not WhatsApp Messenger, not WhatsApp Business. If the school's number is already in use on someone's phone, either delete that account first or use a fresh number.
- Your backend already live on HTTPS. Meta refuses to send webhooks to a plain HTTP address.
-
Create a Meta app
Go to developers.facebook.com → My Apps → Create App. Choose type Business, give it your school's name, and link it to your Business Manager account.
-
Add the WhatsApp product
On the app dashboard find WhatsApp and click Set up. Meta creates a test number for you immediately, which is enough to try everything before you commit a real number.
From the API Setup page, copy these three values:
Value Where it appears Phone number ID Under the "From" number. A long numeric string — not the phone number itself. WhatsApp Business Account ID Just below it on the same page. App ID Top of the app dashboard. -
Create a permanent access token
The token shown on the API Setup page expires after 24 hours and is only useful for a quick test. For production you need a System User token, which never expires.
In Business Settings → Users → System Users, create a system user with the Admin role. Click Add Assets and give it full control of your WhatsApp app and WhatsApp Business Account. Then Generate New Token, select your app, set expiry to Never, and tick these permissions:
whatsapp_business_messagingwhatsapp_business_managementCopy the token now Meta shows it exactly once. If you navigate away without copying it you must generate a new one.
-
Enter the credentials in the system
Sign in as Super Admin and open System Settings → WhatsApp. Fill in:
Field Value WhatsApp enabled On Phone number ID from step 2 Business account ID from step 2 App ID from step 2 Permanent token from step 3 WhatsApp number the number in international format, e.g. 923001234567Webhook verify token any random string you invent — you will paste the same one into Meta next Default country code e.g. 92for Pakistan. Local numbers stored as03001234567are converted using this.Messages per second 1is safe. Raising it risks Meta rate-limiting you. -
Point Meta's webhook at your server
Back in the Meta app dashboard, open WhatsApp → Configuration and click Edit next to Webhook. Enter:
Callback URL https://yourschool.com/apis/public/api/v1/whatsapp/webhookVerify token the exact string you invented in step 4 Click Verify and save. Meta calls your server straight away; if the tokens match it saves, and if they do not it shows an error immediately.
Then click Manage and subscribe to the
messagesfield. Without that subscription you can send messages but will never receive replies.Verification failing? Almost always one of three things: the URL is missing the
/apis/publicpart, the site is not on HTTPS, or the verify token has a stray space. Checkapis/storage/logs/laravel.log— the incoming attempt is recorded there. -
Add your real number
Once testing works, go to WhatsApp → API Setup → Add phone number, enter the school's number and verify it by SMS or call. You will also need to complete Business Verification in Business Manager before you can message beyond a handful of test recipients — Meta usually takes a few days to review.
-
Send a test
Open Communication → WhatsApp in the portal and send a message to your own number. If it arrives, the integration is complete.
Nothing arrives, no error shown? The message is queued, not lost — and the queue only moves when cron runs. Check Cron & queue first; a missing cron entry is the single most common cause.
26 · WhatsApp & calls
Message templates
WhatsApp does not let a business message someone out of the blue in free text. Anything you send first must use a template that Meta has approved in advance. The system ships with seven, already written to pass review.
| Template | Sent when |
|---|---|
fee_voucher_issued | A fee voucher is generated for a student. |
fee_payment_received | A payment is recorded against a voucher. |
fee_overdue_reminder | A voucher passes its due date. |
attendance_absent_alert | A student is marked absent or late. |
exam_result_published | An exam result is published. |
admission_status_update | An admission application is approved or rejected. |
school_notice | Circulars, events, diary entries and general announcements. |
Getting them approved
Open Communication → WhatsApp Templates. Each template shows its exact wording and a filled-in sample. Create the matching template in Meta Business Manager → WhatsApp Manager → Message Templates, using the same name, the Utility category and language en_US, and paste the body exactly as shown.

Why these get approved Meta rejects templates that read as marketing. All seven here are transactional — they tell a parent something factual about their own child — and are submitted under the Utility category, which is both more likely to be approved and cheaper to send. Approval usually takes minutes to a few hours.
The 24-hour window
Once a parent replies to you, a 24-hour window opens in which you may send ordinary free-text messages. After it closes you must use a template again. The inbox shows which conversations are still open.
27 · WhatsApp & calls
Inbox and the AI assistant
The inbox
When a parent replies, the message lands in Communication → WhatsApp Inbox. The system matches the sender's number against parent and student records and attaches the conversation to that family, so staff see who they are talking to and about which children. Numbers it cannot match are listed separately for someone to identify.

The AI assistant
Optionally, an AI assistant answers parents automatically. It is deliberately narrow: it can only read the records of children belonging to the number that messaged, and it can only answer questions about attendance, fees, results, timetable and recent announcements.
Enable it under System Settings → AI, where you can also edit the prompt that sets its tone and rules.
It cannot leak another family's data Ownership is enforced in the API, not in the prompt. Before any record is fetched the system checks that the child is actually linked to the parent who sent the message. A cleverly worded question cannot talk its way past that.
Escalation
Anything the assistant cannot answer confidently — or any message where the parent asks for a person — is flagged for a human, and a staff member can take over the conversation from the inbox at any point.
28 · WhatsApp & calls
Twilio voice calls
For urgent announcements the system can telephone parents and read the notice aloud — useful when a school closes at short notice and a message might not be read in time.
-
Get Twilio credentials
Create an account at twilio.com and buy a phone number with voice capability. From the console dashboard copy your Account SID and Auth Token.
-
Enter them in the system
In System Settings → Twilio, switch it on and enter the Account SID, Auth Token, and the Twilio number to call from in international format.
-
Send a voice announcement
Create an announcement as usual and choose Voice call as the channel. The system places a call to each parent, and when it is answered a synthesised voice reads out the notice, naming the branch it came from.
What is tracked
| Outcome | Recorded as |
|---|---|
| A person answers and hears the notice | Delivered |
| An answering machine picks up | Not delivered — the notice is left as a message instead |
| No answer, busy, or failed | Failed, with the reason |
Both channels at once An announcement can go out as a WhatsApp message and a voice call together. The parent receives both, and each is tracked separately so you can see which actually reached them.
29 · Mobile app
Setting up Expo
The mobile app is built with Expo, which means you can run it on a real phone and produce an installable APK without installing Android Studio. The app covers every role — admin, teacher, student and parent — across more than 80 screens.





What you need
| Tool | Version | Notes |
|---|---|---|
| Node.js | 20 LTS or newer | From nodejs.org. |
| Expo Go | latest | Free app from Play Store or App Store — for testing on your own phone. |
| Expo account | free | Sign up at expo.dev. Required to build an APK. |
| Android Studio | optional | Only for an emulator or a fully offline build. |
Install the dependencies
cd mobile
npm install
This takes a few minutes the first time. The project uses Expo SDK 54 with React Native 0.81.
If npm install fails
Delete node_modules and package-lock.json and try again. If it still fails, run npx expo install --fix, which pins every package to the version that matches your Expo SDK.
30 · Mobile app
Pointing the app at your server
This is the one change every buyer must make. The app ships with a placeholder address, so until you set your own the app will fail to sign in with a network error — that is expected, not a fault.
Open mobile/src/services/api.js and replace the placeholder on line 5:
// mobile/src/services/api.js
// ships as:
const API_BASE_URL = 'https://your-domain.com/apis/public/api/v1';
// change to your own server:
const API_BASE_URL = 'https://yourschool.com/apis/public/api/v1';
The value must be the same API base URL you used for the web portal — everything up to and including /api/v1, with no trailing slash.
Which address to use while developing
| Testing on | Use |
|---|---|
| Your live server | https://yourschool.com/apis/public/api/v1 |
| Android emulator, backend local | http://10.0.2.2:8000/api/v1 |
| iOS simulator, backend local | http://localhost:8000/api/v1 |
| A real phone, backend local | http://192.168.1.20:8000/api/v1 — your computer's LAN address |
A real phone cannot reach "localhost"
On a phone, localhost means the phone. Find your computer's LAN address with ipconfig on Windows or ifconfig | grep inet on macOS, use that, and start Laravel with php artisan serve --host=0.0.0.0 so it accepts connections from other devices. Both devices must be on the same Wi-Fi.
Android blocks plain HTTP by default
A release build will refuse http:// addresses silently — the app simply reports a network error. For production always use https://. This is another reason to install an SSL certificate before building your APK.
31 · Mobile app
Running the app
Start the development server
cd mobile
npx expo start
A QR code appears in the terminal along with a menu of shortcuts.
On your own phone — the quickest way
- Install Expo Go from the Play Store or App Store.
- Make sure the phone and the computer are on the same Wi-Fi network.
- Android: open Expo Go and scan the QR code. iPhone: scan it with the built-in Camera app.
The app loads on the phone in a few seconds, and edits you make on your computer appear instantly.
On an emulator or simulator
| Press | Opens | Requires |
|---|---|---|
| a | Android emulator | Android Studio with a virtual device created |
| i | iOS simulator | macOS with Xcode |
| w | Web browser | nothing — useful for a quick look |
| r | Reloads the app | — |
| j | Opens the debugger | — |
Different networks, or a stubborn firewall?
Run npx expo start --tunnel. It routes through Expo's servers so the phone can reach your computer from anywhere. It is slower, so use it only when the normal mode will not connect.
When changes do not show up
Occasionally the bundler serves a stale copy. Clearing its cache fixes it:
npx expo start --clear
32 · Mobile app
App branding
Before building for a school, change the name, icon and package identifier. All of it lives in one file.
Open mobile/app.json:
{
"expo": {
"name": "Greenfield School", // ships as "School Management System"
"slug": "greenfield-school",
"version": "1.0.0",
"icon": "./assets/icon.png",
"splash": {
"image": "./assets/splash-icon.png",
"backgroundColor": "#8d1682" // your school colour
},
"android": {
"package": "com.greenfield.school", // ships as com.yourschool.sms — MUST be unique
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#8d1682"
}
},
"ios": {
"bundleIdentifier": "com.greenfield.school"
}
}
}
The package name must be yours before you publish
It ships as com.yourschool.sms, which is a placeholder. An app identifier can never be changed once published to Google Play, and two different schools cannot share one — so set it to your own reverse-domain form, such as com.greenfield.school, before your first build.
The extra.eas block has already been removed for you, so eas init will create a project under your own Expo account. If you ever see a project ID in app.json that you did not create, delete it — a build against someone else's account will fail.
Replacing the images
| File | Size | Used for |
|---|---|---|
assets/icon.png | 1024 × 1024 | The main app icon. |
assets/adaptive-icon.png | 1024 × 1024 | Android adaptive icon. Keep the logo in the middle 66% — the edges get cropped into a circle or squircle depending on the phone. |
assets/splash-icon.png | 1284 × 2778 | The launch screen. |
Keep the same filenames and PNG format, and the app picks them up on the next build.
33 · Mobile app
Building the APK
EAS Build compiles the app on Expo's servers, so you get a real installable APK without Android Studio, without a Mac, and without configuring signing keys yourself. The free tier is enough for a school.
-
Install the EAS command line tool
npm install -g eas-cli -
Sign in
eas loginUse the free account you created at expo.dev.
-
Link the project to your account
cd mobile eas initThis writes a new project ID into
app.jsonunder your own account. Make sure you deleted the original author'sextra.easblock first, as described in App branding. -
Tell EAS you want an APK, not an AAB
This is the step people miss, so it is already configured for you — but it is worth understanding. By default EAS produces an
.aabbundle, which is what Google Play wants but which cannot be installed directly on a phone. Thepreviewprofile ineas.jsontherefore asks for an APK. Check that it still reads like this:{ "build": { "development": { "developmentClient": true, "distribution": "internal" }, "preview": { "distribution": "internal", "android": { "buildType": "apk" // ← this line gives you an APK } }, "production": { "autoIncrement": true } } } -
Build it
eas build --platform android --profile previewThe first time, EAS offers to generate an Android keystore for you — answer yes and let it manage the signing key.
Never lose that keystore Android identifies an app by its signing key. If you lose it you can never publish an update to the same app — users would have to uninstall and reinstall from scratch. EAS keeps it safe on your account; back it up with
eas credentialsif you prefer to hold your own copy.The build runs on Expo's servers and takes roughly 10–20 minutes. You can close the terminal — progress is at expo.dev under your project's Builds tab.
-
Download and install
When it finishes the terminal prints a download link, and the same link with a QR code is on the build page. Open that link on an Android phone to download the APK, then tap it to install.
Android will warn about installing from an unknown source — that is normal for any app not delivered through the Play Store. Allow it for your browser and the install proceeds.
Building locally instead
If you would rather not use Expo's servers, and you have Android Studio with a JDK installed:
cd mobile
npx expo prebuild --platform android # generates the native android/ folder
cd android
./gradlew assembleRelease
The APK appears at android/app/build/outputs/apk/release/app-release.apk. You must configure your own signing key for a release build — see the React Native documentation on signed APKs.
Rebuild whenever these change The API URL, the app name, the package name, the icon and the splash screen are all compiled into the binary. Change any of them and you need a new build — updating the server alone will not affect an APK already on a phone.
34 · Mobile app
Publishing to the stores
Google Play
-
Create a developer account
A one-off $25 fee at play.google.com/console.
-
Build an AAB, not an APK
Play requires the bundle format. The
productionprofile already produces one:eas build --platform android --profile production -
Upload and fill in the listing
Create the app in the Play Console, upload the
.aab, and complete the store listing — description, screenshots, a 512×512 icon and a 1024×500 feature graphic. You must also complete the Data Safety form and provide a privacy policy URL; a school app collects personal data, so this is mandatory. -
Submit for review
First reviews typically take a few days. Later updates are usually faster.
Submit straight from the terminal
Once the Play Console listing exists, eas submit --platform android uploads each new build for you.
Apple App Store
Requires an Apple Developer account at $99 per year. Build with eas build --platform ios --profile production and submit with eas submit --platform ios. You do not need a Mac — EAS builds iOS on its own machines.
Many schools skip the stores entirely Distributing the APK directly — a download link on the school website, or sent to parents over WhatsApp — avoids the fees, the review delays and the privacy paperwork. It works perfectly well for an app only your own school community uses.
35 · Reference
API reference
A REST API under /api/v1, authenticated with Laravel Sanctum bearer tokens. The web portal and the mobile app both use it, so anything they can do, your own integration can do too.
Authenticating
POST /api/v1/login
Content-Type: application/json
{ "login": "superadmin@sms.com", "password": "your-password" }
The response carries a token. Send it on every subsequent request:
Authorization: Bearer 1|AbCdEf...
Tokens expire after 30 days by default — adjust with SANCTUM_TOKEN_EXPIRATION.
Response format
Every endpoint answers in the same shape, so a client only ever needs to handle one:
// success
{ "success": true, "message": "Students retrieved successfully", "data": [ ... ] }
// failure
{ "success": false, "message": "Forbidden.", "errors": null }
// validation failure
{ "success": false, "message": "The given data was invalid.",
"errors": { "email": ["This email is already registered."] } }
Status codes
| Code | Meaning |
|---|---|
200 / 201 | Success. |
401 | Missing, invalid or expired token. |
403 | Signed in, but lacking the permission for this action. |
404 | Not found — or deliberately hidden from you. |
422 | Validation failed. Details in errors. |
429 | Rate limited. Applies to sign-in attempts. |
Common endpoints
| Method & path | Purpose |
|---|---|
POST /login | Sign in and receive a token. |
POST /logout | Revoke the current token. |
GET /me | The signed-in user, with roles and permissions. |
GET /students | Students, paginated and branch-scoped. |
GET /students/{id} | One student's full profile. |
GET /attendance | Attendance records, filterable by class, section and date. |
POST /attendance/bulk | Mark a whole section at once. |
GET /fee-vouchers | Vouchers with their payment status. |
GET /exams | Exams and their schedules. |
GET /dashboard | The aggregate figures behind the dashboard. |
GET /public/settings | School name, logo and theme. No authentication — used by the sign-in page. |
Branch scoping
A Super Admin may work on one campus at a time by sending a header. Users who already belong to a branch are scoped automatically and the header is ignored:
X-Branch-Id: 3
36 · Reference
Database and seeders
Migrations
159 migrations build the full schema. They are ordered, repeatable and safe to run on an existing installation — new versions of the system add migrations rather than asking you to edit tables by hand.
php artisan migrate # apply anything new
php artisan migrate:status # see what has run
php artisan migrate:fresh --seed # DESTROYS all data, rebuilds from scratch
migrate:fresh drops every table It is for development only. Running it on a live school erases every student, payment and result permanently.
The two SQL dumps
If you cannot run artisan at all, import one of these through phpMyAdmin instead of migrating. Both create all 98 tables.
| File | Contains | Use when |
|---|---|---|
sms-clean-install.sql | Schema, all 135 permissions, roles, default settings, education levels, departments, grading policy, fee heads, and one super admin. | You are setting up a real school and want to enter your own data. |
sms-demo-data.sql | Everything above plus a full sample school — classes, sections, teachers, parents, several hundred students, exams and fee vouchers. | You are evaluating the system and want to click through populated screens. |
After importing either one
Run php artisan key:generate, or set APP_KEY by hand. Without it every request fails. And if you imported the demo data, run php artisan migrate:fresh --seed before entering real records — otherwise your school will contain several hundred fictional students.
Seeders
| Seeder | Creates |
|---|---|
SuperAdminSeeder | The first super admin account. |
PermissionSeeder | Every permission in the system. |
RoleSeeder | Default roles with sensible permission sets. |
BranchSeeder | A starter branch. |
EducationLevelSeeder, ClassSeeder, SectionSeeder, SubjectSeeder | A typical academic structure. |
GradingPolicySeeder | Standard grade boundaries. |
FeeHeadSeeder, FeeHeadPolicySeeder, FeeDiscountSeeder | A working fee structure. |
SystemSettingSeeder | Default settings, including the WhatsApp and Twilio placeholders. |
SchoolDataSeeder | A full demo school for evaluation. Not run by default. |
php artisan db:seed # the standard set
php artisan db:seed --class=SchoolDataSeeder # add demo data
Going live after evaluating
If you explored with demo data and now want to start clean, run php artisan migrate:fresh --seed before entering real records. That keeps the roles, permissions and settings while removing every demo student.
37 · Reference
Troubleshooting
Start with apis/storage/logs/laravel.log — the real reason is almost always written there in full, even when the browser shows something generic.
Installation
| Symptom | Cause and fix |
|---|---|
| 500 error on every API page | Almost always permissions. Run chmod -R 775 storage bootstrap/cache. If that does not fix it, .env is missing or APP_KEY is empty — run php artisan key:generate. |
| Blank white web portal | The .htaccess did not upload. Enable "Show Hidden Files" in File Manager and check. Also confirm VITE_API_BASE_URL was set before you ran npm run build. |
| Portal loads, every page 404s on refresh | Same missing .htaccess — the server is not routing unknown paths to index.html. |
| "Network Error" on the sign-in page | The portal cannot reach the API. Open your API base URL directly in a browser: if it does not return JSON, the backend is the problem, not the portal. |
| CORS errors in the browser console | FRONTEND_URL in .env does not match the address the portal is actually served from. They must match exactly, including https and any www. |
| Migrations fail part-way | Usually PHP below 8.2, or the database user lacking full privileges. Check both, then php artisan migrate:fresh --seed. |
Files and images
| Symptom | Cause and fix |
|---|---|
| Student photos show as broken | The storage symlink is missing. Run php artisan storage:link. See Files and permissions for hosts that block symlinks. |
| Photos worked, then stopped after a server move | The symlink did not survive the copy — recreate it. Some tools copy the link as an empty folder; delete it first, then relink. |
| Large uploads fail silently | PHP's upload_max_filesize and post_max_size are too low. Raise both to 64M. |
| Downloads 404 after upgrading | Teaching material is served through expiring signed links. Confirm APP_URL exactly matches the address the API is served from — a mismatch invalidates every signature. |
| Symptom | Cause and fix |
|---|---|
| Messages queue but never send | Cron is not running. This is the most common problem of all — see Cron & queue. |
| Webhook verification fails | The URL is missing /apis/public, the site is not on HTTPS, or the verify token does not match exactly. |
| Template message rejected | The template is not approved yet, or its name in Meta does not match the name in the system character for character. |
| Replies never arrive in the inbox | You did not subscribe to the messages field under WhatsApp → Configuration → Manage. |
| Works for your number, nobody else's | You are still on the test number, which can only message a few pre-approved recipients. Add a real number and complete Business Verification. |
Mobile app
| Symptom | Cause and fix |
|---|---|
| App shows the demo school's data | API_BASE_URL in src/services/api.js was never changed. See Pointing the app at your server. |
| Network error in the built APK, fine in Expo Go | You are using an http:// address. Android blocks cleartext traffic in release builds — use HTTPS. |
| Expo Go cannot connect | Phone and computer are on different networks. Try npx expo start --tunnel. |
| Changes do not appear | Stale bundler cache. Restart with npx expo start --clear. |
| EAS build fails immediately | Usually the package name still contains "anonymous", or extra.eas.projectId points at an account you do not own. Fix both per App branding. |
| The build produced an .aab you cannot install | That is the Play Store format. Add "android": { "buildType": "apk" } to your build profile — see Building the APK. |
Useful commands
php artisan config:clear # after editing .env
php artisan cache:clear
php artisan route:clear
php artisan optimize # re-cache everything for production
php artisan migrate:status # which migrations have run
tail -f storage/logs/laravel.log
Always clear config after editing .env
In production Laravel caches configuration. Until you run php artisan config:clear, your edits are ignored — which makes it look as though a setting simply does not work.
38 · Reference
Support
Before getting in touch
Most problems are answered in Troubleshooting. If yours is not, having these ready will get it solved in one reply rather than five:
- What you were doing when it happened, and what you expected instead.
- The last 30 lines of
apis/storage/logs/laravel.log. - Your PHP version, and whether you are on shared hosting, a VPS, or local.
- A screenshot, including the browser console if it is a portal problem (F12 → Console).
What support covers
Included
Bug fixes, installation help, clarification of anything in this documentation, and help with the WhatsApp and Twilio setup.
Not included
Custom feature development, changes to your own modifications, server administration, and third-party costs such as Meta message fees or Play Store registration.
Keeping the system updated
- Back up the database and
apis/storage/app/public. - Replace the application files, keeping your
.env. - Run
php artisan migrate— nevermigrate:fresh. - Run
php artisan config:clearandphp artisan optimize. - Rebuild and re-upload the web portal, and rebuild the mobile app if it changed.
School Management System — complete documentation, version 1.0.
Laravel 12 · React 19 · Expo SDK 54. Thank you for your purchase.