spotify-dedup: removing duplicate tracks from your Spotify playlists
Remove duplicates from your Spotify Playlists
At a glance
- What is it?
- spotify-dedup is a small Next.js application that walks your Spotify library, finds tracks that appear more than once with the same URI, and removes them in place so the playlist keeps its creation date and subscribers. The interesting part is the request scheduling, which uses a promise queue to stay inside the Web API rate limit.
- Who is it for?
- Adopt spotify-dedup if your Spotify playlists have accumulated the same track twice and you want the duplicates removed without losing the playlist's creation date or subscriber list, which is what a rebuild-and-replace approach would cost you. Do not adopt it expecting it to find near-duplicates, because the rule is exact URI equality, and do not expect it to run against a large library without patience, since the traversal is one request at a time under a throttle.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 135 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 24, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Duplicates by URI, removed in place
spotify-dedup answers a narrow question: can you remove duplicated songs from your Spotify library, and can you do it without wrecking the playlist you already have.
The project uses the Spotify Web API for managing playlists. You log in, and it traverses your playlists, finding songs that appear multiple times with the same identifier, the Spotify URI, in a given playlist. When it finds duplicates they can be removed by pushing a button.
The detail that makes this better than the obvious approach is that it does not create a whole new playlist. That matters more than it sounds, because a playlist's metadata includes its creation date and its subscribers, and a script that reconstructs a playlist from a list of tracks destroys both. The README is explicit that keeping that information is the reason for removing in place.
The rule for what counts as a duplicate is exact URI equality within a playlist. That is a narrow definition, and it is the right one for a first pass, but it has consequences worth knowing. A track that appears twice because you added it twice has the same URI twice and will be caught. A track that is present in two different playlists, or the same recording released under a different URI, is not a duplicate by this rule and will be left alone.
The licence is MIT, the site is spotify-dedup.com, and the author field in the package manifest names José M. Pérez with a spotify.com address, which tells you the project comes from someone who works inside Spotify, with the API's rate limits as a lived experience rather than a documentation detail.
Running it locally is two commands
The install and run section is short, and there is no build step to get past before you can see the interface.
pnpm install
pnpm devInstalling the dependencies is pnpm install, running it is pnpm dev, and then you open http://localhost:3000 in a browser. The dev script is next dev, so the development server is the Next.js one, and the app is served from the root of the project.
The other scripts in the package manifest follow the same shape. The build script is next build, the start script is next start for a production server, and there is an analyze script that sets BUNDLE_ANALYZE=browser before next build, which means the project is set up to measure its client bundle with a bundle analyser. The test scripts are jest, jest --watch and jest --coverage.
The package is an ES module, with type set to module, and the Next.js version is 15.0.3 with React 18.3.1 and react-dom 18.3.1. The pages/ directory at the repository root is what tells you this is the pages router rather than the app router, and there is an app/ directory as well, so the project carries both conventions, with the app directory probably holding the route handlers or a newer section in progress.
There is also a CNAME file at the root, which typically records the custom domain used for hosting.
The promise queue is the actual engineering
The section on implementation details is the most instructive part of the README, and it is about a problem that anyone writing against the Spotify Web API hits.
The project describes itself as a good example of how to traverse a user's library without incurring a rate limit. The mechanism is Promises and a promise queue, used to control the amount of requests sent to the Web API.
That is the right shape for the problem. A library traversal is a graph walk: you fetch a playlist, you get its tracks, you follow the pagination cursor, and for saved tracks you fetch the items and then fetch each item's details. Done naively that is a burst of hundreds of requests, and the Web API will start refusing them. Done naively but slowly, it is a crawl. A queue that keeps a fixed number of requests in flight is neither, and the promise-throttle package that the README points at, written by the same author, is the implementation of exactly that idea.
It is worth noticing what this project is, in a way that the name does not suggest. It is a small, complete reference implementation of a rate-limit-aware API traversal in TypeScript, in a Next.js application, with the traversal as the point and the deduplication as the reason it exists. If you are writing anything against a paginated REST API with a request budget, the dedup/ and lib/ directories are worth reading even if you never use the site.
A Next.js app with translations, charts and a fetched library
The dependency list describes a fairly complete small application.
The data layer is axios for HTTP and swr for fetching and caching on the client, with node-fetch present as a dependency for server-side calls, and cheerio for parsing HTML, which is what a tool that inspects pages rather than APIs would need. The chart is chart.js, and it is presumably what draws the duplicate distribution the user sees before deciding to remove anything.
The interface is React with Radix UI for the progress component, Tailwind for styling with tailwindcss-animate, clsx and tailwind-merge for class composition, and postcss behind it. The configuration files at the root match that: postcss.config.cjs, tailwind.config.js, and components.json for the component library setup.
Internationalisation is a first-class concern rather than an afterthought. There is i18n.ts at the root, languages.ts beside it, i18next and react-i18next for the runtime, next-i18next for the Next.js integration, and layouts/ and components/ directories. The manifest also carries eslint-config-next and dotenv, so the local configuration is environment driven.
The remaining top-level directories, app/, public/, src/, styles/, utils/ and lib/, are the conventional Next.js split between routing, static assets, source, styles, helpers and shared library code.
How to test it, including creating duplicates on purpose
The testing section describes something most projects of this size do not, which is a way to produce the condition the app is built to fix.
To test saved tracks, the README asks you to create duplicated tracks by executing a Web API request on the Spotify developer console, the put-current-user-saved-tracks endpoint, with a list of specific track identifiers. The request given includes the identifiers 2JZfTvWWtpaE8NohqRXqFr, 1poUtf2dDdVUtWL8tn03Wd, 6ADSaE87h8Y3lccZlBJdXH, 2x45xqISlmmDJqxOqr8BuS and 1iQ1BpOGF1Umd3lpTV4OPO.
Adding the same tracks to your saved library more than once produces exactly the duplication the tool looks for, which gives you a reproducible test case instead of waiting for the real thing to happen.
The test tooling is Jest with ts-jest and the jsdom environment, jest-environment-jsdom at version 29, and the testing-library packages for React and jest-dom. The configuration is jest.config.js with jest.setup.js and the corresponding type declaration files jest-setup.d.ts and env.d.ts, and the scripts are test, test:watch and test:coverage.
The coverage script matters for a project whose core logic is a traversal: the interesting behaviour is in how requests are scheduled and in which tracks get identified, and that is exactly the kind of code a coverage report should be pointing at.
Maintenance state and the small print
The repository is not archived, and the last push was on 2026-06-12. There are no GitHub releases, so the version history is the commit log, and the package version in the manifest is 2.0.0.
That the manifest says 2.0.0 while the last release tag does not exist is worth pausing on, because it means the released artefact and the source tree are not the same thing by construction. The README badge is a Greenkeeper badge.
The Next.js version in the manifest is 15.0.3, and the React version is 18.3.1, while the package is on a 2.x line of its own. Nothing in the repository suggests the project is abandoned, but a project with no releases, a last push four months old and an older dependency set is one to check the commit log on before you build on it.
The scope point is the one to keep in mind when you decide. This is a tool for one specific job, finding exact URI duplicates in playlists, with the emphasis in the README on not creating a new playlist so the metadata survives. It is not a library organiser, it is not a playlist organiser in the sense of reordering or re-describing, and it does not touch your liked songs beyond the saved-tracks traversal.
Editorial conclusion
Adopt spotify-dedup if your Spotify playlists have accumulated the same track twice and you want the duplicates removed without losing the playlist's creation date or subscriber list, which is what a rebuild-and-replace approach would cost you. Do not adopt it expecting it to find near-duplicates, because the rule is exact URI equality, and do not expect it to run against a large library without patience, since the traversal is one request at a time under a throttle. Before you start, log in with an account that owns the playlists you want to clean, and test on a small playlist first, because removal is the action that matters and the tool does not ask twice.
Frequently asked questions
How does spotify-dedup decide that two tracks are duplicates?
It looks for songs that appear multiple times with the same identifier, the Spotify URI, in a given playlist. The rule is exact URI equality, so the same recording released under a different URI is not treated as a duplicate.
How do I run spotify-dedup locally?
Run pnpm install and then pnpm dev, and open http://localhost:3000 in a browser. The dev script is next dev, and the production equivalents are next build and next start.
Does spotify-dedup rebuild my playlists when it cleans them?
No. It removes the duplicate entries in place rather than creating a new playlist, and the stated reason is that this keeps the playlist information such as the creation date and the subscribers.
How does spotify-dedup avoid hitting the Spotify Web API rate limit?
The README describes it as an example of traversing a user's library without incurring a rate limit, using Promises and a promise queue to control the number of requests sent to the Web API, and it points at the promise-throttle package for the throttling pattern.
How can I test spotify-dedup without waiting for real duplicates?
The README describes creating duplicated tracks on purpose by executing the put-current-user-saved-tracks Web API request from the Spotify developer console with a list of track identifiers, which produces saved tracks that appear more than once.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/jmperez-spotify-dedup)Was this useful?