Skip to content

Self-hosted · Open source · MIT

Every play, counted.

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.

Listening now

Blue in Green

Miles Davis — Kind of Blue

1:05 5:37
Feishin · home-nas
Featured on Product Hunt MIT licensed Docker Hub — stepaniah/navidrome-statistic 18 palettes · 7 languages · any Subsonic client

Everything your listening history knows

Navidrome clients don’t share stats with each other. Navidrome Stat watches the server instead — one history, every client.

Now playing, live
  • 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

Every client, one history

DSub, Symfonium, Feishin, web players — plays from any Subsonic client land in the same history. Multiple Navidrome servers, too.

DSub Symfonium Feishin Navidrome Stat

Charts that go deep

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.

A year in review

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.

3,847
plays
412
hours listened
21
day streak

→ Year in review

Cover art, proxied and cached

Album art flows through an authenticated, size-capped cache — your library stays private.

1,024 covers cached · 128 MB

Your data, your rules

Everything lives in one SQLite file you own. Per-user export, import, and deletion. Optional token auth. No telemetry.

JSON export & importPer-user deletionOptional token auth

18 palettes, light & dark

Nine palette families — Catppuccin, Nord, Dracula, Tokyo Night, Gruvbox, Solarized, and more — each with matching light and dark variants.

Seven languages

English, 简体中文, 繁體中文, 日本語, Deutsch, Français, Español.

English简体中文繁體中文日本語DeutschFrançaisEspañol

Pick a theme, any theme

Nine palette families, each with a light and a dark variant, recolor every tab instantly. Click one and watch the dashboard reskin:

localhost:39421

Your year in music

One page, your whole year: totals, streaks, and the songs that got you through it.

0
plays
0
hours listened
0
artists
0
day streak

Top tracks

Blue in Green

214

Sample data — the real numbers come from your own library.

2025 · Sample library

Your year in music

0

plays

412
hours listened
486
artists
21
day streak

Top tracks

Sample data — the real numbers come from your own library.

How it works

No client plugins, no accounts, no cloud. One small container next to your music server.

  1. 1

    Polls getNowPlaying

    Every 10 seconds (configurable), the collector asks each Navidrome server what’s playing — via the Subsonic API you already run.

  2. 2

    Tracks real sessions

    Pauses and gaps are excluded. A track counts as played once active listening crosses your threshold — 30 seconds by default.

  3. 3

    Stores and serves

    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.

Up in one compose file

Copy, paste, and open port 39421. Pin a version tag for reproducible updates.

compose.yaml
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:

With STATS_API_TOKEN set, enter it once on the login screen — the browser keeps an HttpOnly session cookie, never the token.

Full configuration reference

Private by default

  • Data stays local

    Listening history lives in one SQLite file inside your own volume.

  • Export or delete anytime

    Per-user JSON export, import, and deletion are built into the settings page.

  • Optional auth

    Set STATS_API_TOKEN and the dashboard and APIs require login — sessions use an HttpOnly cookie.

  • No telemetry

    The container talks to exactly one outside party: your Navidrome servers.

Honest limitations

  • One instance per set of sources

    Multiple instances polling the same servers would double-count plays.

  • Plaintext listening data

    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.

  • No built-in TLS

    For remote access, put it behind your HTTPS reverse proxy.

Questions, answered

Which music clients does it support?

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.

Can I track multiple Navidrome servers?

Yes. Add each server under Settings → Connections; plays are tagged per source and the dashboard can filter by server or user.

I already have months of play history. Can I import it?

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.

Can I share what I see?

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.

How exactly is a play counted?

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.

How are collaborating artists counted?

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.

Does it work with Jellyfin or Airsonic?

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.

How is this different from Last.fm or Maloja?

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.

Where does my data live?

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.

How heavy is it?

One small container, one SQLite file, a 10-second poll loop. A Raspberry Pi handles it comfortably.