Skip to content

Changelog

v0.14.1

Full Changelog

🐛 Bug Fixes

  • (ui) Update JSX namespace to fix missing ambient in react 19
  • (No Category) Fix argument order for logger labels
  • (No Category) Add missing artist credit transforms causing Navidrome Source plays to create errors #613

⚙️ Miscellaneous Tasks

  • (No Category) Re-generate lock file

v0.14.0

What’s New?

I am excited to release Multi-Scrobbler 0.14.0 :tada: This is one of the largest releases for MS in years which significantly rewrites large portions of MS’s architecture to allow it to scale in preparation for even more exciting, near-future functionality like the upcoming ui overhaul and eventual bulk scrobble transfer feature (move all your scrobbles from Client X to Client Y).

Stats for this release:

  • 158 commits
  • 243 files touched
    • +15,400 additions
    • -4,200 deletions
  • No AI was used during development. This is organic, free-range coding.

I want to acknowledge and say a big thank you to all the users who helped test and give feedback during this release process. Your input, patience, and reporting was invaluable :heart: :

@4rft5 @FestiveByteRider @gabeklavans @psyciknz @DigitalStarSys @owendaprile @timmyhbk @Sioeen @Ectalite @HStep20

Backend Overhaul

This release introduces a huge architectural upgrade for the backend: almost all data is now backed by a sqlite database powered by drizzle.

Some visible change highlights:

  • Discovered plays (from sources) are persisted
    • Backlogged, already discovered plays are not re-sent to clients on each startup (they are found in database and ignored)
  • Scrobbled plays (successfully sent by clients) are persisted
    • Reduces dupe scrobbles since identical scrobble data in MS can now be compared across restarts
  • Database migrations are automatic and also automatically make a backup of your database before upgrading
  • All queues and Plays are now managed in database instead of in-memory
    • Reduced memory usage in all usecases
    • Drastically reduced memory when there are many sources/client (5+) or many queued scrobbles (100+)
  • Source and Client statistics are persisted
  • (breaking) Simplified cache configuration to leverage database where feasible

To prepare for database usage in 0.14.0, please read the Upgrade Path docs.

The migration should be simple for the majority of users. For edge cases and power users, the docs cover how to migrate more advanced settings.

Documentation Improvements

The introduction of the database forced a good look at, and cleanup of, the existing docs. Application-wide config should now be easier to read and the kitchensink examples are now more straight-forward and relevant to common usecases.

Other Changes (Changelog Highlight)

⛰️ Features

  • (database) Implement database structures needed for historical plays
  • (database) Track component last ready and last active at
  • (tealfm) Add now playing support with fm.teal.alpha.actor.status by @HiiJax :tada:
  • (tealfm) Update lexicon defs with new mbid uri format teal-fm/teal#102
  • (now playing) More robust handling of clients with real time status updates (teal, discord) #608
  • (now playing) reduced logging noise
  • (lastfm) bypass scrobble rejection for very short tracks (< 30 seconds) by omitting duration #596
  • (No Category) refactor artists to directly map/associate MBIDs

⚙️ Miscellaneous Tasks

  • (No Category) update all dependencies #594 by @philocalyst :tada:

📚 Documentation

  • (lastfm) Document 2 week scrobble window
  • (No Category) Overhaul kitchensink
  • (No Category) Add _ID and _NAME examples to all instances of ENV configs
  • (No Category) Add id to all instances of file/aio configs
  • (No Category) Add more descriptive error logging for example config file parsing

0.13.3

What’s Changed

🐛 Bug Fixes

  • (transform) Fix bad usage of cache potentially causing older-timestamped plays to be returned when newer play data was identical
  • (spotify) Don’t use platform creation timestamp as stateLastUpdatedAt, fixes spotify player accidentally going stale while still active

⚙️ Miscellaneous Tasks

  • (lastfm) Increase connection test timeout for slow networks and add DNS resolution logging

