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.

campuslink.district.k12.us
  • /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.

01

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.

02

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.

03

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.