When organisations merge, their buildings merge with them. Overnight, several hundred people need to know which building they are in, which room they have, and whether it is still theirs next Tuesday. That knowledge usually lives in one spreadsheet, maintained by one person, that nobody else dares open.
Onsite is that spreadsheet replaced properly — and built as a product rather than another internal tool. Every organisation defines its own buildings, rooms, sessions and job titles, sets itself up without anybody's help, and can never see another organisation's data.
Three screens carry almost all of the work: the board for today, the shifts nobody has picked up yet, and the settings that let an admin change how the place runs without asking a developer.
Today
The screen most people only ever need. It opens on wherever you usually work, so most staff never touch the building chips at all — and your own booking is the one that stands out.
Northgate · Meridian Group
08:00–13:00
13:00–18:30
08:00–13:00
13:00–18:30
13:00–17:00
08:00–12:00
Demonstration · invented data — try the buildings
Shifts
An admin publishes what needs covering; whoever is eligible claims it; somebody approves. The part that usually goes missing is what happens when a person keeps pulling out after being confirmed — so that is measured, and it appears at the moment somebody is deciding, not in a report nobody runs.
Offering 13:00–17:00 — “Happy to take this one but I have to be away by five.”
Pulled out of 1 confirmed shift in the last 14 — 7%, below your 20% flag.
Demonstration · invented data — try approving the first one
What the database refuses
Rules with no exceptions belong in the database, where nothing can get past them. Rules with exceptions belong in the screen, where they can explain themselves and let a person decide. Telling those two apart is most of the design.
One person, two rooms
There is no version of this that is correct, so the write is not allowed to succeed.
UPDATE assignments SET room_id = 'room_c' WHERE person_id = 'p_doyle' AND on_date = '2026-09-08' AND session = 'pm';
Two people, one room
A trainee sitting in alongside someone senior is not a mistake. So this one warns, and lets you carry on.
INSERT INTO assignments
(person_id, room_id, on_date, session)
VALUES
('p_ellis', 'room_1', '2026-09-08', 'am');
The constraint on the left is real, and it is in the schema
Four decisions worth the trouble.
Separation between organisations is a row-level security policy in Postgres, not a filter somebody has to remember to write. A screen with a bug in it returns nothing, rather than returning somebody else's rota.
Rooms retire, people deactivate, bookings cancel. Last March's rota still points at every one of them, which is exactly what you want when somebody asks where a session was held.
“Every Tuesday” is written out as actual bookings in advance rather than worked out on the fly. Changing one Tuesday because somebody is on leave becomes editing one row — instead of the hardest bug in every rota system ever written.
UK region, fixed when the project was created. It is the first question on every security questionnaire, and the only answer on this list that cannot be changed afterwards.
Onsite is in private beta.
If you run a multi-site team with the same problem, I would like to hear how yours works.