Use this file to discover all available pages before exploring further.
Crate stores all library data in a SQLite database (crate.db) opened in WAL mode. The schema is intentionally simple: five tables in the main database plus a separate activity log. This page documents every column, index, and constraint — useful if you are writing a custom importer, a migration script, or running analytical queries against your collection.
The built-in importer (Settings → Library → Import Existing Library) handles the common case: MP3 and FLAC files with embedded tags, with dry-run preview and idempotent re-runs. Use direct database writes only when the built-in importer does not fit — unsupported formats, exotic tag layouts, or bulk migrations from another tool.
CREATE TABLE artists ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, provider TEXT NOT NULL, provider_id TEXT NOT NULL, image_url TEXT, status TEXT NOT NULL DEFAULT 'watched' CHECK (status IN ('watched', 'partial', 'owned')), watch_new_releases INTEGER NOT NULL DEFAULT 0, watch_new_releases_since TEXT, created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')), updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')));
Column
Type
Notes
id
INTEGER PK
Auto-increment
name
TEXT NOT NULL
Artist display name
provider
TEXT NOT NULL
Provider name, e.g. musicbrainz, deezer, local
provider_id
TEXT NOT NULL
Provider-specific ID (MBID, Deezer int as string, loc-… hash)
image_url
TEXT
Artist image URL; nullable
status
TEXT
watched — full discography tracked; partial — some albums present; owned — files exist but not fully tracked
watch_new_releases
INTEGER
0 or 1; opt-in per-artist new-release detection
watch_new_releases_since
TEXT
RFC3339 timestamp; only releases after this date are auto-added
CREATE TABLE albums ( id INTEGER PRIMARY KEY AUTOINCREMENT, artist_id INTEGER NOT NULL REFERENCES artists(id) ON DELETE CASCADE, title TEXT NOT NULL, year INTEGER, provider TEXT NOT NULL, provider_id TEXT NOT NULL, cover_url TEXT, record_type TEXT NOT NULL DEFAULT 'album', status TEXT NOT NULL DEFAULT 'watched' CHECK (status IN ('watched', 'owned', 'ignored')), created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')), updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')));
CREATE TABLE tracks ( id INTEGER PRIMARY KEY AUTOINCREMENT, album_id INTEGER NOT NULL REFERENCES albums(id) ON DELETE CASCADE, title TEXT NOT NULL, track_number INTEGER NOT NULL DEFAULT 1, disc_number INTEGER NOT NULL DEFAULT 1, duration_ms INTEGER NOT NULL DEFAULT 0, provider TEXT NOT NULL, provider_id TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'wanted' CHECK (status IN ('wanted', 'downloading', 'owned', 'ignored')), file_path TEXT, created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')), updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')), -- Added by later migrations: downloaded_from TEXT, download_format TEXT, download_bitrate INTEGER, downloaded_filename TEXT, mb_recording_id TEXT);
Column
Type
Notes
id
INTEGER PK
Auto-increment
album_id
INTEGER FK
References albums(id); cascades on delete
title
TEXT NOT NULL
Track title
track_number
INTEGER
Track number on disc; defaults to 1
disc_number
INTEGER
Disc number; defaults to 1
duration_ms
INTEGER
Duration in milliseconds
provider
TEXT NOT NULL
Same convention as parent album
provider_id
TEXT NOT NULL
Provider-specific track ID
status
TEXT
wanted — queued for download; downloading — in progress; owned — file exists; ignored — suppressed
file_path
TEXT
Path to file on disk when owned. Paths relative to the library dir and absolute paths both work. Crate never deletes files outside the library dir.
downloaded_from
TEXT
slskd username the file was downloaded from
download_format
TEXT
File format detected at search time: flac, mp3, wav, etc.
download_bitrate
INTEGER
Bitrate in kbps (0 for lossless formats like FLAC)
downloaded_filename
TEXT
Original filename as downloaded from slskd, before organizing
mb_recording_id
TEXT
MusicBrainz recording ID (AcoustID/Picard); used as a high-confidence match signal during import
Do not insert rows into download_queue directly. The table is managed entirely by the download service. To make Crate download a track, set tracks.status = 'wanted' and let the scheduler pick it up — or trigger an immediate search from the UI.
CREATE TABLE download_queue ( id INTEGER PRIMARY KEY AUTOINCREMENT, track_id INTEGER NOT NULL REFERENCES tracks(id) ON DELETE CASCADE, slskd_search_id TEXT, status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'searching', 'downloading', 'organizing', 'complete', 'failed')), attempts INTEGER NOT NULL DEFAULT 0, last_attempt TEXT, error TEXT, created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')), -- Added by later migrations: next_retry_at TEXT, source TEXT NOT NULL DEFAULT 'user', last_progress_bytes INTEGER NOT NULL DEFAULT 0);
Column
Type
Notes
id
INTEGER PK
Auto-increment
track_id
INTEGER FK
References tracks(id); cascades on delete
slskd_search_id
TEXT
slskd search UUID, or username|transferID during an active download
A flat key/value store for all runtime configuration. Values from this table override environment-variable defaults and are set through the Settings UI.
Column
Type
Notes
key
TEXT PK
Setting identifier
value
TEXT NOT NULL
Setting value
Known keys:
Key
Description
provider_primary
Active provider for search (e.g. musicbrainz)
slskd_url
slskd API base URL
slskd_api_key
slskd API key
library_path
Absolute path to the organized library directory
download_format_preference
Preferred format string
min_bitrate
Minimum acceptable bitrate in kbps
scan_interval
How often to auto-queue wanted tracks (e.g. 6h)
cache_ttl_hours
Provider response cache TTL in hours (default 24)
quality_tiers
JSON array of quality tier objects
upgrade_last_artist_id
Internal cursor for the daily upgrade scanner
navidrome_url
Navidrome base URL for post-download library scans
Tracks files and users that should never be retried during automatic or manual searches.
CREATE TABLE slskd_blacklist ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT NOT NULL, filename TEXT NOT NULL, reason TEXT NOT NULL DEFAULT '', created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')));CREATE UNIQUE INDEX idx_blacklist_user_file ON slskd_blacklist(username, filename);
Column
Type
Notes
id
INTEGER PK
Auto-increment
username
TEXT NOT NULL
slskd username
filename
TEXT NOT NULL
Full file path on the remote user’s machine
reason
TEXT NOT NULL
Why it was blacklisted (e.g. "transfer Errored"); empty string when unspecified
created_at
TEXT
RFC3339
A unique index on (username, filename) prevents duplicates. Blacklisted entries are skipped by the file-picker during both auto-downloads and manual searches, but remain visible (dimmed) in the manual search results.
Temporarily blocks users who triggered a shadow ban (e.g. went offline mid-transfer or whose queued downloads stalled). Unlike the blacklist, entries expire automatically.
CREATE TABLE user_cooldowns ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT NOT NULL, reason TEXT NOT NULL DEFAULT '', expires_at TEXT NOT NULL, created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')));CREATE INDEX idx_cooldowns_username ON user_cooldowns(username);CREATE INDEX idx_cooldowns_expires ON user_cooldowns(expires_at);
Column
Type
Notes
id
INTEGER PK
Auto-increment
username
TEXT NOT NULL
slskd username under cooldown
reason
TEXT NOT NULL
Why the cooldown was applied; empty string when unspecified
expires_at
TEXT NOT NULL
RFC3339 timestamp; downloader skips this user until this time passes
CREATE INDEX idx_albums_artist_id ON albums(artist_id);CREATE INDEX idx_tracks_album_id ON tracks(album_id);CREATE INDEX idx_tracks_status ON tracks(status);CREATE INDEX idx_download_queue_status ON download_queue(status);CREATE INDEX idx_download_queue_track_id ON download_queue(track_id);-- Prevents two active download attempts for the same trackCREATE UNIQUE INDEX idx_download_queue_track_active ON download_queue(track_id) WHERE status IN ('pending', 'searching', 'downloading', 'organizing');-- Deduplication constraintsCREATE UNIQUE INDEX idx_artists_provider ON artists(provider, provider_id);CREATE UNIQUE INDEX idx_albums_provider ON albums(provider, provider_id);CREATE UNIQUE INDEX idx_tracks_provider ON tracks(provider, provider_id);
The unique partial index idx_download_queue_track_active is the duplicate-download guard: SQLite enforces that at most one row per track_id can exist in any active state at the same time, making it impossible to accidentally queue the same track twice.
If the built-in importer doesn’t fit your workflow, insert directly into artists, albums, and tracks. A few conventions keep your data compatible with the rest of Crate:
Provider naming for custom importers
Use local as the provider value for any entity that does not have a real provider ID. local is a reserved name — it is always treated as healthy (no orphaned indicator, no gRPC calls), and entities using it can be relinked to a real provider at any time from the UI or via the Relink API.If you do have real provider IDs (e.g. you are importing from a MusicBrainz export), use musicbrainz or deezer as the provider value and supply the actual IDs. Those entities will behave identically to ones discovered through search.For the local provider, use any unique string as the provider_id. A hash of artist + album + title (lowercase, trimmed) works well — it survives file moves and is stable across re-runs.
The activity log lives in a separate SQLite file (activity.db by default; configurable via CRATE_ACTIVITY_PATH). It contains a single table:
CREATE TABLE activity_log ( id INTEGER PRIMARY KEY AUTOINCREMENT, action TEXT, entity_type TEXT, entity_id INTEGER, details TEXT, created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')));
Column
Description
action
Event type, e.g. download_complete, library_import, track_bad
entity_type
The kind of entity involved: track, album, artist, library
entity_id
The primary key of the entity in crate.db
details
Human-readable summary string
created_at
RFC3339 timestamp
The activity log is completely independent of crate.db. You can delete it, truncate activity_log, or point CRATE_ACTIVITY_PATH at a new file at any time — the main database is unaffected. Retention is configured via the activity_retention_days settings key.