Spotifast Rebuilds Spotify in Rust, Then Runs It on Two Identities at Once
A native egui client with no browser engine and a fraction of the memory. Its real trick is a runtime switchboard that picks the right Spotify app for every single API call.
- Spotifast is a native Rust Spotify client that skips the embedded browser engine entirely, trading hundreds of megabytes of RAM for something closer to 150MB.
- Its signature move is running two separate Spotify app identities at once and routing every API call to whichever one is allowed to answer.
- A pure plan() function encodes Spotify's Development Mode rules as code, flipping destinations based on playlist ownership that is only discovered mid-request.
- It ships Winamp 2 skins, an equalizer, and MilkDrop because someone clearly misses 1998.
The 600MB Problem
The official Spotify desktop client is a web application wearing a desktop costume. It ships an embedded browser engine, a full rendering stack, and enough background machinery that playing one song can cost between 600MB and 1GB of RAM.
Spotifast does the same job in 100 to 250MB. It is a native Rust application built on egui, an immediate-mode GUI toolkit, with librespot handling the audio. No browser engine. No Chromium. No web view. The project's own README and website publish those memory figures, so treat them as the author's claims rather than an independent benchmark.
The tagline is three words long.
Spotify, native and fast.
That is the pitch. Everything interesting lives in what the pitch leaves out.
The Trick Is Not the Renderer
Drawing a music player in egui is not the hard part. Playlists, search results, an album grid, a queue: these are solved problems, and dozens of projects have solved them.
The hard part is that Spotify will not let one app do everything.
Development Mode, Spotify's sandbox for registered applications, restricts what any single identity can read. A personal app registered by a developer gets tighter quotas and narrower access than Spotify's own shared public app. Some operations are only legal from one identity. Some are only legal from the other. A client that wants to browse a library, edit playlists, and search the catalog has to be more than one app.
Spotifast's answer is to register two identities and decide, per request, which one is allowed to answer.
Two Apps, One Client
The code names them SHARED and PERSONAL.
SHARED is a public Web API application. The source comments say its client ID is shared with spotify-player, ncspot, and Omarchy Spotify. PERSONAL is the user's own registered Client ID, supplied during sign-in.
The two identities are not interchangeable. Each carries its own limits. The SHARED profile allows search_limit: 20 and artist_albums_limit: 50. The PERSONAL profile allows 10 and 10. Spotify constrains personal apps more tightly, so the shared identity is less a fallback than a wider door.
Every API call passes through one pure function, plan(), which returns either Shared or Personal.
The function is a match statement. It reads like a policy document.
fn plan(operation: Operation, personal_ready: bool) -> ApiSource {
match operation {
CanonicalAccount
| PlaylistLibrary
| PlaylistSearch
| ArtistTopTracks
| UnsupportedDevelopmentMode => Shared,
PlaylistMetadata(External | Unknown)
| PlaylistItems(External | Unknown)
| PlaylistMutation(External | Unknown) => Shared,
PlaylistMetadata(_) | PlaylistItems(_) | PlaylistMutation(_)
if personal_ready => Personal,
Playback | UserData | PlaylistCreation | Catalog | CatalogSearch
if personal_ready => Personal,
_ => Shared,
}
}
How a Request Picks a Side
The input to plan() is an Operation, an enum with about 13 variants: CanonicalAccount, Playback, UserData, PlaylistLibrary, PlaylistSearch, PlaylistMetadata, PlaylistItems, PlaylistMutation, PlaylistCreation, ArtistTopTracks, Catalog, CatalogSearch, and UnsupportedDevelopmentMode.
Most of these route the same way every time. CanonicalAccount, PlaylistLibrary, PlaylistSearch, and ArtistTopTracks always go to SHARED. They are operations the shared identity can serve.
The playlist operations are different. They carry a PlaylistAccess value, and that value changes the destination.
| PlaylistAccess | Meaning | Destination |
|---|---|---|
| Owned | The signed-in user owns the playlist | Personal, when personal_ready |
| Collaborative | Shared editing, user is a member | Personal, when personal_ready |
| External | Owned by someone else | Shared |
| Unknown | Owner not yet known | Shared, then re-routed once the owner is revealed |
The Unknown case is the interesting one. When Spotifast does not yet know who owns a playlist, it cannot know which identity is allowed to read it. So it sends the request to SHARED, which can serve anything. Once the response reveals the owner, the gateway re-routes subsequent calls.
The routing decision is not made once at startup. It is made per request, with information that arrives mid-flight. Spotifast is not a load balancer for traffic. It is a load balancer for permissions.
You Sign In Twice, On Purpose
The same constraint shows up in authentication.
auth.rs implements OAuth 2.0 Authorization Code with PKCE. The module comment is explicit about the security boundary: the browser handles password entry, and the local process only ever sees the one-time authorization code Spotify sends back to a loopback listener.
The central abstraction is a Grant, which bundles a client_id, a redirect_port, and a list of scopes. There are three constructors.
playback()uses Spotify's own desktop client ID, port 8898, and the single scopestreaming.shared_web_api()uses the public Web API app ID, port 8989, and 15 scopes.personal_web_api(client_id)takes the user's own registered Client ID and rejects empty input.
The PKCE details are careful. begin() generates a 48-byte random verifier, derives an S256 challenge, and adds an 18-byte state value. The verifier never touches the browser.
The loopback port is reserved before the browser opens. listen_for_redirect binds the socket first and fails with an actionable message if the port is taken, which prevents a race where the browser returns to a closed port. The listener then waits for the code with a 10-minute timeout and a cancel signal.
Scopes are minimized on purpose. The Web API sign-in requests user-read-private so the app can detect whether the account is Free or Premium, which decides whether local playback is offered. No email.
The Winamp Skin in the Machine
For all the policy machinery, Spotifast is also a love letter to desktop music software.
It loads Winamp 2 .wsz skins. It has an equalizer. It has MilkDrop visualizations. There are dedicated winamp, skin, eq, and vis modules. The project ships a sample skin and an Omarchy theme template. On macOS there is a menu bar integration and a notch widget. On Windows there is a taskbar thumbnail toolbar.
None of this is necessary. All of it is the point.
Where It Fits
Spotifast sits between two poles. On one side, Spotify's official client: fully supported, fully featured, and heavy. On the other, terminal clients like spotify-player, ncspot, and spotify-tui: light, fast, and keyboard-driven.
| Project | Interface | Playback engine | Browser engine | Playlist editing |
|---|---|---|---|---|
| Spotifast | Native GUI (egui) | librespot | No | Yes |
| Spotify official | Official GUI | Proprietary | Yes | Yes |
| spotify-player | TUI | librespot | No | Varies |
| ncspot | TUI | librespot | No | Varies |
| Spotube | Flutter GUI | Varies | No | Varies |
Figures are as documented by each project and change over time.
The community is already asking for more than a lighter Spotify. A commenter on the project's Show HN post asked whether it could ingest music from other sources.
Great work!! Would you be willing to consider supporting ingesting music using yt-dlp and libtorrent, identifying and categorizing those imports using Musicbrainz, and potentially storing them in an S3 compatible target (versus a POSIX filesystem)?
That request is outside the project's scope today. The repository is MIT-licensed, so it does not have to stay that way.





