All projects

Work · 2026

Spicetify Stats

A Spicetify app that logs what you listen to, locally, and turns it into top lists and a heatmap.

LiveSource

At a glance

  • Top artists, top tracks and a GitHub-style heatmap
  • A play counts after 30 seconds or half the track length
  • Nothing is uploaded, everything stays in localStorage
  • Imports your own Spotify data export, duplicates included
  • Raw events for 180 days, older months as rollups
  • No build step, every chart hand-built as inline SVG

The idea

Spotify shows you Wrapped once a year and nothing the rest of the time. This custom app puts its own entry in the sidebar: top artists, top tracks and a GitHub-style heatmap, over 7, 30 or 90 days or across the whole history.

It comes in two parts. A collector runs as an extension alongside Spotify and writes the log whether or not the app is open. The app itself only reads what is already there. The single connection between them is localStorage — nothing is uploaded anywhere.

The real work is in the counting

A track only counts as played once it has run for 30 seconds or half its length, whichever comes first. Skipping through a playlist therefore produces no entries at all.

Listening time is measured from the player’s own playback position, bounded by the time that has actually elapsed. Pausing accumulates nothing, seeking forward does not count as listening, and neither a suspended machine nor a throttled background timer distorts the number.

A track on repeat counts again each time — an hour on loop is an hour of listening — but only after a full threshold of genuinely new playback. Spotify sometimes fires its songchange event more than once for the same track, and those duplicates cannot inflate the count.

The play is written the moment the threshold falls, not when the track ends. Quitting Spotify mid-track therefore cannot lose a play that already counted.

Podcasts, local files and ads are not counted.

The space problem

localStorage is small, and a data export spanning several years does not fit into it as raw rows. Hence two layers: individual plays are kept for 180 days, capped at 20,000 entries. Anything older is folded into monthly rollups — plays, listening time, the top 100 artists and tracks — and the raw rows are dropped. “All time” reads both layers.

The cost is visible rather than hidden: the heatmap only knows per-day detail for raw events. Months that have been folded in still count towards totals and top lists, but not in the grid.

Importing the Spotify export

Spotify will send you your own listening history on request. The app reads both shapes it comes in and recognises plays that are already stored — by an exact timestamp, and additionally by seeing the same track within about ten minutes. That second check is necessary because the collector timestamps a play when the threshold is crossed while the export timestamps it when playback ended: the same play, at two different times.

No build step

Spicetify loads the files directly, so there is no npm, no bundler, no TypeScript and no JSX — custom apps cannot use it, and React elements are built through Spicetify.React.createElement instead.

As with Zen Mode, every chart is hand-built inline SVG, because Spotify’s Content-Security-Policy blocks external and data: resources. The colours come purely from Spotify’s semantic variables, so the dashboard follows whichever theme is active — including kpop-theme.