# [plyr.fm](https://plyr.fm)
music on [atproto](https://atproto.com)
check the [plyr.fm artist page](https://plyr.fm/u/plyr.fm) for the latest [auto-generated](.github/workflows/status-maintenance.yml) development podcast!
tech stack
### backend
- **framework**: [FastAPI](https://fastapi.tiangolo.com)
- **database**: [Neon PostgreSQL](https://neon.com)
- **storage**: [Cloudflare R2](https://developers.cloudflare.com/r2/)
- **background tasks**: [docket](https://github.com/zzstoatzz/docket) (Redis-backed)
- **hosting**: [Fly.io](https://fly.io)
- **observability**: [Pydantic Logfire](https://logfire.pydantic.dev)
- **auth**: [atproto OAuth 2.1](https://atproto.com/specs/oauth)
### frontend
- **framework**: [SvelteKit](https://kit.svelte.dev) with Svelte 5 runes
- **runtime**: [Bun](https://bun.sh)
- **hosting**: [Cloudflare Pages](https://pages.cloudflare.com)
- **styling**: vanilla CSS (lowercase aesthetic)
### services
- **moderation**: Rust ATProto labeler for copyright/sensitive content
- **transcoder**: Rust audio conversion service (ffmpeg)
local development
### prerequisites
- [uv](https://docs.astral.sh/uv/) for Python
- [bun](https://bun.sh/) for frontend
- [just](https://github.com/casey/just) for task running
- [docker](https://www.docker.com/) for dev services (redis)
### quick start
```bash
# install dependencies
uv sync
cd frontend && bun install && cd ..
# start dev services (redis for background tasks)
just dev-services
# run backend (hot reloads at http://localhost:8001)
just backend run
# run frontend (hot reloads at http://localhost:5173)
just frontend dev
```
### useful commands
```bash
# run tests
just backend test
# run linting
just backend lint
just frontend check
# database migrations
just backend migrate "migration message"
just backend migrate-up
# stop dev services
just dev-services-down
```
features
### listening
- audio playback with persistent queue across tabs
- like tracks, add to playlists
- browse artist profiles and discographies
- share tracks, albums, and playlists with link previews
- unified search with Cmd/Ctrl+K
- teal.fm scrobbling
### creating
- OAuth authentication via ATProto (bluesky accounts)
- upload tracks with title, artwork, tags, and featured artists
- organize tracks into albums and playlists
- drag-and-drop reordering
- timed comments with clickable timestamps
- artist support links (ko-fi, patreon, etc.)
### data ownership
- tracks, likes, playlists synced to your PDS as ATProto records
- portable identity - your data travels with you
- public by default - any client can read your music records
project structure
```
plyr.fm/
├── backend/ # FastAPI app
│ ├── src/backend/ # application code
│ │ ├── api/ # public endpoints
│ │ ├── _internal/ # services (auth, atproto, background tasks)
│ │ ├── models/ # database schemas
│ │ └── storage/ # R2 adapter
│ ├── tests/ # pytest suite
│ └── alembic/ # migrations
├── frontend/ # SvelteKit app
│ ├── src/lib/ # components & state
│ └── src/routes/ # pages
├── moderation/ # Rust labeler service
├── transcoder/ # Rust audio service
├── docs/ # documentation
└── justfile # task runner
```
costs
~$35-40/month:
- fly.io backend (prod + staging): ~$10/month
- fly.io transcoder: ~$0-5/month (auto-scales to zero)
- neon postgres: $5/month
- audd audio fingerprinting: ~$10/month
- cloudflare (pages + r2): ~$0.16/month
## links
- **production**: https://plyr.fm
- **staging**: https://stg.plyr.fm
- **API docs**: https://api.plyr.fm/docs
- **python SDK / MCP server**: [plyrfm](https://github.com/zzstoatzz/plyr-python-client) ([PyPI](https://pypi.org/project/plyrfm/))
- **documentation**: [docs/README.md](docs/README.md)
- **status**: [STATUS.md](STATUS.md)
### mirrors
- **github**: https://github.com/zzstoatzz/plyr.fm
- **tangled**: https://tangled.sh/@zzstoatzz.io/plyr.fm