v0.13.2

What’s New?

Discord Improvements

  • Fall back to using release group MBID to retrieve album art if the defined release does not have associated art
  • Use zero-width padding to allow single-character titles

Fresher Now Playing

For Sources that support multi-platform (plex, jellyfin), MS will more aggressively mark players as stale when there has been no activity but the Source still reports a player is alive. It will also switch Now Playing to the player that has actual activity if the “current” Now Playing player goes inactive (paused, stopped, stale).

Duplicate Scrobble Detection Docs

A new section in the docs has been added that goes into depth on:

  • How MS detects duplicate scrobbles
  • Configurations that won’t cause dupes
  • Configurations that MAY cause dups
    • Solutions for avoiding these scenarios

Full Changelog

⛰️ Features

  • (discord) Implement fallback to release group art if present #578
  • (endpointlz) Add stubbed playing-now route to make some clients happy(er) #579
  • (endpointlz) Implement proper username handling for validate and now-playing routes #579
  • (lastfm) Add more logging to auth process and clean up host detection
  • (mpd) Rewrite with newer MPD client for better payload parsing #574
  • (player) Skip stale updates based on state timestamp and prevent dead players from being created from stale updates
  • (source) Add timestamps to play state to track player and play last updated, if present

🐛 Bug Fixes

  • (cache) Explicitly define auth/metadata as memory when testing file caching
  • (discord) Include known spotify cdn for media providers #569
  • (discord) Workaround activity minimum character limit #578
  • (endpointlz) Fix fallback route not returning a response
  • (lastfm) Fix total results reporting for librefm
  • (now playing) Prefer fresher players #480
  • (player) Initialize player state in the past to prevent false positive on stale update check if update happens at the same time player is initialized
  • (transformer) Try-catch cached steps and fallback to non-cache generation
  • (webscrobbler) Convert express wildcard path to regex #583
  • (No Category) Check undefined on correct property #567
  • (No Category) Simplify listenrange usage
  • (No Category) Fix missing await on stagger mapper initial sleep
  • (No Category) Workaround for old play structure

📚 Documentation

  • (mpd) Add permission guidance
  • (No Category) Add base site env detection for cloudflare pages
  • (No Category) Order BASE_SITE first to allow overriding other envs
  • (No Category) Fix missing backticks causing template errors on version equality
  • (No Category) Try to replace canonical urls for github.io with new site
  • (No Category) Add duplicate scrobble guidance
  • (No Category) Remove extra dupe detection section from client overview

🚜 Refactor

  • (No Category) Replace fast-deep-equal with fast-equals #569

🧪 Testing

  • (player) Fix incorrect variable used for advanced timestamp

