Documentation
Everything you need to deploy, configure, and extend Crate.
Overview
Crate is a self-hosted music manager for collectors. It connects to music metadata providers (MusicBrainz, Deezer, or your own) to search and browse artists, then downloads tracks from Soulseek via slskd. Downloaded files are automatically organized, tagged with full metadata and cover art, and placed in your library.
The stack is a Go backend with SQLite, a React frontend, and standalone gRPC provider binaries — all shipped in a single Docker image.
Deployment
The recommended deployment is via Docker Compose with slskd as a sidecar. Crate needs a Soulseek account (provided through slskd) to download music.
# docker-compose.yml
services:
crate:
image: ghcr.io/theoutdoorprogrammer/crate:latest
ports:
- "6969:6969"
volumes:
- ./crate:/app/data # SQLite databases
- ./slskd/downloads:/app/downloads # Must match slskd's download dir
- ./library:/app/library # Organized music library
environment:
- CRATE_SLSKD_URL=http://slskd:5030
- CRATE_SLSKD_API_KEY=your_api_key
depends_on:
- slskd
restart: unless-stopped
slskd:
image: slskd/slskd:latest
ports:
- "5030:5030"
volumes:
- ./slskd:/app # slskd data (config, downloads)
- ./library:/music # Share your library on Soulseek
environment:
- SLSKD_REMOTE_CONFIGURATION=true
- SLSKD_API_KEY=your_api_key
- SLSKD_SOULSEEK_USERNAME=your_username
- SLSKD_SOULSEEK_PASSWORD=your_password
restart: unless-stopped
/app/downloads) must point to the same host directory that slskd writes completed downloads to. With the config above, slskd stores downloads under ./slskd/downloads/ and crate reads from the same path. If these don't match, crate won't find downloaded files.
CRATE_SLSKD_API_KEY must match the SLSKD_API_KEY you set for slskd. These two services communicate over the slskd REST API.
Volumes
Three volume mounts are important:
- Data directory — where Crate stores its SQLite databases. Map to a persistent path.
- Downloads directory — shared with slskd. Crate reads completed downloads from here. Must be the same path slskd writes to.
- Library directory — where organized, tagged music files end up. Point your media server (Navidrome, Plex, etc.) here.
Authentication
Crate does not include built-in authentication. It's designed to run behind your existing auth layer — a reverse proxy with basic auth, Authelia, Authentik, Cloudflare Access, a VPN, or whatever you prefer. If it's only accessible on your local network, no auth may be necessary.
Environment Variables
These are set at startup and control core paths, ports, and provider configuration. All have sensible defaults — only CRATE_SLSKD_API_KEY is required.
| Variable | Default | Description |
|---|---|---|
CRATE_PORT |
6969 |
HTTP port the web UI and API listen on. |
CRATE_DB_PATH |
./crate.db |
Path to the main SQLite database. |
CRATE_CACHE_PATH |
./cache.db |
Path to the provider cache database. Stores cached search results and metadata with a configurable TTL. |
CRATE_ACTIVITY_PATH |
./activity.db |
Path to the activity log database. Records download events, errors, and system events. |
CRATE_DOWNLOADS_DIR |
./downloads |
Directory where slskd places completed downloads. Crate reads from here. |
CRATE_LIBRARY_PATH |
./library |
Directory where organized music files are placed. Layout is controlled by the naming template. |
CRATE_SCAN_INTERVAL |
6h |
How often the scheduler runs (new release detection, auto-queue, quality upgrades). Accepts Go duration strings: 30m, 6h, 24h, etc. |
CRATE_SLSKD_URL |
http://localhost:5030 |
Base URL of your slskd instance. |
CRATE_SLSKD_API_KEY |
none | API key for slskd. Required for downloads to work. |
CRATE_PROVIDERS |
musicbrainz:./provider-musicbrainz:50051,deezer:./provider-deezer:50052 |
Comma-separated list of providers. Format: name:binary:port. See Providers. |
CRATE_MB_USER_AGENT |
Crate/0.1.0 ... |
User-Agent string for MusicBrainz API requests. MusicBrainz requires a descriptive User-Agent. |
UI Settings
These settings are stored in the database and configurable from the Settings page in the web UI. Changes take effect immediately without restarting.
| Setting | Default | Description |
|---|---|---|
provider_primary |
musicbrainz |
Default provider for search and browse. Can be switched on the fly from the search UI. |
cache_ttl_hours |
24 |
How long provider search results and metadata are cached before re-fetching. |
quality_tiers |
FLAC > MP3 320 > MP3 256 |
Priority-ordered list of acceptable download formats. The downloader picks the highest-tier match from available sources. Stored as JSON. |
quality_fallback_enabled |
true |
Whether to accept files outside configured quality tiers. When disabled, only files matching a tier are downloaded. |
negative_keywords |
[] |
JSON array of keywords (e.g. ["acapella", "instrumental"]). Files matching any keyword are skipped during auto-download. Manual search still shows them, marked as negative matches. |
naming_template |
{artist}/{album} ({year})/{track:2} - {title} |
Folder/file layout for new downloads. See Naming Template. Empty = default. |
max_concurrent_slskd |
10 |
Maximum number of simultaneous downloads from Soulseek. |
shadow_ban_duration_minutes |
60 |
How long to temporarily block a user after failed or stale downloads. Expired bans are cleaned up automatically. |
max_auto_queue |
50 |
Maximum tracks the scheduler will auto-queue per cycle. Prevents flooding the download queue. |
requeue_cooldown_days |
7 |
Minimum days before a failed track can be re-queued by the scheduler. |
activity_retention_days |
30 |
How long activity log entries are kept before automatic cleanup. |
scan_interval |
from env | Override the scheduler interval at runtime (same format as CRATE_SCAN_INTERVAL). |
slskd_url |
from env | Override the slskd URL at runtime. |
slskd_api_key |
from env | Override the slskd API key at runtime. Treated as sensitive (masked in the UI). |
library_path |
from env | Override the library path at runtime. |
download_format_preference |
none | Preferred download format when multiple options are equivalent in quality tier. |
navidrome_url |
none | Base URL of your Navidrome instance. See Navidrome Integration. |
navidrome_user |
none | Navidrome username for triggering library scans. |
navidrome_password |
none | Navidrome password. Treated as sensitive (masked in the UI). |
slskd_url, slskd_api_key, library_path, and scan_interval can be set via environment variables at startup or overridden in the UI. The UI value takes precedence when set.
Naming Template
The folder and file layout for downloads is a template you can change from Settings → Library, with a live preview as you type. Use it to match an existing library convention (e.g. one served by Navidrome) so Crate's downloads slot in cleanly. The default matches Crate's original layout:
{artist}/{album} ({year})/{track:2} - {title}
| Token | Meaning |
|---|---|
{artist} / {albumartist} |
Artist name. Crate tracks album artists, so these are identical — {albumartist} exists for familiarity with Lidarr/beets templates. |
{album} |
Album title. |
{year} |
Album release year. Renders empty when unknown. |
{track} |
Track number. Zero-pad with a width: {track:2} renders 6 as 06. |
{disc} |
Disc number. Supports padding. Renders empty when unknown. |
{title} |
Track title. |
Example: to match the common Artist/Artist - Year - Album convention:
{artist}/{artist} - {year} - {album}/{track:2} {title}
Behavior
- Extension is automatic — the downloaded file's extension is appended; don't put it in the template.
- Empty tokens clean up after themselves — when
{year}is unknown,{album} ({year})renders asAlbum Title, notAlbum Title (). Dangling separators next to empty tokens are removed the same way. - New downloads only — changing the template never renames existing files. Quality upgrades organize with the current template and remove the file they replace, even if it lived at a path from an older template.
- Safety — templates must be relative paths, the filename must contain
{title}or{track}, and filesystem-unsafe characters (<>:"/\|?*) in metadata are replaced with_. Invalid templates are rejected when you save.
Providers
Providers supply music metadata (artist search, discography browsing, album/track details, cover art). Crate ships with two built-in providers and supports adding custom ones via gRPC.
Built-in Providers
| Provider | Source | Rate Limit | Notes |
|---|---|---|---|
| MusicBrainz | MusicBrainz.org API | 1 req/s | Open database. Best for completeness and accuracy. Default provider. |
| Deezer | Deezer public API | 10 req/s | Faster browsing, good cover art. Useful as a secondary provider. |
How Providers Work
- Default provider — set in settings as
provider_primary. Used for search and browse unless the user switches providers in the search UI. - Entity tracking — every artist, album, and track stores which provider and provider ID it came from. This is how Crate knows where to check for new releases.
- Provider switching — you can change the default provider at any time. Existing entities keep their original provider association.
- Relinking — any entity can be relinked to a different provider via the API. Useful if you switch primary providers and want to unify your library.
- Orphan detection — if a provider goes offline, entities from that provider show as “orphaned” in the UI.
- Caching — provider responses are cached in a separate SQLite database (
cache.db) with configurable TTL. The cache can be cleared from the Settings page.
Provider Configuration
The CRATE_PROVIDERS environment variable defines which providers to load. Format:
# Built-in providers (binary path relative to crate binary)
CRATE_PROVIDERS=musicbrainz:./provider-musicbrainz:50051,deezer:./provider-deezer:50052
# External provider (network address instead of binary path)
CRATE_PROVIDERS=musicbrainz:./provider-musicbrainz:50051,spotify:external:192.168.1.10:50053
Built-in providers are started as child processes by Crate. External providers connect to a remote gRPC server you run separately.
Download Flow
When you watch an artist, album, or track, here's what happens:
- Queuing — watched items are saved with status
wanted. The scheduler checks for new wanted tracks every scan interval. - Search — Crate searches slskd (Soulseek) for each wanted track, scoring all results through a unified scoring system.
- Selection — the highest-scoring result is selected. Blacklisted files, shadow-banned users, and files outside configured tiers (when fallback is disabled) are excluded.
- Download — the file is downloaded via slskd. Track status changes to
downloading. The downloader checks progress every 10 seconds. - Organize — completed downloads are moved into the library using the configurable naming template.
- Tag — metadata (artist, album, track name, track number, year) and cover art are embedded into the file.
- Notify — if Navidrome is configured, a library scan is triggered. Track status becomes
owned.
Retry, Blacklist & Shadow Bans
Not every download succeeds on the first try. Crate handles failures automatically:
- No results — track stays
wanted. The scheduler will try again on the next cycle. - Transfer failure (rejected, errored, cancelled) — the specific username + filename pair is permanently blacklisted. Future searches skip that source but can still find the track from other users.
- Stale detection — downloads are monitored for progress with state-aware timeouts: 5 minutes for active transfers, 30 minutes for queued transfers (waiting for a slot), and 10 minutes for requested transfers. If no progress is made within the timeout, the download is cancelled.
- Shadow banning — when a queued download stalls or a download fails to start (e.g. user offline), the user is temporarily blocked for a configurable duration (default 60 minutes). Unlike permanent file blacklists, shadow bans expire automatically. This prevents wasting time retrying users who are consistently unavailable.
- Retry backoff — failed downloads retry with increasing delays: 5m → 15m → 30m → 1h. After 4 attempts (~2 hours), the track reverts to
wantedfor the next scheduler cycle. - Management — view and remove both blacklisted files and shadow-banned users from the Blocked Sources section in Settings.
Concurrency
The max_concurrent_slskd setting (default 10) limits how many downloads run simultaneously. The max_auto_queue setting (default 50) caps how many tracks the scheduler adds per cycle, preventing the queue from growing unbounded.
Scoring System
Every search result is scored through a unified pipeline that balances quality, correctness, and availability. The highest-scoring file wins.
| Component | Points | Description |
|---|---|---|
| Quality tier | 25 – 100 | Based on your configured tier list. Tier 1 = 100, Tier 2 = 75, Tier 3 = 50, etc. Files outside configured tiers use a fallback score (capped below the lowest tier). |
| Artist match | +20 | Bonus if the artist name appears in the filename. Increases confidence it's the correct file. Intentionally kept below the tier gap (25) so quality always dominates. |
| Free upload slot | +10 | Bonus if the user has a free upload slot, meaning the download can start immediately. |
| Queue length | 0 – 15 | Inverse decay: 15 / (1 + queueLength). Empty queue = 15 points. Queue of 5 = 2 points. Longer queues approach zero. |
The title must appear in the filename (hard filter). All bonuses combined (max 45) can overcome one quality tier gap but never two, ensuring your quality preferences are respected while still favoring available, correctly-labeled sources.
quality_fallback_enabled is set to false, files that don't match any configured tier are rejected entirely. When enabled (default), they receive a reduced score below the lowest tier.
acapella or instrumental in Settings. Files matching any keyword are automatically skipped during scheduled downloads. Manual search still returns them (dimmed, labeled "negative keyword") so you can override when needed.
Quality Upgrades
Crate continuously improves your library quality. The quality system works as follows:
- Quality tiers are priority-ordered in settings (default: FLAC > MP3 320 > MP3 256). Higher in the list = better.
- When a track is downloaded, its format and bitrate are recorded.
- The scheduler scans one artist per day (round-robin), checking if any owned tracks can be upgraded to a higher quality tier.
- Upgradeable tracks are re-queued automatically. The new download replaces the old file.
The requeue_cooldown_days setting (default 7) prevents the scheduler from re-queuing the same track too frequently if better quality isn't available yet.
Lidarr API Compatibility
Crate includes a Lidarr v1 API compatibility shim at /api/v1/, allowing iOS and Android apps built for Lidarr to manage your Crate library. No extra configuration needed — point the app at your Crate URL with any API key and it works.
Concept Mapping
Lidarr and Crate model things differently. The shim translates between them transparently:
| Lidarr Concept | Crate Equivalent | Notes |
|---|---|---|
| Monitor "all" | Artist status watched |
Full discography tracked. |
| Monitor "latest" | Watch newest album + enable new releases | Most recent album by year, future albums auto-added. |
| Quality profile | "Crate Quality" | Crate uses priority-ordered quality tiers instead of Lidarr-style profiles. |
| Metadata profile | Active provider name | Shows whichever provider is set as primary (MusicBrainz, Deezer, etc.). |
| Root folder | CRATE_LIBRARY_PATH |
Library directory with real disk usage stats. |
| Monitored album | Album status watched |
Tracks set to wanted. |
| Unmonitored album | Album status ignored |
Tracks cascade to ignored, preserving owned and downloading states. |
| ArtistSearch command | Queue all wanted tracks | Same as "Search wanted tracks" in the Crate UI. |
| AlbumSearch command | Queue album's wanted tracks | Same as searching from the album page. |
| "Search for missing" on add | Auto-queue after watch | Queues all wanted tracks immediately when enabled. |
Supported Endpoints
The shim implements the Lidarr v1 endpoints that mobile apps typically use:
- System — status, health check, disk space
- Profiles — quality profiles, metadata profiles
- Library — root folders, custom filters, calendar, history, wanted/missing
- Artists — search, lookup, add (with monitoring strategy), list, get, delete
- Albums — list, delete, monitor/unmonitor toggle (with track status cascade)
- Tracks — list tracks for an album
- Downloads — queue status
- Commands — ArtistSearch, AlbumSearch
Authentication
The shim accepts any API key in the X-Api-Key header or apikey query parameter. Crate has no built-in auth — use a reverse proxy if you need access control.
Contributing
The Lidarr shim covers the most common endpoints. If your app needs an endpoint that isn't implemented yet, contributions are welcome — the shim is a single file at internal/api/lidarr.go.
Library Import
Crate can adopt a music collection it didn't download. Settings → Library → Import Existing Library scans your library folder, reads embedded tags, and records everything as owned — making Crate a drop-in manager for an established Navidrome/media-server setup rather than a fresh-start tool.
How it works
- Tag-based, not path-based — folder structure and file names are ignored entirely; only embedded metadata (MP3 ID3, FLAC Vorbis comments) matters. If your library plays correctly in a tag-based server like Navidrome, it will import correctly.
- Dry run first — the scan preview reports exactly what an import would add, claim, and skip, without writing anything.
- Non-destructive, always — files are never moved, renamed, or modified. Import doesn't apply the naming template to existing files.
- MusicBrainz tags link automatically — libraries tagged by Picard or beets carry MusicBrainz IDs, and those entities import under the MusicBrainz provider with their real IDs. Everything else imports under the reserved
localprovider and can be relinked to a real provider anytime (for search, cover art, or new-release watching). - Wanted tracks get claimed — files matching tracks you already watch flip them to owned instead of leaving them queued for download.
- Quality upgrades apply — imported tracks record their real format and bitrate (parsed from the files). Import MP3s with a FLAC-first tier list and the upgrade scanner will gradually replace them, one artist per day. Files outside the library folder are never deleted, even when an upgrade replaces them.
- Idempotent — re-running an import skips what it already knows. Files missing artist/album/title tags are skipped and reported with reasons.
Navidrome Integration
Crate can trigger a Navidrome library scan after each successful download, so new music appears in your streaming library immediately.
Setup
Configure these three settings in the Crate UI (Settings page):
| Setting | Example |
|---|---|
navidrome_url |
http://navidrome:4533 |
navidrome_user |
admin |
navidrome_password |
your_password |
All three fields are required. If any are empty, the Navidrome integration is silently disabled. Crate authenticates via the Subsonic API token+salt scheme.
CRATE_LIBRARY_PATH and Navidrome's music folder to the same directory (or use a shared volume). Crate organizes files using the naming template, which you can set to match an existing library convention, and Navidrome indexes them automatically.
Supported Formats
| Format | Extension | Type |
|---|---|---|
| FLAC | .flac | Lossless |
| WAV | .wav | Lossless |
| MP3 | .mp3 | Lossy |
| Ogg Vorbis | .ogg | Lossy |
| Opus | .opus | Lossy |
| AAC | .aac | Lossy |
| MPEG-4 Audio | .m4a | Lossy |
The downloader recognizes all these formats when scoring search results. The tagger embeds metadata into FLAC (Vorbis comments) and MP3 (ID3v2) files. Cover art is embedded in both formats.
API Reference
Crate exposes a REST API on the same port as the web UI. All endpoints return JSON.
Status
| Method | Path | Description |
|---|---|---|
GET | /api/status | Health check and system status. |
Search & Browse
| Method | Path | Description |
|---|---|---|
GET | /api/search?q=&limit=&offset= | Search for artists via the active provider. Paginated. |
GET | /api/browse/artist/{id} | Browse an artist's full discography from the provider. |
GET | /api/browse/album/{id} | Browse an album's track listing from the provider. |
Library
| Method | Path | Description |
|---|---|---|
GET | /api/library/search?q= | Search your local library. |
GET | /api/artists | List all watched/owned artists. |
GET | /api/artists/{id} | Get artist details with albums. |
DELETE | /api/artists/{id} | Remove artist and all associated data. |
GET | /api/albums/{id} | Get album details with tracks. |
DELETE | /api/albums/{id} | Remove album. |
GET | /api/tracks/{id} | Get track details. |
DELETE | /api/tracks/{id} | Remove track. |
Watching
| Method | Path | Description |
|---|---|---|
POST | /api/watch/artist | Watch an artist (full discography tracking). |
POST | /api/watch/album | Watch a specific album. |
POST | /api/watch/track | Watch a specific track. |
Downloads
| Method | Path | Description |
|---|---|---|
GET | /api/downloads | List current download queue. |
POST | /api/downloads/{id}/retry | Retry a failed download. |
DELETE | /api/downloads/{id} | Cancel/remove a download. |
Providers
| Method | Path | Description |
|---|---|---|
GET | /api/providers | List all configured providers with health status. |
POST | /api/relink/artist/{id} | Relink an artist to a different provider. |
POST | /api/relink/album/{id} | Relink an album to a different provider. |
POST | /api/relink/track/{id} | Relink a track to a different provider. |
Blocked Sources
| Method | Path | Description |
|---|---|---|
GET | /api/blacklist | List all permanently blacklisted username+file pairs. |
DELETE | /api/blacklist/{id} | Remove a blacklist entry (allows re-downloading from that source). |
GET | /api/cooldowns | List active shadow bans (temporary per-user blocks). |
DELETE | /api/cooldowns/{id} | Remove a shadow ban (immediately unblocks the user). |
Settings & Activity
| Method | Path | Description |
|---|---|---|
GET | /api/settings | Get all settings (sensitive values masked). |
PUT | /api/settings | Update settings. Rejects invalid naming_template values. |
GET | /api/settings/naming-preview?template= | Render a naming template against sample metadata. Empty = default template. |
POST | /api/library/import | Start a library import. Body: {"path": "...", "dry_run": true} (both optional; path defaults to the library dir). Returns 409 if one is already running. |
GET | /api/library/import | Import status: state, progress, and the final report. |
GET | /api/activity?limit=&offset= | Get activity log. Paginated. |
DELETE | /api/cache | Clear the provider cache. |
Custom Providers
You can build your own metadata provider by implementing the gRPC service defined in proto/provider/provider.proto. The service interface includes:
SearchArtists— search by name, return ranked resultsGetArtist— full artist details with discographyGetAlbum— album details with track listingGetTrack— individual track details
Run your provider as a standalone gRPC server and add it to the CRATE_PROVIDERS env var:
CRATE_PROVIDERS=musicbrainz:./provider-musicbrainz:50051,my-provider:external:my-host:50053
Use the external keyword as the binary path to tell Crate it should connect to a remote server instead of launching a child process. Community-built providers and the database schema are documented in DATABASE.md.
Database
Crate uses three SQLite databases:
crate.db— main database. Artists, albums, tracks, settings, download queue, blacklist. WAL mode, single writer connection. Schema migrations via goose (embedded SQL files, run automatically on startup).cache.db— provider response cache with TTL. Can be safely deleted to force re-fetching, or cleared from the Settings page.activity.db— event log. Download completions, errors, scheduler actions. Auto-pruned based onactivity_retention_days.
The full database schema is documented in DATABASE.md in the repository.