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 10 themes · 5 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

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.

A year in review

Totals, listening streaks, and your top artists, albums, and tracks — your own Wrapped, on your own data.

3,847 412 h 21 d → Year in review

Cover art, proxied and cached

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

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

Ten themes

Catppuccin, Nord, Dracula, Tokyo Night, Gruvbox, Solarized — instant switch.

Five languages

English, 简体中文, 繁體中文, 日本語, Deutsch — more on the roadmap.

English简体中文繁體中文日本語Deutsch

Pick a theme, any theme

Ten built-in themes 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.

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.8.1
    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 SQLite

    The database and credentials saved via settings are stored unencrypted — protect the volume like any secret.

  • 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.

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.

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.