⚙️ Miscellaneous Tasks

  • (No Category) Remove references to KOITO_ALLOWED_HOSTS (#581) thanks @gabehf
  • (No Category) Override rate-limit-threshold to use new version with reduced dependencies

v0.13.1

Changelog

  • fix: Add missing next() invocation preventing ingress Sources from completing requests #561 #562

v0.13.0

What’s New?

Yandex Music Source

Thanks to @Druidblack for implementing a new source for the Yandex Music service! :tada:

Check the docs for setup and configuration.

Improved Source/Client Restart

An overhaul of the functionality responsible for Source polling and Client scrobble processing makes restarts from the dashboard cleaner and more dependable. There shouldn’t be any more instances of unable to restart due to waiting for signal... :tada:

New Docs Domain and Social Previews

Docs are now “officially” located at https://docs.multi-scrobbler.app

Docs have also been improved with nice social graph preview images (og:image) that will display when a page is shared on a social site. There are images generated for each page that include the page title and/or description.

image

Docker Log Colors

Logs viewed through docker logs now have ~f a n c y~ colors that mirror what is seen in the dashboard.

These are turned on by default but if you prefer plain logs they can be disabled by setting this environmental variable:

COLORED_STD=false

Image Cleanup Space Savings 🧹

Fixing how the frontend is served from the backend enabled almost all frontend packages to be removed from the production build. This, along with a node_modules cleanup script, results in a ~32% decrease in docker image size.

Breaking Changes

Changelog Highlights

⛰️ Features

  • (client) Make scrobble processing abortable
  • (client) Strict ENV types
  • (docker) Use prune-mod to remove unneeded files in image
  • (docker) Make colored console output the default
  • (docs) Update docusaurus to 3.9.2
  • (musicbrainz) Handle rate limiting within api calls
  • (source) Make discover abortable
  • (sources) Better type enforcement
  • (yandex) add yandex music by @Druidblack #523

🐛 Bug Fixes

  • (jellyfin) Fix typo on devicesBlock env assignment #522
  • (scrobbler) Fix stagger map generation variable usage
  • (No Category) Fix circular dependency in error utils
  • (No Category) Align dockefile and devcontainer node/npm versions to fix lockfile mismatch
  • (No Category) Initialize plays in correct scope
  • (No Category) Build vite-express config based on production env to avoid making vite fallback to development

⚙️ Miscellaneous Tasks

  • (No Category) Remove unused testing libraries and fix storybook version
  • (No Category) Update node/npm engine constraints in package.json
  • (No Category) Bump node to 24
    • BREAKING: Bump node to 24

📚 Documentation

  • (No Category) Add Yandex Music Source docs
  • (No Category) Replace instances of foxxmd.github.io
  • (No Category) Finalize a working social card strategy

🚜 Refactor

  • (No Category) Implement abortable source polling
  • (No Category) Reduce docker image by moving frontend packages to devDependencies
  • (No Category) Install expressjs@5
  • (No Category) Replace awaitjs/express with express usage

🧪 Testing

  • (No Category) Make loop stop wait time configurable so tests run faster/more stable

v0.12.2

Changelog

  • Fix play initialization not being reset on each API fetch invocation

v0.12.1

What’s New?

Music Assistant Source

Music Assistant 2.8.0 was released with support for custom Listenbrainz scrobbling URL which means the Multi-Scrobbler Listenbrainz Endpoint Source can now be used for scrobbling from Music Assistant! :tada:

See the Music Assistant setup instructions inside the LZ Endpoint docs to get started.

Notable Fixes and Improvements

  • Fixed issues with transform re-use and caching #495 #510 #481
  • Improved memory usage for caching and queued scrobbles #507 #484
  • Fixed and improved dead scrobble retry behavior #488 #489
  • Added Grafana dashboard for use with Prometheus metrics, thanks to @4rft5

Upcoming UI Overhaul

Many of the changes in this release are non-visible improvements/refactoring in preparation for a complete rewrite of the UI. If you would like see a preview of the UI or add feedback on what you’d like to see in a new UI, please check out Issue #500

Changelog

⛰️ Features

  • (listenbrainz) Use upstream for backlogged
  • (lzendpoint) Update json body parsing to support Music Assistant
  • (scrobble) Increase default dead letter retries from 1 to 3
  • (No Category) Implement play with lifecycle fixture
  • (No Category) Add step and flow result to lifecycle
  • (No Category) Add seenAt to source/client play meta
  • (No Category) Skip fetching backlogged plays entirely if scrobbleBacklog=false
  • (No Category) Add cache stats to prometheus metrics

🐛 Bug Fixes

  • (musicbrainz) Catch recordings with no releases
  • (scrobble) Improve dead scrobble iteration and logging
  • (scrobble) Improve ui/api experience for dead scrobble processing
  • (test) Fix expected plays for YTM interim plays
  • (No Category) Remove schema-plugin types hint from docusaurus config
  • (No Category) Fix type-erased meta property for json plays
  • (No Category) Fix wrong operation for maxRandomStagger
  • (No Category) Add stagger options as injected dependency to make tests faster
  • (No Category) Better handling when stage fails but should continue
  • (No Category) Refactor play tranforming to dogfood steps and fix caching #495
  • (No Category) Fix sleep time between consecutive dead scrobble attempts
  • (No Category) Only enable metrics collection for real runs
  • (No Category) Use lifecycle invariant play for closest match

📚 Documentation

  • (No Category) Add duplicate scrobble detection explanation
  • (No Category) Add Music Assistant to lz endpoint docs
  • (No Category) Add prometheus usage
  • (No Category) Add netlify badge to footer on preview docs
  • (No Category) Add more caching guidance
  • (No Category) Fix discord link on jellyfin page
  • (No Category) Add grafana dashboard
  • (No Category) Simplify installation
  • (No Category) Add music assistant to readme

🚜 Refactor

  • (No Category) Move some utils into core folder
  • (No Category) Fix JsonPlayObject to properly type dates
  • (No Category) Separating functions so they are usuable by frontend storybook
  • (No Category) Move stagger config responsibility to transformer
  • (No Category) Refactor cache building to be able to specify both primary and secondary
  • (No Category) Remove ttl from mb api to ensure primary/secondary ttl is used correctly

v0.12.0

What’s New?

Overhauled Historical Scrobble Matching

MS was originally built with only real-time scrobbling as a design goal so the logic behind “check if this track-to-be-scrobbled already exists for Scrobbler X” only extended as far as checking scrobbles MS had already seen, or the first page of scrobbles returned from the scrobbler’s API. MS has long since evolved beyond these (6 year old) goals but the existing scrobble logic had stayed mostly the same which means many users have had problems with MS when it comes to scrobbling old scrobbles that don’t conform to this real-time assumption.

Now, ~3000 changed lines later and thanks to testing from @4rft5 , this logic has been completely overhauled to enable arbitrary time-range checks for any historical scrobbles: allowing MS to scrobble everything with the assurance they won’t be duplicated, regardless of when they were scrobbled.

Note: This is a fully internal, functional change that does not require any changes in configuration or behavior from you, the user.

Some caveats that will be improved in future releases:

  • This change does not yet apply to Rocksky or teal.fm due to constraints on searching PDS repositories. This will be addressed in a future release with manual user interaction.
  • There may be issues with fetching in some edge cases pertaining to many results returned. EX: you imported scrobbles into a client with no timestamps so “1 hour timerange” contains ~3000 scrobbles – MS may behave unexpectedly in this scenario. Please file an issue if this scenario applies to you.

This change also paves the way for bulk scrobble migrations between clients. Be on the lookout for this in a future release ;)

