Kiosk time & attendance terminal for Frappe HR — turn any tablet (or any device with a browser) into a wall-mounted punch clock. Employees clock in and out via a touch grid + PIN or by holding their QR badge to the camera. Every punch lands as a standard Employee Checkin, so HRMS Auto Attendance, timesheets and payroll work out of the box.
No proprietary hardware, no forked doctypes — a small app on top of plain Frappe HR.
All names shown are fictional demo data.
| Idle — clock & badge scan | Employee grid (PIN path) |
|---|---|
![]() |
![]() |
| PIN entry | Confirmation with undo |
|---|---|
![]() |
![]() |
Kiosk (/kiosk)
- Clock-first idle screen in a dark terminal look: big clock + date (synced to the server clock, so the display always matches the recorded punch time — even on a misconfigured tablet), badge prompt, one tap to the employee grid
- Employee grid with initials avatars and live presence (green dot + "seit HH:MM" while clocked in) → Kommen/Gehen (pre-selected from the last punch) → touch PIN pad; falls back to the clock after 45 s of inactivity
- QR badge scanning with the device camera — active on the idle screen and the grid; hybrid: the PIN path always stays available
- Direction is toggled automatically on badge scans, with a 5-second undo
- Privacy by default: the camera scans without showing its image (a badge icon + "scanner ready" pulse instead); a live preview can be enabled in settings. If the camera cannot start, the kiosk says so loudly and falls back to PIN
- Confirmation sounds (rising two-tone for IN, falling for OUT), toggleable
- Works on any webcam/browser for testing — camera access requires a secure context (HTTPS or
localhost)
Admin (Desk)
- Own app tile in the launcher + Timeclock workspace, visible only to System Managers / HR Managers
- Dashboard: who's-in board (name, in since, device), number cards (present now, punches today, missing check-outs, absent today) and a working-hours chart
- Time Clock tab on the Employee form: enable flag, PIN (encrypted at rest, validated server-side), badge ID, inline QR preview and badge printing (86×54 mm card print format with server-rendered QR)
- Generate Badge re-issues a random UUID — a lost card stops working immediately
Timeclock Settingssingle: camera preview, sounds, kiosk language (German/English)
Integration & security
- Punches are standard Employee Checkin records (
log_type,time,device_id) — HRMS Auto Attendance turns them into Attendance + working hours - No custom processing and no scheduler jobs of its own: HRMS' Auto Update Last Sync option on the Shift Type advances
last_sync_of_checkinfor realtime punches (see Setup) - PIN comparison is constant-time and server-side, with per-employee lockout after 5 failed attempts; unknown badges are rate-limited per device
- Badges are random 128-bit UUIDs, never the employee number
- The kiosk runs under a dedicated user with the Timeclock Kiosk role — no desk access, no employee data beyond the grid
Timeclock deliberately owns no time data. Frappe HR already models the levels this needs, and the app writes into them instead of beside them:
| Level | DocType | Binding? |
|---|---|---|
| Raw event | Employee Checkin — not submittable, track_changes on |
No. A log. HR can correct it, and every edit is kept in the version trail |
| Evaluated day | Attendance — submittable, created by HRMS Auto Attendance |
Draft until someone submits it |
| Money | Salary Slip and whatever else reads Attendance | Yes |
The app's own schema is one Single (Timeclock Settings) plus four custom fields on Employee (enable flag, PIN, badge ID, QR preview). There is no parallel time-record table, and nothing writes Attendance directly — the app only ever creates Employee Checkin.
That is the whole design decision, and it buys three things: every HRMS feature reading checkins keeps working (shifts, overtime, timesheets, geolocation fields, a biometric device running in parallel), corrections happen in the standard HR tooling your team already knows, and you can uninstall the app without losing a single punch.
- Frappe Framework + Frappe HR (hrms)
cd $PATH_TO_YOUR_BENCH
bench get-app $URL_OF_THIS_REPO
bench --site yoursite install-app timeclock
bench --site yoursite migrateThe built kiosk frontend ships with the repo — no Node step is needed for installation. If you develop on the frontend, rebuild before committing:
cd apps/timeclock/frontend
npm install
npm run build- Enable employees: Employee form → Time Clock tab → check Time Clock Enabled, set a PIN. For QR: Generate Badge, then Print Badge (or let employees scan the on-screen QR).
- Create the kiosk user — the shared login for the kiosk device(s):
- Desk → New User → email e.g.
kiosk@yourcompany.com, user type Website User (no desk access), disable the welcome email and set a password - assign exactly one role: Timeclock Kiosk (created by the app) — it gates the kiosk API and nothing else; the user cannot read employee data beyond the kiosk grid
- log in once on the kiosk device and open
/kiosk— the session is long-lived, one kiosk user can serve any number of terminals (tell them apart via the?device=parameter) - or skip manual logins entirely with device auto-login: in Timeclock Settings set Kiosk Auto-Login User + a Kiosk Auto-Login Token (min. 20 chars), then use
/kiosk?device=front-door-1&token=<token>as the kiosk start URL. Devices heal themselves after reboots and session expiry; regenerating the token locks all devices out instantly. Auto-login refuses any account that has System Manager or lacks the Timeclock Kiosk role
- Desk → New User → email e.g.
- Auto Attendance (once): create a
Shift Typewith Enable Auto Attendance (working hours from First Check-in and Last Check-out, direction Strictly based on Log Type) and assign it to your employees — without a shift, checkins are recorded but no Attendance is created. On that same Shift Type, also tick "Automatically update Last Sync of Checkin". You find it in the Auto Attendance Settings block, right column, directly below the Last Sync of Checkin field. HRMS only processes checkins up tolast_sync_of_checkin, and that field is normally advanced by a biometric device's sync tool — a kiosk has none, so without this checkbox punches are recorded but never turn into Attendance. With it, HRMS' own hourly job advances the field, clamped to the end of each shift. Did it work? The Last Sync of Checkin field above turns read-only once the box is ticked. - Open the kiosk: log in as the kiosk user and open
https://yoursite/kiosk?device=front-door-1. Thedeviceparameter is recorded on every punch and shown on the who's-in board.
Any Android 8+ tablet works. A proven setup — step-by-step guide incl. all traps: docs/freekiosk-setup.md:
| Device | Notes |
|---|---|
| Desktop browser + webcam | Chrome/Chromium with any standard webcam — handy for development and evaluation (camera needs localhost or HTTPS) |
| Samsung Galaxy Tab A11+ (SM-X230, 11″) | ✅ Verified in production — wall-mounted with FreeKiosk in Device Owner mode; the front camera reads the 40 mm badge QR in well under a second. 3D-printable wall mount: MakerWorld — Wall Mount Samsung Tab A11 |
- FreeKiosk (MIT) as the lockdown shell: WebView mode with the kiosk URL (ideally the auto-login URL incl.
&token=, so nobody ever types credentials on the device), Device Owner viaadb shell dpm set-device-owner, boot autostart, admin PIN. Note: FreeKiosk's built-in "Website Authentication" only answers HTTP Basic Auth challenges — it cannot fill the Frappe login form; use the token auto-login instead - Camera scanning needs a secure context — serve the site via HTTPS (or allow the origin explicitly in the shell)
- Enable the vendor's battery protection / charge limit — the tablet is plugged in 24/7
- Allow media autoplay in the WebView if you want confirmation sounds without a prior touch
Punches are recorded, but no Attendance is ever created. By far the most common one, and it fails silently — nothing errors, Attendance simply never appears. Check the Shift Type in this order:
- Is an employee actually assigned to the shift? (Shift Assignment, or Default Shift on the Employee.) No shift → the checkin is stored with an empty
shiftfield and auto attendance skips it. - Is Enable Auto Attendance ticked?
- Is Automatically update Last Sync of Checkin ticked? (Auto Attendance Settings → right column, below the Last Sync of Checkin field.) This is the one people miss: HRMS only looks at checkins older than
Last Sync of Checkin, and that field is normally advanced by a biometric device's sync tool. A kiosk has none, so without the checkbox the field never moves and every punch stays unprocessed. - Is Process Attendance After set to a date before the punches you are waiting for?
Also note Attendance appears only after the shift has ended, and the scheduler runs hourly — so do not expect a record mid-shift. To check without waiting:
bench --site yoursite execute hrms.hr.doctype.shift_type.shift_type.update_last_sync_of_checkin
bench --site yoursite execute hrms.hr.doctype.shift_type.shift_type.process_auto_attendance_for_all_shiftsThe kiosk shows "No connection to the server". The site did not answer. A deployment, a restart, a reverse proxy's error page and a dropped network all look identical from the kiosk, and it deliberately does not try to tell them apart — every operator signals them differently. It retries every 15 seconds and recovers by itself; no reload and no reboot needed. Punching is impossible in the meantime, which is why the clock in without badge button is disabled while the notice is up.
The camera panel stays empty / no QR is read.
getUserMedia requires a secure context: serve the site over HTTPS, or use http://localhost while developing. A camera already claimed by another app also yields an empty panel.
The Timeclock tile is missing from the desk launcher.
It is created on app install and re-ensured on every bench migrate, so bench --site yoursite migrate brings it back. The tile is only visible to System Manager and HR Manager — that is intentional, employees never see the app.
A user gets "Not permitted" on the kiosk. The kiosk API is gated on the Timeclock Kiosk role. Website users need exactly that role; System Manager also works, but do not use an admin account for a wall-mounted device.
# backend: standard bench workflow
bench --site yoursite migrate
# frontend dev server (proxies to your bench)
cd apps/timeclock/frontend
npm run devPython code is formatted with ruff (tabs, line length 110, Frappe conventions). Backend tests: bench --site yoursite run-tests --app timeclock. CI (GitHub Actions) runs lint, the frontend build and the server tests against Frappe v16 + HRMS.
The kiosk UI ships in German and English — switch via Timeclock Settings → Kiosk Language. Backend error messages follow the kiosk user's language (German translations included).
A wall terminal is a trust-by-convenience device. It is worth being explicit about what this one does not do.
- A PIN or a badge identifies, it does not authenticate. Anyone who watches you type your PIN or photographs your badge QR can punch for you. Timeclock does not try to prevent buddy punching — there is no biometry and none is planned. What it offers instead is traceability: every punch carries its
device_id, the who's-in board makes presence visible in real time, and Generate Badge invalidates a copied card immediately. - Rejected scans leave no trace. An unknown badge, or an employee who is not enabled, gets an error on screen and nothing is written anywhere. So "but I did clock in" cannot be checked afterwards, and a badge still in circulation after someone has left goes unnoticed. A quarantine log is on the roadmap.
- No offline mode. If the network or the site is down, the terminal cannot punch. That is a deliberate trade for now: every recorded time comes from the server clock, never from the tablet — the kiosk display is merely synced to it, so a device with the wrong time zone still records correctly. An offline queue breaks that guarantee, which is why it is designed as a review step rather than a direct write (see Roadmap). While the site is unreachable the kiosk says so plainly, disables the PIN path so nobody walks into a dead end, and retries every 15 seconds — it comes back on its own after a deployment, without anyone touching the tablet.
- The kiosk device is trusted. The auto-login token is a long-lived shared credential: whoever holds the URL can list employee names and punch with a valid PIN or badge. Treat it like a password, keep it on managed devices, and rotate it — which locks out every terminal at once, by design.
- Turning punches into attendance is HRMS' job, not this app's. Timeclock adds no rules of its own; working hours, overtime and shift handling depend entirely on your Shift Type setup, including the one checkbox that fails silently (see Troubleshooting).
- No compliance claims. Recording punches is not the same as meeting your jurisdiction's working-time documentation duties. Review that with whoever is responsible for it.
- Maturity. v0.1.x. In daily production use at one site (~30 employees, a single wall-mounted terminal). CI runs lint, the frontend build and the server tests against Frappe v16 + HRMS. Multi-terminal and larger deployments are untested.
- Quarantine log for rejected scans — unknown badges and disabled employees recorded as a visible, expiring log (hashed badge id, device, reason) instead of vanishing
- Offline queue (service worker). Queued punches carry a timestamp claimed by the device, not the server, so they will land in a small review queue — with the claimed time, the arrival time and the device's clock offset — rather than being written to
Employee Checkinunseen - Wallet passes (Apple/Google) carrying the badge QR
- Reduced badge-management view for supervisors without full HR permissions



