- B
So What
DSub · 2m ago
- T
Take Five
Symfonium · 14m ago
- G
Goodbye Pork Pie Hat
Feishin · 31m ago
3 clients · 2 servers · updating live
Self-hosted · Open source · MIT
Navidrome Stat polls the getNowPlaying API of your Navidrome servers and turns it into one self-hosted dashboard — listening time, trends, rankings, and a year in review. Your data never leaves your machine.
Blue in Green
Miles Davis — Kind of Blue
Navidrome clients don’t share stats with each other. Navidrome Stat watches the server instead — one history, every client.
So What
DSub · 2m ago
Take Five
Symfonium · 14m ago
Goodbye Pork Pie Hat
Feishin · 31m ago
3 clients · 2 servers · updating live
DSub, Symfonium, Feishin, web players — plays from any Subsonic client land in the same history. Multiple Navidrome servers, too.
Hourly and daily trends, a weekday-by-hour heatmap, client usage, transcoding rates, and artist, album, and track rankings that drill down into shareable detail pages — with daypart and period-over-period comparisons.
Totals, listening streaks, and your top artists, albums, and tracks — your own Wrapped, on your own data. Scope it by year, server, user, and timezone, and share the view by URL.
Album art flows through an authenticated, size-capped cache — your library stays private.
1,024 covers cached · 128 MB
Everything lives in one SQLite file you own. Per-user export, import, and deletion. Optional token auth. No telemetry.
Nine palette families — Catppuccin, Nord, Dracula, Tokyo Night, Gruvbox, Solarized, and more — each with matching light and dark variants.
English, 简体中文, 繁體中文, 日本語, Deutsch, Français, Español.
Nine palette families, each with a light and a dark variant, recolor every tab instantly. Click one and watch the dashboard reskin:
One page, your whole year: totals, streaks, and the songs that got you through it.
Top tracks
Blue in Green
Sample data — the real numbers come from your own library.
No client plugins, no accounts, no cloud. One small container next to your music server.
Every 10 seconds (configurable), the collector asks each Navidrome server what’s playing — via the Subsonic API you already run.
Pauses and gaps are excluded. A track counts as played once active listening crosses your threshold — 30 seconds by default.
Qualified plays land in SQLite on your disk and render in a self-contained dashboard at port 39421.
When a server advertises the OpenSubsonic playbackReport extension, it is used automatically for sharper position and duration tracking. You can also watch a Navidrome smart playlist to import your pre-install history as estimated plays.
Copy, paste, and open port 39421. Pin a version tag for reproducible updates.
services:
navidrome-stat:
image: stepaniah/navidrome-statistic:v0.9.2
container_name: navidrome-stat
user: "1000:1000"
ports:
- "39421:39421"
volumes:
- navidrome-stat-data:/data
environment:
NAVIDROME_URL: ${NAVIDROME_URL}
NAVIDROME_USER: ${NAVIDROME_USER}
NAVIDROME_PASS: ${NAVIDROME_PASS}
STATS_API_TOKEN: ${STATS_API_TOKEN}
DATABASE_URL: /data/navidrome_stats.db
restart: unless-stopped
volumes:
navidrome-stat-data: docker run -d --name navidrome-stat \
-p 39421:39421 \
-e NAVIDROME_URL=https://navidrome.example.com \
-e NAVIDROME_USER=you \
-e NAVIDROME_PASS=change-me \
-e STATS_API_TOKEN=$(openssl rand -hex 24) \
-v navidrome-stat-data:/data \
stepaniah/navidrome-statistic:v0.9.2 NAVIDROME_URL=https://navidrome.example.com
NAVIDROME_USER=example_user
NAVIDROME_PASS=change-me
STATS_API_TOKEN=use-a-long-random-value With STATS_API_TOKEN set, enter it once on the login screen — the browser keeps an HttpOnly session cookie, never the token.
Full configuration referenceListening history lives in one SQLite file inside your own volume.
Per-user JSON export, import, and deletion are built into the settings page.
Set STATS_API_TOKEN and the dashboard and APIs require login — sessions use an HttpOnly cookie.
The container talks to exactly one outside party: your Navidrome servers.
Multiple instances polling the same servers would double-count plays.
Listening history is stored unencrypted in SQLite. Credentials saved in settings are encrypted at rest with a local key file — that guards backups against casual inspection, not a fully compromised host.
For remote access, put it behind your HTTPS reverse proxy.
Any Subsonic-compatible client — DSub, Symfonium, Feishin, play:Sub, substreamer, the Navidrome web UI, and more. Statistics come from the server’s getNowPlaying endpoint, so nothing needs to be installed client-side.
Yes. Add each server under Settings → Connections; plays are tagged per source and the dashboard can filter by server or user.
Yes. Point a saved connection at a Navidrome smart playlist (an .nsp such as “Recently Played”) and Navidrome Stat imports one estimated play per track from its last-played timestamp. Re-runs never double count, plays already covered by live polling are skipped, and only pre-install listens are imported.
Yes. Dashboard filters, artist and album detail pages, and the year-in-review scope persist in the URL — reloads keep your view and copied links open the same one. Client detail names are sent in POST requests and never appear in shareable URLs.
Accumulated active listening time must cross PLAY_THRESHOLD_SEC (30 seconds by default). Paused and missing intervals are excluded, checkpoints update the same record, and sessions that end below the threshold are kept as attempts — not plays.
In Settings → Preferences, choose combined or separate artist credits. Separate mode credits each artist once per play across rankings and detail views, while track counts, total plays, and listening time stay unchanged.
It works with any server that speaks the Subsonic API, but it is developed and tested against Navidrome. OpenSubsonic extensions are used when a server offers them.
Last.fm records scrobbles in someone else’s cloud; Maloja is self-hosted but still depends on clients submitting scrobbles. Navidrome Stat needs no client support at all — it watches the server’s own now-playing feed, so every Subsonic client is covered by default, and the data stays on your disk.
In a single SQLite file on your own disk, inside the container’s /data volume. You can export it as JSON, import it elsewhere, or delete a user’s history — all from the settings page.
One small container, one SQLite file, a 10-second poll loop. A Raspberry Pi handles it comfortably.