Improved Support for File/AIO Config in Documentation

File and AIO Configuration for every Source/Client, on the documentation website, has been improved with new tools:

  • A schema explorer lets you browse all possible properties of the config to more easily understand its shape and what is required
  • A json editor enables you to modify the examples in-place, while validating your changes against the accompanying schema
    • You can edit plain json or use the point-and-click for a guided experience if you aren’t comfortable with json yet
image

These same tools are available on the Config Playground for general AIO editing and exploration.

image

Breaking Changes

Koito Support in Listenbrainz Client

The Listenbrainz Client previously has partial support for scrobbling to Koito instances due to a partially compatible listenbrainz api. The new historical scrobble logic makes this support impossible so this support has been fully removed.

If you are still using a LZ client for interacting with a Koito instance you need to migrate your configuration to use the official Koito Client configuration.

This does not affect users already using the Koito client.

Spotify Premium

As of March 9th 2026 Spotify no longer allows Spotify Developer Apps to be owned by a non-premium account.

If you have a non-premium account in this scenario you will likely experience 403 errors the next time your authentication needs to be refreshed.

Note that non-premium accounts can still use MS but you need to re-create an app that is owned by a premium account, first. See the Premium Account Required expandable section in the Spotify Source docs for instructions on how to implement this workaround.

