A parent's dashboard for Skyward Family Access (Qmlativ): current grades with the percentage and category breakdown for every grading period, missing assignments for the whole year, and what's coming up, for every student on your account.
Skyward has no parent API, so the app signs in with your Family Access login and reads the same pages your browser does. It updates on a schedule you choose and keeps what it fetches on your machine; the dashboard never waits on Skyward, and nothing is sent anywhere else.
It works only with Skyward Qmlativ, not the older Skyward SMS 2.0; schools and districts use one or the other. To tell which yours is, sign in to Family Access in a browser and look at the address bar:
- Qmlativ (works): after the site's address comes
/Student/..., and the sign-in page is under/StudentSTS/. - SMS 2.0 (doesn't work yet): the address contains
/scripts/wsisa.dll/WService=and ends in a page such assfhome01.w.
The screenshots use made-up example data.
It works on phones, and you can add it to your home screen: in Safari, Share → Add to Home Screen; in Chrome, ⋮ → Add to home screen (or Install app).
It follows your device's light or dark setting. The button at the top right lets you pick automatic, light or dark; each browser remembers its own choice.
You need Docker. In a new folder, create .env with your district's Family Access address and
your login (the address is whatever your browser shows before /Student/..., without it):
SKYWARD_BASE_URL=https://skyward.yourdistrict.org
SKYWARD_USER=your-username
SKYWARD_PASS=your-passwordDownload the compose file:
chmod 600 .env
curl -O https://raw.githubusercontent.com/andrewfraley/skyward-dashboard/main/docker-compose.ymlIn docker-compose.yml, change TZ=Etc/UTC to your timezone, as a
tz database name such as
America/New_York or Europe/London. Updates run on its clock and an e-paper display shows times
in it; left at UTC, both are hours off. Then start it:
docker compose up -dOpen http://<your server>:8080. The first update runs right away and takes about 30 seconds.
Settings go in .env or docker-compose.yml:
| Variable | Default | |
|---|---|---|
SKYWARD_BASE_URL |
Your district's Family Access address | |
SKYWARD_USER, SKYWARD_PASS |
Your Family Access login | |
SKYWARD_SYNC_CRON |
0 6-21/3 * * * |
When to update, in cron syntax: every 3 hours, 6am to 9pm |
TZ |
Etc/UTC |
Your timezone, e.g. America/Chicago: the schedule and the display's times use it |
PUID, PGID |
1000 |
The user that owns ./data |
SKYWARD_LOG_LEVEL |
INFO |
DEBUG for more detail in docker compose logs, WARNING for less |
If your password has a $, #, space or quote in it, put it in single quotes:
SKYWARD_PASS='pa$$word'. Docker Compose would otherwise read $word as a variable.
Skyward allows one sign-in per account at a time, so an update may sign you out of Skyward in
your browser. The app keeps its Skyward session in data/ and reuses it, signing in again only
when Skyward has ended it, and always as the same device, so you shouldn't get a "new sign-in"
email for every update. Keep the schedule modest.
The dashboard has no login of its own. Keep it on your home network, or put it behind a reverse proxy with authentication. See SECURITY.md.
docker compose up -d pulls the newest release (the compose file sets pull_policy: always).
To stay on a release, pin the image, e.g. image: afraley/skyward-dashboard:0.1.0. Release notes
are on the GitHub releases page.
You can also put the dashboard on a battery e-paper screen, on a shelf or the fridge: grades, missing work and what's coming up, readable from across the room without opening a browser.
- Three pages per student: Grades, Missing and Coming Up. The buttons page through them, and the labels at the bottom say where each one goes.
- Missing work stands out: the count is in large type, and in the grading period's last week the countdown to its end turns black while anything is still missing.
- Long battery life: it wakes only when the dashboard has updated, redraws only when something changed, and sleeps in between.
- Runs ESPHome: the firmware is in this repository. It supports the Seeed Studio reTerminal E1001 (7.5", 800×480), and any 800×480 ESPHome e-paper screen can use the same layout.
DISPLAY.md covers buying, flashing, the buttons, battery life and adding another display.
Everything is read-only over the local copy, except POST /api/sync.
GET /api/ping: liveness and the running versionGET /api/status: the last update, the last one that found something new (last_change), the next scheduled one, whether one is running, and whether automatic updates are paused because Skyward rejected the sign-inPOST /api/sync: update now. It needs the headerX-Requested-With: XMLHttpRequest, so a web page on another site can't start one:curl -X POST -H 'X-Requested-With: XMLHttpRequest' http://localhost:8080/api/sync. 409 if one is running, 429 within five minutes of the last.GET /api/studentsGET /api/students/{id}/courses: classes with every grading period's grade, percent and categoriesGET /api/students/{id}/assignments?status=missing|upcoming|pastGET /api/courses/{student_section_id}: one class, its assignments and grade historyGET /api/changes?student_id=: new grades, scores and missing work, newest firstGET /api/display: everything a small screen shows, in one response under a few KB: each student's current grades, missing work this grading period and what's due next, already worded ("Tomorrow", "3 days ago"), plussleep_seconds, how long a battery display can sleep before the next update brings anything new, and ahashthat changes only when something to show does. Version 1 of this shape only ever gains fields; DISPLAY.md has the details.
Interactive docs are at /docs.
Your data stays on your machine: .env holds your login and data/ holds what the app fetched.
The project repository never contains anyone's real school data. Tests use made-up, scrubbed
examples, and every commit is checked for identifying information. See
DEVELOPING.md.
See DEVELOPING.md for running from source, how the Skyward client works, and how releases are made.
If the dashboard is useful to you, you can buy me a coffee. Thank you!







