For school administrators

Linking a parent to a pupil

Linking a parent to a pupil

How a school gives a parent or guardian access to one pupil in the parent portal, and how EduSpaze makes sure the right person is linked to the right child. Decided 2026-10-21.

1. Where it happens

Students → a pupil → Parents and guardians (/school/students/<id>). Module school.guardians (sensitive): Create invites, Edit resends and restricts, Delete cancels and removes. Only the shipped School admin holds it; a school can give it to its safeguarding lead in its own roles. The section is shown only while parent access is on for the platform (Admin → Settings → Modules); with the school's own switch off, links can be prepared but the parent sees nothing until it is on.

2. The process

  1. The school invites. Name, email, relationship, and two ticks the admin makes deliberately: *they have parental responsibility, as our records show*, and *nothing stops them seeing this child's information — no court order, no safeguarding concern*. Without both, no invitation.
  2. A single-use link is emailed to the address entered (GUARDIAN_INVITE). The email never names the child. There is no separate code: the school asked for the email alone to be enough (2026-10-22), so the link is what proves the person is the one the school invited.
  3. The parent opens the link. Opening it spends nothing (mail scanners open every link). The page names the school and the child's first name, and the parent confirms *"Yes, {child} is my child."* Then they choose a password — or, if they already have a parent account (a brother or sister at this or another school), enter its password.
  4. Then, and only then, the GUARDIAN account (if new) and the GuardianRelationship are created: source MANUAL, parental responsibility as asserted, contactSuppressed = false. They are signed in to /parent, where the four gates (lib/parent/gates.ts) decide what they see.

The email address is therefore the control: check it before sending — a mistyped address sends the link to whoever owns it. If that happens, Cancel the invitation (or Remove the link if it was already accepted) at once. A guessed link is useless: it is 32 random bytes, stored only as a hash. The link lasts 14 days. Resend issues a new link; the old one stops working. If the email fails to send, the admin is shown the link to pass on themselves.

3. Afterwards

  • Restrict a link and the parent sees nothing of that child, at once, and is not told why (gate G2). Lift it only when the school is sure it may.
  • Remove a link: that child is gone from the parent's account. The account stays — they may have other children.
  • Links from the school's SIS or the demo seed show their source; the same controls apply.

4. Records

Every step is on the school's audit log: guardian.invited, guardian.invite_resent, guardian.linked, guardian.restricted, guardian.unrestricted, guardian.invite_revoked, guardian.unlinked. The invitation stores a hash of the link, never the link itself.

5. What changed

Until 2026-10-21 guardian accounts came only from the seed, standing in for an invitation flow that did not exist (discovery.md row 5), and a test held that no runtime path could grant the guardian role. That test now holds that exactly one path can — acceptInvite in lib/school/guardians.ts, after the emailed link is checked — and that the only other mention is signing in the account it has just made. There is still no self-registration: a parent cannot ask for access, only accept a school's invitation.

On 2026-10-22 the separate school code was dropped at the school's request: handing a code over in person was a second step schools did not want. GuardianInvite.codeHash is now nullable and unused (migration 20261022150000_guardian_invite_no_code); invitations made before that still work by their link alone.

Source: docs/parent-access/linking.md in the repository. Last updated with the code it describes.