About this project

UniPlateTracker is a self-hosted, private dashboard for tracking license plates captured by UniFi Protect cameras. It receives detection events pushed by the UniFi NVR over webhooks, records them in a PostgreSQL database, and presents them through a web interface for viewing, searching and analysing vehicle activity. Thumbnails and video files stay on the user's own server, with an optional UK DVLA lookup as the only described external data source. What it does - Real-time event processing: license plate detections arrive from UniFi Protect webhooks and are processed as they happen. - On-demand video capture: a standalone Node.js worker records a short clip for each detection from the camera's RTSP stream, with pre- and post-event buffering handled by a separate buffer manager process. - Optional DVLA integration (UK): fetches vehicle details such as make, colour, tax and MOT status. An international mode disables UK-specific features so the app can run in other regions, though non-UK plates will show "Unknown" unless ENABLE_INTERNATIONAL_API is configured. - Admin dashboard: a protected admin area with charts and usage statistics, plus hierarchical admin accounts where the initial admin can grant or revoke access for other administrators; those actions are written to an audit trail for review. - Detections table: dense and sortable, intended for reading quickly. Sorting and filtering run in the database, so ordering applies to the whole result set rather than only the current page, and the table reflows into a card list on narrow screens. - Status highlighting: taxed vehicles with a valid MOT stay visually quiet, while expired, untaxed and SORN vehicles are highlighted; "no DVLA record" is shown as a distinct hollow, dashed chip so missing information never reads as a clean result. - Accessibility: labelled controls, keyboard-operable sorting and paging, aria-sort on the table, and prefers-reduced-motion support. The project states it was audited with zero violations. Light and dark mode follow system preferences. Tech stack Next.js 16 (App Router) with TypeScript, React 19, Tailwind CSS v4 and shadcn/ui; Next.js API routes alongside a standalone Node.js worker/buffer manager; PostgreSQL for storage; FFmpeg for video processing; integrations with UniFi Protect webhooks and the DVLA API. Because Tailwind v4 takes its theme from CSS, there is no tailwind.config.ts — design tokens, plate and status styles live in the @theme block of src/app/globals.css. Deployment The documented baseline is Debian 12 with a UVC-AI-Pro camera (device version 5.1.57) and UniFi Protect 9.4.19, with the note that other configurations are expected to work. Two methods are described: - Manual installation: install Node.js 20+, PostgreSQL and FFmpeg; copy the example environment file to .env.local and fill in values such as POSTGRES_URL and SESSION_SECRET; create the buffer and final capture directories if video capture is enabled; install dependencies for both the app and the worker; run npm run db:init to create tables and register the first admin email; build and start the app, optionally keeping it persistent with PM2. - Docker: download the compose file and environment template, rename it to .env.local, supply POSTGRES_URL, SESSION_SECRET and NEXTAUTH_URL (using the server's network IP rather than localhost), run a one-off container to initialise the database and create the first admin, then pull and start with Docker Compose. The web interface is documented on port 3000 and the webhook endpoint on port 4000. In UniFi Protect, an alarm is created under Settings > System > Other Settings > Alarm Manager, triggered on ID > LPR for unknown and known vehicles, scoped to the LPR cameras, and pointed at the app's /webhook URL with thumbnails enabled. Troubleshooting notes from the README Detections can show "No clip" because detection and capture are separate paths: the webhook writes the row while the worker records from RTSP, so capture can fail while detections keep arriving. Suggested checks are the newest file date in VIDEO_FINAL_CAPTURE_PATH, whether FFmpeg is still installed and on its expected path, whether camera credentials or the RTSP URL are still valid, whether both background processes are alive, and whether the user running the app can read the capture directory. A dashed "No clip" tile means a clip was expected but missing, while a plain camera icon means the detection never referenced one. Video and thumbnail requests return 404 unless filenames contain only letters, digits, dots, hyphens and underscores with .mp4 or .jpg extensions, and any path that changes when normalised is refused, so directory traversal fails rather than escaping the capture directory. Vehicle details showing "Unknown" mean the DVLA holds no record for that registration or the lookup failed.