Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/TheOutdoorProgrammer/crate/llms.txt

Use this file to discover all available pages before exploring further.

Crate separates music metadata from the core application through a gRPC plugin system. Every metadata source — MusicBrainz, Deezer, or anything you build — is a standalone process that implements the MusicProvider gRPC service. Crate either starts these processes itself and manages their lifecycle, or connects to ones already running on the network. From Crate’s perspective every provider is equal: search results, artist pages, discographies, and the upgrade scanner all work identically regardless of which provider backs them.
The MusicProvider service is defined in proto/provider/provider.proto using proto3. Any language with a gRPC code generator — Go, Python, TypeScript, Rust, Java, and more — can implement it.

The gRPC Contract

Your provider implements the MusicProvider service:
service MusicProvider {
  rpc Info(InfoRequest) returns (InfoResponse);
  rpc SearchArtists(SearchRequest) returns (ArtistSearchResponse);
  rpc GetArtist(EntityRequest) returns (ArtistDetail);
  rpc GetArtistAlbums(EntityRequest) returns (AlbumList);
  rpc GetAlbum(EntityRequest) returns (AlbumDetail);
  rpc SearchArtistTracks(ArtistTrackSearchRequest) returns (ArtistTrackSearchResponse);
}

RPC Reference

RPCPurpose
InfoReturn provider name, display name, and version. Called on registration and on every health check.
SearchArtistsSearch for artists by name. Supports limit and offset for pagination. Return total for the full result count.
GetArtistReturn artist details by provider-specific ID.
GetArtistAlbumsList all albums and releases for an artist.
GetAlbumReturn album details including the full track listing.
SearchArtistTracksSearch tracks within a specific artist’s catalog by query string.

Key Fields

IDs are plain strings. Use whatever your source API returns — MBIDs, Deezer int64s cast to strings, Spotify URIs, anything. Crate stores them opaquely and only uses them for cache keys, deduplication, and live lookups. rank controls display order on search results, album lists, and track lists. Higher rank means shown first. MusicBrainz uses search relevance score; Deezer uses fan count. Any integer works. metadata is a map<string, string> field present on every entity message (ArtistResult, ArtistDetail, AlbumSummary, AlbumDetail, TrackInfo). Put anything useful here — country, disambiguation, genre, fan count, release date — and the frontend renders each entry as an info tag. No schema registration needed. record_type on AlbumSummary should be one of album, single, ep, or compilation. The frontend displays this as a badge next to the album title.
The metadata map is the safest place to expose source-specific fields. Adding a new key never requires a Crate update or database migration — the frontend renders whatever keys you return.

Running Your Provider

Crate starts the binary for you and passes the port via environment variable. Set CRATE_PROVIDERS to a comma-separated list of name:binary:port entries:
CRATE_PROVIDERS=musicbrainz:./provider-musicbrainz:50051,deezer:./provider-deezer:50052,myprovider:./my-provider:50053
On startup Crate:
  1. Launches each binary with PORT=<port> in its environment.
  2. Waits up to 10 seconds for the gRPC server to respond to Info().
  3. Registers the provider using the name returned by Info().
The binary must listen on 0.0.0.0:<PORT> (or localhost:<PORT>) and be ready to serve before the timeout expires.

Minimal Go Implementation

The example below is a working, compilable provider skeleton. Embed your API calls inside SearchArtists, GetArtist, GetArtistAlbums, GetAlbum, and SearchArtistTracks.
package main

import (
    "context"
    "net"
    "os"

    "google.golang.org/grpc"
    pb "github.com/TheOutdoorProgrammer/crate/proto/provider"
)

type server struct {
    pb.UnimplementedMusicProviderServer
}

func (s *server) Info(ctx context.Context, req *pb.InfoRequest) (*pb.InfoResponse, error) {
    return &pb.InfoResponse{
        Name:        "myprovider",
        DisplayName: "My Provider",
        Version:     "0.1.0",
    }, nil
}

func (s *server) SearchArtists(ctx context.Context, req *pb.SearchRequest) (*pb.ArtistSearchResponse, error) {
    // Call your API here, map results to pb.ArtistResult
    return &pb.ArtistSearchResponse{
        Artists: []*pb.ArtistResult{
            {Id: "123", Name: "Example Artist", Rank: 100},
        },
        Total: 1,
    }, nil
}

// Implement GetArtist, GetArtistAlbums, GetAlbum, SearchArtistTracks similarly...

func main() {
    port := os.Getenv("PORT")
    if port == "" {
        port = "50053"
    }
    lis, _ := net.Listen("tcp", ":"+port)
    s := grpc.NewServer()
    pb.RegisterMusicProviderServer(s, &server{})
    s.Serve(lis)
}

Caching and Rate Limiting

Response caching

Crate caches every provider response in a separate SQLite database (cache.db). Cache keys are namespaced by provider name — myprovider:artist:123 — so providers never collide. Users can clear the cache and adjust the TTL (default 24 hours) from the Settings page.

Rate limiting

If your upstream API has rate limits, enforce them inside your provider process. Crate’s cache layer significantly reduces repeat calls during normal use, but your provider should still apply its own token-bucket or leaky-bucket limiter for burst protection. See golang.org/x/time/rate for a lightweight Go implementation.

Health Checks and Recovery

Crate periodically calls Info() on every registered provider with a 2-second timeout. If the call fails, all artist and album entities linked to that provider show an orphaned indicator in the UI. No data is lost — the rows remain in the database. When the provider comes back online and Info() succeeds again, the orphaned indicator clears automatically. Users can also manually relink any artist to a different provider (for example, switching from a failing custom provider to musicbrainz) without losing any library data.

Provider Configuration Reference

Each entry in the comma-separated list follows one of two formats:
ModeFormatExample
Managed processname:binary_path:portmyprovider:./my-provider:50053
External processname:external:host:portmyprovider:external:10.0.0.5:50053
name must be unique and must not be local (reserved for the library importer).
Info() is called both at startup (registration) and repeatedly as a health check. Keep the implementation fast and stateless.
FieldTypeNotes
namestringMachine-readable identifier; should match the name in CRATE_PROVIDERS
display_namestringHuman-readable label shown in the UI provider selector
versionstringSemver or any string; shown in the Settings providers list
SearchArtists receives limit and offset integers. Return the full total count (not just the current page size) so the UI can render correct pagination controls. If your API does not support offset pagination, return total equal to len(artists) in each response.

Build docs developers (and LLMs) love