Rostering
The shift roster plans who works when, for shops, hospitals and care homes, warehouses, hotels, contact centres and security. People down the side with their skills, contracted hours, rate, leave and requests not to work; days across, one, two or four weeks at a time; a shift in each cell. Paint shifts across days, type a shift's code, drag one to move or swap it, and lock what must stay.
Rest between shifts, hours a week, days and nights in a row, the skills a shift needs, leave and requests are checked as you go, and every problem is marked on its cell. Cover shows along the bottom and hour by hour for any day. Auto-fill adds shifts where cover is short, keeping every rule and spreading hours against contracts, and says for each gap it cannot fill why nobody could, person by person.
Two weeks of a care home
Fifteen staff of a fictional care home, early, late, long-day and night shifts, and the cover the home needs: a nurse day and night, medication rounds, mornings, afternoons and nights. The first two days are planned. Choose a shift above the grid and drag across days to paint it, type E, L, D or N in a cell, or choose Auto-fill.
Get the code
<script src="/assets/bmx-components.min.js"></script> <bmx-shift-roster id="sr-care" label="Willow Court care home, two weeks" today="2026-10-14" locale="en-GB" file-name="willow-court-roster" style="inline-size: 100%"></bmx-shift-roster>
Four weeks of a warehouse, filled in one go
Thirty people — pickers, forklift drivers and supervisors, some part-time — on three shifts round the clock, with leave booked. Nothing is planned yet. Fill the four weeks and look at what is left: the gaps, and the people still short of their contracted hours.
// Node 18 or later: the roster engine, from the Gold package.
import { readRoster, withPeriod, autoFill, checkRoster, rosterTotals, rosterToCsv } from './dist/node/bmx-shift-roster.mjs';
// Every Sunday night: fill the week after next and send the gaps to the managers.
const roster = withPeriod(readRoster(await loadRoster()), nextMondayButOne(), 7);
const { roster: filled, gaps } = autoFill(roster);
const errors = checkRoster(filled).filter(p => p.severity === 'error' && p.kind !== 'short');
const { hours, cost } = rosterTotals(filled);
await saveRoster(filled);
await writeFile('payroll-preview.csv', rosterToCsv(filled, 'long'));
await notifyManagers({ gaps, errors, hours, cost });