Full Changelog

⛰️ Features

  • (docs) Implement config example/structure guidance
  • (jellyin) Add external URL guidance
  • (scrobbler) Implement play-dependent, arbitrary timerange fetching for duplicate checks
  • (scrobbler) Re-implement scrobble preload
  • (scrobbler) Improved dupe detection
  • (ui) Add service status page link to dashboard
  • (ui) Default logs to trace
  • (No Category) Implement timerange consolidation
  • (No Category) Implement better request retry logic

🐛 Bug Fixes

  • (discord) Add hidden activity type
  • (discord) Don’t throw on unknown activity type
  • (lastfm) Check for singular scrobble response
  • (lastfm) Fix not returning nowPlaying scrobble
  • (lastfm) Default to not including now playing in history response
  • (nowPlaying) Scrobbler should report now playing if player state is known nowPlayingMode source
  • (plex) Also read MBIDs from guid field @owendaprile
  • (sources) Add a confidence signal to plays generated from authoratiative sources to avoid fuzzy matching repeats
  • (transform) Fix missing defaults assignment for stage config parsing
  • (transform) Fix user transform default value
  • (transform) Fix transform for stage not using name for selection #481

📚 Documentation

  • (spotify) Add premium account guidance
  • (No Category) Fix broken links and anchors

🚜 Refactor

  • (atproto) Replace naive TID impl with @ewanc26/tid
  • (docs) Replace docusaurus json-schema-plugin
  • (listenbrainz) Remove Koito support
    • BREAKING: Remove Koito support
  • (source) Reduce logging noise for activity checks
  • (No Category) Use trace logging for defaults
  • (No Category) Re-implement retry logic for LZ with fixed request init
  • (No Category) Reorganized and consolidate source/client types

⚙️ Miscellaneous Tasks

  • (No Category) Exclude schema folder from search
  • (No Category) Add netlify-specific build command

v0.11.5

What’s New?

Discord Rich Presence

MS now supports using Discord Rich Presence for Now Playing functionality.

This means that any Source that reports Now Playing or a real-time player, can be used to update your Discord status with a “Listening to…” activity :tada:

There are two types of Discord integrations to choose from:

  • App integration uses your existing Discord application on your desktop/laptop
  • Headless integration communicates directly with Discord’s API gateway so it is not tied to any specific Discord instance

Check out the parent Discord docs page to get an overview of the differences between these integrations.

Upstream Service Status Page

There is now a status page for monitoring the reachability of public services used by some Sources/Clients is available at

https://status.multi-scrobbler.app

This monitor runs on a VPS and checks the uptime of actual APIs for these services, not just landing pages. On a ~minute interval it checks:

  • Listenbrainz API
  • Musicbrainz API
  • CoverArtArchive API
  • Last.fm API
  • A few MS-hosted support services

The idea is that this monitor can help you check if external service communication issues are internet-wide or if maybe the issue is in your network.

Full Changelog

⛰️ Features

  • (discord) Initial implementation #468
  • (discord) Artwork fallback and ENV config
  • (discord) Add album art functionality
  • (discord) Use artwork url if its from a known domain
  • (discord) Use release mbid to get coverart if fallback would otherwise be used
  • (discord) Implement retry for cover art api
  • (musicbrainz) Implement CoverArtArchive API
  • (nowplaying) Update now playing based on play presence AND player status
  • (scrobblers) Run preCompare transform hook on playing now data

🐛 Bug Fixes

  • (scrobblers) Don’t queue now playing data if scrobbler is not ready
  • (transform) Use temporally-invariant hash comparison
  • (No Category) Fix config validation error source
  • (No Category) Fix labels for MS heartbeat task

📚 Documentation

  • (discord) Add discord docs
  • (discord) Update artwork guidance for cover art archive
  • (discord) Add discord container instructions
  • (faq) Add connection/dns guidance
  • (No Category) Add status page to monitoring section