Anonymous music playlist sharing. No accounts, no sign-up. Create a playlist, share the link.
Paste any Spotify, Apple Music, YouTube, or Deezer URL — Plevoid resolves it via song.link and shows platform links for every listener.
- Create and share playlists with a public link; edit via a secret token stored in
localStorageand the URL hash - Song search autocomplete (iTunes + Spotify, interleaved and deduped)
- Import public playlists from Spotify, Deezer (including link.deezer.com short links), or YouTube
- Bulk URL import — paste multiple track URLs at once
- Odesli enrichment: artwork, title, artist, and cross-platform links resolved asynchronously
- Search-added tracks render immediately with preview metadata; full Odesli data fills in via queue
- Platform icons on each track open an in-page iframe player (Spotify, YouTube, Apple Music, Deezer, SoundCloud, Tidal)
- Track reordering via up/down buttons
- CSV export with per-platform URLs (Spotify, Apple Music, YouTube, Deezer, song.link) — compatible with Soundiiz and TuneMyMusic
- Open Graph meta tags on playlist pages for rich link previews on Bluesky, iMessage, Slack, Discord, etc.
- 50-track limit per playlist; playlists deleted after 90 days of inactivity
- Worker — TypeScript + Hono on Cloudflare Workers
- Database — Cloudflare D1 (SQLite)
- Queue — Cloudflare Queues (async Odesli resolution, ≤10 req/min)
- Frontend — three static HTML files, no framework, no build step
- Tests — Vitest (unit tests for pure functions)
Requires mise for Node version management.
cd worker
mise exec -- npm install
mise exec -- npm run db:init # first time only — creates local D1
mise exec -- npm run dev # worker + frontend at http://localhost:8787Running tests:
mise exec -- npm test
mise exec -- npm run typecheckOne-time infra setup (run locally):
cd worker
npx wrangler login
npx wrangler d1 create plevoid-db # paste the database_id into wrangler.toml
npx wrangler queues create plevoid-odesli-queue
npx wrangler d1 execute plevoid-db --remote --file=schema.sql
# required for Spotify import and search
npx wrangler secret put SPOTIFY_CLIENT_ID
npx wrangler secret put SPOTIFY_CLIENT_SECRET
# required for YouTube playlist import
npx wrangler secret put YOUTUBE_API_KEY
# optional — song.link stopped issuing new API keys; anonymous mode works at 10 req/min
npx wrangler secret put ODESLI_API_KEYEvery push to main deploys automatically via GitHub Actions. The workflow runs typecheck and npm test before deploying.
Required GitHub secret: CLOUDFLARE_API_TOKEN (Workers edit permission).
When the schema changes, run the migration on both local and remote D1:
# example
npx wrangler d1 execute plevoid-db --command="ALTER TABLE tracks ADD COLUMN position INTEGER"
npx wrangler d1 execute plevoid-db --remote --command="ALTER TABLE tracks ADD COLUMN position INTEGER"POST /api/playlists/:id/tracks
→ validate URL, check 50-track limit
→ if metadata (search pick): store _preview stub, enqueue, return immediately
→ else: call Odesli synchronously; on error/429 fall back to queue
POST /api/playlists/:id/import/{spotify,deezer,youtube}
→ resolve playlist ID, fetch up to 50 track URLs
→ batch-insert tracks, enqueue each for Odesli resolution
Queue consumer (max_batch_size=1, max_concurrency=1, 6s pre-call sleep)
→ call Odesli API (~7s per message, ≤9 req/min)
→ on 429: sleep Retry-After then retry once; if still 429 → msg.retry()
→ on non-retryable 4xx: write {_notFound:true} and ack
→ UPDATE tracks SET odesli_data = ? -- or {_notFound:true} on 404/4xx
Frontend polls GET /api/playlists/:id every 5s
→ updates track cards when odesli_data arrives or _preview clears
PATCH /api/playlists/:id/tracks/reorder -- token-protected
→ batch-updates position column
GET /api/playlists/:id/export.csv -- public
→ CSV with Title, Artist, Spotify URL, Apple Music URL, YouTube URL, Deezer URL, song.link, Added
→ skips unresolved tracks; X-Skipped-Tracks header reports the count
Cron (every 10 min):
→ delete playlists inactive for 90+ days
→ re-enqueue tracks with odesli_data IS NULL older than 10 minutes (lost queue messages)
Each playlist has a public id (UUID v4) and a secret edit_token (UUID v4). The token is returned once at creation, stored in localStorage and the URL hash (#token=...), and is never returned by the public read endpoint.
Supported platforms: Spotify, Apple Music, YouTube, YouTube Music, Deezer, Tidal, SoundCloud, Amazon Music, Bandcamp, Napster, Anghami, Boomplay, Pandora.
This project follows Semantic Versioning. Releases are tagged in git (v0.2.0, …) and summarised in CHANGELOG.md.
- Patch (
0.x.Y): bug fixes, copy changes, styling tweaks - Minor (
0.X.0): new user-facing features, backwards-compatible API additions - Major (
X.0.0): breaking API changes or significant architecture shifts
To cut a release:
# bump version in worker/package.json, update CHANGELOG.md, then:
git tag v0.2.0
git push origin v0.2.0