Self-hosted, open-source HR and workforce platform built with Next.js, Supabase, and Tailwind CSS.
StaffPortal is a full-featured HR and workforce management platform you run on your own infrastructure. It covers attendance, leave, timesheets, expenses, visitors, wellness, and IT support behind a single role-based login, with single sign-on, an immutable audit trail, GDPR data exports, monthly leave accruals, a mobile-friendly kiosk, and an optional conversational AI assistant. It is built for small-to-medium organisations that want a modern, clean alternative to expensive HR software without handing staff data to a third party.
Full documentation lives in the project wiki: start with Home, then Quick-Start, Architecture, Single-Sign-On, Leave-Accruals, GDPR-Export, Kiosk-Mode, Audit-Log, and Roadmap.
Five commands from a fresh clone to a running dev server. You will need a free Supabase project and a Resend API key first.
git clone https://github.com/sarmakska/staff-portal.git && cd staff-portal
npm install
cp .env.local.example .env.local # then fill in your Supabase and Resend keys
npm run lint && npm test # confirm the toolchain is healthy
npm run dev # open http://localhost:3000Run the SQL files in supabase/migrations/ in numbered order against your Supabase project before first login. The full step-by-step setup, including auth redirect URLs and creating the first admin, is in Getting Started below.
Use StaffPortal if you are a small-to-medium organisation that wants to own its staff data, self-host on Vercel and Supabase, and run attendance, leave, expenses, and visitor management from one place. It suits teams that are comfortable managing a Supabase project and a few environment variables, and that want a codebase they can fork and extend rather than a closed SaaS subscription.
Look elsewhere if you need certified payroll processing, statutory tax filing, or deep integration with an existing enterprise HRIS out of the box. It is not a multi-tenant SaaS product: each deployment serves one organisation. If you cannot run a Supabase instance or do not want to manage your own deployment, a hosted HR product will be a better fit.
flowchart TD
subgraph Client
Browser["Staff browser / Kiosk"]
end
subgraph Vercel["Next.js on Vercel"]
Pages["App Router pages + components"]
Actions["Server actions (lib/actions)"]
API["API routes + cron handlers"]
MW["middleware.ts (auth + role guards)"]
end
subgraph Supabase
Auth["Auth"]
DB[("PostgreSQL + Row Level Security")]
end
Resend["Resend (email)"]
Groq["Groq API (the assistant, optional)"]
Cron["Scheduler (Vercel Cron / GitHub Actions)"]
IdP["Identity provider (Entra ID / Google / SAML)"]
Browser --> MW --> Pages
Pages --> Actions --> DB
Pages --> API
Actions --> Auth
Auth --> IdP
API --> DB
API --> Resend
API --> Groq
Cron -->|Bearer CRON_SECRET| API
Cron -->|leave-accrual, year-end-rollover| API
- Dashboard — Personalised overview: leave balances, week hours summary, diary reminders, quick actions
- Attendance — Clock in/out, work from home toggle, running late logging, late arrival detection
- WFH Logging — Log full day, morning, or afternoon as work from home
- Attendance Corrections — Submit correction requests with approval workflow
- Timesheets — Weekly hours view with contracted hours tracking and Excel export
- Leave — Apply for annual, sick, maternity/paternity, or unpaid leave; multi-step approval with PDF certificates
- Leave Withdrawal — Withdraw approved leave; full record kept, balance reversed, email sent
- Expenses — Expense claims with category, merchant, amount, receipt upload; PDF claim forms
- Purchase Requests — Submit purchase requests for admin approval
- Diary — Personal work notes with date-based email reminders
- Calendar — Team-wide calendar showing leave, WFH, events, and office presence
- Staff Directory — Contact cards with phone, email, and profile details
- Announcements — Company-wide announcements with email broadcast
- Polls — Company polls with real-time vote counts
- AI Assistant (the assistant) — Conversational AI assistant with awareness of your attendance, leave, expenses, and team
- Visitors — Pre-register visitors, QR code references, host email notifications on check-in
- Reception Desk — Quick check-in and check-out for today's visitors
- Visitor PDF Passes — Generate and print visitor passes
- Kiosk Mode — Self-service clock in/out and visitor check-in at
/kiosk(no login required)
- Wellness check-ins and mood tracking
- Breathing exercises and stretch reminders
- Admin wellness dashboard
- IT ticket submission
- Admin ticket management and status tracking
- Auto-cleanup of resolved tickets via cron
- User Management — Create, activate/deactivate accounts, assign roles
- Department Management — Create and manage departments
- Work Schedule Management — Set per-user contracted hours and working days
- Leave Records — Full leave history with filters; resend approval emails
- Leave Allowances — Set balances per employee, configure carry-forward caps
- Leave Accruals — Monthly accrual top-ups per balance, capped at full entitlement, run by cron or on demand
- Corrections Management — Review and approve timesheet correction requests
- Forgotten Clock-Outs — Auto-detect staff who forgot to clock out
- Email Notification Settings — Toggle each notification type on/off individually
- Analytics — Late arrivals, WFH trends, office attendance, hours vs contracted; CSV export
- Bank Statement Reconciliation — Export and reconcile expense data
- Single Sign-On — Route staff to Microsoft Entra ID, Google Workspace, GitHub, GitLab, or SAML 2.0 by email domain
- Audit Log — Full immutable system audit trail, including SSO logins, leave accruals, and GDPR exports
- GDPR Data Export — One-click portable JSON export of every record held about a member
The built-in AI assistant is powered by Groq and has real-time access to:
- Your attendance records — clock in/out times, late arrivals
- Your leave balances and upcoming approved leave
- Your contracted hours vs actual hours this week (overtime calculator)
- Team leave — who is off this week and next week
- Who is currently in the office (WFH vs office)
- Upcoming team birthdays
- Your recent expenses and purchase requests
- Active polls and announcements
- Issue reporting — collects details and emails the admin team
- Wellness support mode
The assistant's personality and system prompt are fully customisable in app/api/chat/route.ts. You can swap Groq for any OpenAI-compatible API.
Staff can sign in with your organisation's identity provider. SSO is layered on top of Supabase Auth, so it coexists with email and password and the kiosk PIN flow without any second session system.
An admin maps an email domain to a provider under Admin → Single Sign-On. When a member types an email whose domain has an active connection, the login screen routes them straight to the identity provider instead of asking for a password:
- OAuth providers (Microsoft Entra ID, Google Workspace, GitHub, GitLab) use Supabase
signInWithOAuth. - SAML 2.0 connections use Supabase
signInWithSSO.
First-time SSO sign-ins bootstrap a profile and the standard leave balances, and every SSO login is written to the audit log. The provider apps and SAML metadata are configured in the Supabase dashboard under Authentication → Providers and Authentication → SSO.
Each leave balance can carry a monthly accrual_rate. A cron job (/api/cron/leave-accrual, first of each month) tops every accruing balance up by the elapsed months times its rate, capped at the configured entitlement. The job is idempotent: re-running in the same month grants nothing further, because each balance records how much it has accrued and when. Admins and accounts staff can preview and run accruals on demand under Admin → Leave Accruals.
At year end a second cron job (/api/cron/year-end-rollover, 1 January) opens each employee's new leave year. Unused, unpending days carry over, capped per employee by max_carry_forward, and the prior year's carried amount is stripped from the base so carry-forward never compounds from one year to the next. The carry-forward arithmetic is a pure helper (computeCarryForward) shared with the route and covered by its own test suite, so an over-spent balance can never produce a negative carry.
Under the right to data portability, any signed-in member can download a single portable JSON document containing every record held about them (profile, attendance, leave, expenses, diary, visitors, feedback, complaints, and the audit events attributed to them) from Settings → Privacy and data. Administrators can export another member by calling /api/gdpr/export?userId=<id>. Every export is recorded in the audit log.
The set of personal-data tables is declared once in GDPR_TABLES, and the export route asserts at module load that it covers every declared table (assertGdprCoverage). If a new personal-data table is added but not wired into the export, the build and the test suite fail before an incomplete export can reach a data subject.
| Layer | Technology |
|---|---|
| Framework | Next.js 16 (App Router, React 19) |
| Language | TypeScript |
| Database & Auth | Supabase (PostgreSQL + Row Level Security) |
| Styling | Tailwind CSS + shadcn/ui |
| Resend | |
| AI | Groq API (llama-3.3-70b-versatile) — optional |
| PDFKit | |
| Charts | Recharts |
| Excel Export | ExcelJS |
| Deployment | Vercel (recommended) |
| Role | Access Level |
|---|---|
employee |
Own attendance, timesheets, leave, expenses, diary, calendar, announcements |
reception |
Employee + visitors, reception desk, kiosk settings |
director |
Employee + analytics, all timesheets (read-only), staff summary |
accounts |
Employee + all timesheets (read-only), expense reports |
admin |
Full access to everything |
- Node.js 20+
- Supabase account — free tier works
- Resend account — free tier works
- Groq API key — optional, only needed for the AI assistant
git clone https://github.com/sarmakska/staff-portal.git
cd staff-portal
npm installReplace the placeholder logo with your own:
- Sidebar & kiosk logo: Replace
public/logo.svgwith your logo file (SVG recommended, or rename tologo.pngand update references) - Browser favicon / tab icon: Replace
app/icon.png,public/apple-icon.png,public/icon-dark-32x32.png,public/icon-light-32x32.pngwith your own icons - PDF documents: Place a
public/logo.png(PNG format, recommended size 300×80px) — the PDFs automatically pick it up. If the file is missing, PDFs generate without a logo.
The sidebar logo is displayed at
h-7(28px height) — a wide horizontal logo works best.
- Go to supabase.com and sign in
- Click New Project, set a name and strong database password
- Wait ~1 minute for the project to be ready
- Go to Project Settings → API and copy:
- Project URL
- anon/public key
- service_role key (keep this secret — server-side only)
- In Supabase, go to SQL Editor
- Open each file in
supabase/migrations/in numbered order - Paste and run them one by one:
001_...,002_..., through all files
Or use the Supabase CLI:
supabase db pushcp .env.local.example .env.localFill in .env.local with your values. See the Environment Variables section below.
- In Supabase go to Authentication → URL Configuration
- Set Site URL to
http://localhost:3000 - Add
http://localhost:3000/auth/callbackto Redirect URLs
npm run devOpen http://localhost:3000.
- Go to
/signupand sign up with your admin email address - Verify your email via the confirmation link
- Log in — update your role to
admindirectly in the Supabase table editor
npx vercelAdd all environment variables in the Vercel dashboard under Project Settings → Environment Variables. Update NEXT_PUBLIC_APP_URL to your production URL.
After deploying, update Supabase:
- Authentication → Site URL → your production URL
- Redirect URLs → add
https://your-domain.com/auth/callback
| Variable | Description | Required |
|---|---|---|
NEXT_PUBLIC_SUPABASE_URL |
Supabase project URL | Yes |
NEXT_PUBLIC_SUPABASE_ANON_KEY |
Supabase anon key | Yes |
SUPABASE_SERVICE_ROLE_KEY |
Supabase service role key (server-side only) | Yes |
RESEND_API_KEY |
Resend API key for sending emails | Yes |
RESEND_FROM_EMAIL |
Sender email address (e.g. [email protected]) |
Yes |
NEXT_PUBLIC_APP_URL |
Your app's public URL | Yes |
CRON_SECRET |
Secret token to authenticate cron job requests | Yes |
GROQ_API_KEY |
Groq API key for the AI assistant — comma-separate multiple keys for load balancing | Optional |
ACCOUNTS_EMAIL |
Accounts team email for finance notifications | Optional |
ADMIN_NOTIFY_EMAIL |
Admin email for system notifications | Optional |
See .env.local.example for the full list with descriptions.
StaffPortal uses cron jobs for automated reminders and maintenance. GitHub Actions workflow files are included in .github/workflows/ — add your APP_URL and CRON_SECRET as repository secrets to activate them.
Alternatively, configure them in vercel.json for Vercel Cron, or use any cron service that can call your API endpoints with the Authorization: Bearer <CRON_SECRET> header.
| Job | Endpoint | Recommended Schedule |
|---|---|---|
| Birthday reminders | /api/cron/birthday-reminder |
Daily 8am |
| Absent reminders | /api/cron/absent-reminder |
Daily 10am |
| Missing attendance | /api/cron/missing-attendance |
Daily 6pm |
| Forgotten clock-out | /api/cron/forgotten-clockout |
Daily 8pm |
| Leave accrual | /api/cron/leave-accrual |
Monthly, 1st at 00:10 UTC |
| Year-end rollover | /api/cron/year-end-rollover |
Yearly, 1 Jan at 00:05 UTC |
| Stretch reminder | /api/cron/stretch-reminder |
Weekdays 2pm |
| IT ticket cleanup | /api/cron/it-ticket-cleanup |
Weekly |
staff-portal/
├── app/
│ ├── (app)/ # All authenticated app pages
│ │ ├── admin/ # Admin-only pages
│ │ ├── analytics/ # Attendance analytics
│ │ ├── expenses/ # Expense management
│ │ ├── help/ # Help & support
│ │ ├── leave/ # Leave management
│ │ ├── visitors/ # Visitor management
│ │ └── wellness/ # Wellness tracking
│ ├── (auth)/ # Auth pages (login, signup, reset)
│ ├── api/ # API routes and cron handlers
│ └── kiosk/ # Public kiosk (no auth required)
├── components/
│ ├── chat/ # AI assistant chat components
│ ├── layout/ # Sidebar, topbar
│ └── ui/ # Shared shadcn/ui components
├── lib/
│ ├── actions/ # Server actions (all DB operations)
│ ├── email.ts # Email sending via Resend
│ └── supabase/ # Supabase client utilities
├── supabase/
│ └── migrations/ # Database SQL migrations (run in order)
└── types/ # TypeScript type definitions
Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
- Fork the repository
- Create a branch:
git checkout -b feature/your-feature - Make your changes and test locally with
npm run dev - Commit:
git commit -m "feat: your feature description" - Push and open a Pull Request
Ideas for contributions:
- Mobile app (React Native / Expo)
- Slack / Teams integration
- Payroll export (Xero, QuickBooks)
- Shift scheduling and rota management
- Custom leave types per organisation
- Multi-language (i18n) support
- Browser-driven UI tests on top of the existing logic test suite
MIT License — free to use, modify, and distribute for any purpose including commercial use.
Bug reports and feature requests: GitHub Issues
Built by Sarma — a UK-based software engineer building open-source AI infrastructure and platform engineering tools. StaffPortal is part of a broader portfolio of twelve production-shaped open-source repositories.
- Project page · sarmalinux.com/products/staff-portal
- Whitepaper · sarmalinux.com/products/staff-portal/whitepaper
- All open-source projects · sarmalinux.com/open-source
- Engineering blog · sarmalinux.com/blog
Part of a portfolio of twelve production-shaped open-source repositories built and maintained by Sarma.
| Repository | What it is |
|---|---|
| Sarmalink-ai | Multi-provider OpenAI-compatible AI gateway with 14-engine failover and intent-based plugin auto-routing |
| agent-orchestrator | Durable multi-agent workflows in TypeScript with deterministic replay and Inspector UI |
| voice-agent-starter | Sub-second full-duplex voice agent loop. WebRTC, mediasoup, pluggable STT / LLM / TTS |
| ai-eval-runner | Evals as code. Python, DuckDB, FastAPI viewer, regression mode for CI |
| mcp-server-toolkit | Production Model Context Protocol server starter (Python / FastAPI) |
| local-llm-router | OpenAI-compatible proxy that routes to Ollama or cloud providers based on policy |
| rag-over-pdf | Minimal end-to-end RAG starter for PDF corpora |
| receipt-scanner | Vision OCR for receipts with Zod-validated JSON output |
| webhook-to-email | Webhook receiver that forwards events to email via Resend |
| k8s-ops-toolkit | Helm chart for shipping Next.js to Kubernetes with full observability stack |
| terraform-stack | Vercel + Supabase + Cloudflare + DigitalOcean modules in one Terraform repo |
| staff-portal | Open-source HR / ops portal — leave, attendance, expenses, kiosk mode |
Engineering essays at sarmalinux.com/blog · All projects at sarmalinux.com/open-source