Platform
One application, not a suite with a shared login
Most district platforms are several products behind one marketing page — separate schemas, separate permissions, separate audit trails, joined by SSO. CampusLink is one Next.js application against one database. That single decision is what everything below follows from.
Routing
Every module lives under one domain
Modules are paths — /Helpdesk, /Facilities,
/Safety — never subdomains. One SAML session, one cookie
domain, one CSP origin.
The practical consequence is that moving between a facilities work order and the asset it was raised against does not re-authenticate, does not lose your scope, and does not need a second system to trust the first. Cross-module links are just links.
- /Hometier-aware dashboard
- /Helpdesktickets, KB, kiosk
- /Assetsdevices, audits, labels
- /Facilitieswork orders, PM, vendors
- /Safetyincidents, drills, SDS
- /Reservationsbookings, permits, invoices
- /Adminroles, users, audit, branding
- …and the rest of the sixteen
Identity
Everyone exists exactly once
A single core_users table. Module records reference it; they
never duplicate it. A teacher who files a ticket, requests professional
development, books the library and holds a Chromebook is one row.
SAML 2.0 single sign-on
Entra, Okta or Google Workspace as the identity provider. NameID as email, RSA-SHA256 signing, attributes for display name and groups.
Claims map to roles at login
Rules match on group, title, org unit or email domain and assign roles as the session is created — so a new building secretary has the right access on their first sign-in, not after a ticket.
Nobody lands with access by accident
The first sign-in on a fresh install is promoted to Superintendent
once, guarded three ways. Everyone after that waits in
pendingApproval until an administrator activates them.
External people, scoped
Guardians, vendors and community organisations sign in by magic link into their own portals — the same directory, a different surface, sharply limited scope.
Federated identity sources
PowerSchool, Google and Entra can each contribute user data. Field priority decides who wins, and disagreements land in an identity conflict queue instead of overwriting each other.
One site registry
Buildings, warehouses and operations sites are defined once in
core_sites. Modules add their own detail keyed to a site
rather than keeping a second list of buildings.
Authorisation
Three layers, so permissions can be reasoned about
Districts change constantly — a Principal covers two buildings for a month, a director leaves, a secretary picks up badging. A permission model that cannot express "temporarily, here, until this date" ends up expressed as a permanent grant nobody revokes.
Permission primitives
The atoms — asset.check_out,
facilities.approval.approve,
restricted.pii.read. Defined in code, checked in every
route with one can() call. Not editable by
administrators, because the code depends on their meaning.
Roles
Editable bundles of primitives, seeded from Tier 1–5 templates and then adjusted per district. Every change is versioned, and a simulate tool shows exactly what a role can see before you save it.
Scoped grants
A tuple of user, role, scope type, scope id and an optional expiry. This is the layer that makes temporary access temporary: a substitute Principal at one building for two weeks needs no cleanup task, because the grant ends itself.
Permission tiers
Every primitive carries a tier, and the tier decides what ceremony a change to it requires.
| Tier | Examples | What the platform requires |
|---|---|---|
| Standard | Create a ticket, book a room, view your own assets | Granted by role. No extra ceremony. |
| Sensitive | View all tickets district-wide, manage seniority, issue fees | Granted by role, but every use is audited. |
| Protected | Edit roles, release footage, approve off-cycle payroll | Changes wait in a co-sign queue for a second administrator. Four eyes, always. |
| System-managed | Internal primitives the platform assigns to itself | Cannot be granted by hand at all. |
Break-glass access
Emergencies happen, and a system with no escape hatch gets one improvised around it — usually a shared administrator password. CampusLink gives elevated access a front door: it is granted explicitly, bounded in time, and written to the audit log as a break-glass event that is meant to be reviewed afterwards.
Role simulation and history
Before saving a role, preview the platform as someone holding it — which nav entries appear, which queues are reachable. Every saved version is kept, so "who widened this role, and when" is answerable without a database restore.
Accountability
One audit log, written by one function
Every module calls the same writeAudit() into the same
immutable table. There is no per-module log to correlate, and no
module that quietly forgot to keep one.
- Filterable by module, action and actor
- Captures every read of restricted or confidential student data
- Records the co-signer on four-eyes actions, not just the initiator
- Retained under configurable retention policies, per data class
- The first place to look when something failed — the error is usually there with full context
Data model
Namespaced by convention
One schema, with table prefixes encoding module ownership:
core_users, reservations_bookings,
supply_orders_products.
The prefix is not decoration. It makes it obvious in a query, a migration or a backup which module owns a table — and it makes a module that has started reaching into another module's tables visible in review rather than discovered in production.
Modules are switched on and off in core_module_toggles.
Disabling one hides its navigation and dashboards and preserves every
row, so a district can pilot a module for a semester and turn it off
without losing what it recorded.
Extending it
The seams are on the outside
Districts need to connect things nobody anticipated. Every extension point below is scoped, logged, and incapable of exceeding the authority of the person who set it up.
API tokens
Issue and revoke scoped tokens. A token can never carry a permission its issuer does not hold, so a delegated integration cannot quietly out-rank the person who created it.
Outbound webhooks
HMAC-SHA256 signed deliveries on module events, with a retry queue in the background worker.
Inbound webhooks
Verified endpoints with a full delivery log, so a partner system pushing data leaves a trail you can read.
Apps platform
Integrations install as Apps that declare typed contracts. They never write to the core schema directly, and conflicts between two identity sources surface as a resolvable queue rather than as silent overwrites.
Email templates
Every outbound message is a database-resident template with {{variable}} interpolation, editable by an administrator, logged to core_email_log.
Workflows
Approval chains and onboarding fan-outs are configured per district rather than compiled in, so a new approval step is a settings change.
Bring your hardest permission question
Every district has one — the person who needs to see three buildings but only one module, or the approval nobody should be able to give themselves. Ask it on the call.