How to Create a Custom Music Provider for Music Assistant: A Complete Developer's Guide

To create a custom music provider for Music Assistant, you must implement a Python class inheriting from MusicProviderBase located in music_assistant/models/provider.py, define a manifest.json configuration file, and place both files in a new subdirectory under music_assistant/providers/.

Music Assistant, the open-source media server from the music-assistant/server repository, exposes a modular plugin architecture that enables developers to integrate any external music API or local source capable of providing metadata and streamable URLs. Creating a custom music provider requires adhering to an asynchronous contract defined by the core framework, which automatically discovers and loads providers at runtime by scanning for valid manifest files.

Understanding the Provider Contract

Music Assistant loads music providers as Python packages from the music_assistant/providers/ directory. Each provider must contain three essential components that define its interface and metadata.

manifest.json declares the provider's identity, configuration schema, and dependencies. This file sits at the root of your provider folder and uses the same schema format as Home Assistant configuration flows.

provider.py contains the concrete implementation class that inherits from MusicProviderBase. This abstract base class, defined in music_assistant/models/provider.py, specifies the asynchronous methods every provider must implement to support search, retrieval, and streaming operations.

__init__.py makes the directory a valid Python package and typically re-exports the provider class for the framework's import system.

The framework discovers providers by scanning music_assistant/providers/ for manifest.json files. The loading routine in music_assistant/providers/__init__.py then imports the module and registers the class automatically.

Step-by-Step Implementation Guide

Step 1: Copy the Demo Template

The repository includes a minimal, functional reference implementation at music_assistant/providers/_demo_player_provider/. This folder contains working examples of manifest.json, provider.py, __init__.py, and an icon asset.

Copy this folder to create your provider structure:

cp -r music_assistant/providers/_demo_player_provider music_assistant/providers/my_custom_provider

Rename the folder to match your provider's identifier, which becomes the unique key used in URLs and configuration.

Step 2: Configure the Provider Manifest

Edit manifest.json to declare your provider's metadata and configuration requirements. The config_schema field defines settings that appear in the Music Assistant UI, such as API keys or base URLs.

{
    "type": "music",
    "id": "my_custom_provider",
    "name": "My Custom Music",
    "description": "Integration with custom music API",
    "config_schema": {
        "api_key": {
            "type": "string",
            "title": "API Key",
            "required": true
        },
        "base_url": {
            "type": "string",
            "title": "API Base URL",
            "default": "https://api.example.com"
        }
    },
    "dependencies": ["httpx"]
}

The type field must be "music" for music providers, and dependencies lists PyPI packages required for your implementation.

Step 3: Implement the Provider Logic

Open provider.py and replace the demo class with your implementation. Your class must inherit from MusicProviderBase and implement the core async methods required by the framework.

from music_assistant.models.provider import MusicProviderBase, MediaItem

class MyCustomProvider(MusicProviderBase):
    async def async_initialize(self) -> None:
        """Perform async setup, such as creating HTTP clients."""
        self.client = httpx.AsyncClient(
            base_url=self.config.get("base_url", "https://api.example.com")
        )
        self.api_key = self.config["api_key"]

    async def search(self, query: str, limit: int = 10) -> list[MediaItem]:
        """Return search results matching the query string."""
        resp = await self.client.get(
            "/search",
            params={"q": query, "limit": limit, "key": self.api_key}
        )
        resp.raise_for_status()
        data = resp.json()
        
        return [
            MediaItem(
                item_id=track["id"],
                name=track["title"],
                artist=track["artist"],
                album=track["album"],
                duration=track["duration"],
                thumbnail=track.get("cover_url")
            )
            for track in data["tracks"]
        ]

    async def get_item(self, item_id: str) -> MediaItem:
        """Retrieve a specific track by its provider ID."""
        resp = await self.client.get(
            f"/tracks/{item_id}",
            params={"key": self.api_key}
        )
        resp.raise_for_status()
        track = resp.json()
        
        return MediaItem(
            item_id=track["id"],
            name=track["title"],
            artist=track["artist"],
            album=track["album"],
            duration=track["duration"],
            thumbnail=track.get("cover_url")
        )

    async def stream_url(self, item: MediaItem) -> str:
        """Return a direct URL that Music Assistant can stream to players."""
        return f"{self.config.get('base_url')}/stream/{item.item_id}?token={self.api_key}"

The MusicProviderBase also defines optional methods such as async get_playlist(self, playlist_id: str) and async get_image(self, item: MediaItem) that you can implement to support playlist browsing and artwork retrieval.

Step 4: Add Helper Modules

If your integration requires utility functions for authentication, pagination, or data transformation, place these modules in the same provider directory. Import them from provider.py to keep your main class focused on the framework contract.

Step 5: Automatic Registration and Testing

No explicit registration code is required. Once your manifest.json is valid and the files are in music_assistant/providers/<your_provider>/, the loader in music_assistant/providers/__init__.py will discover the provider on the next server start.

Test your implementation by running the server in debug mode:

python -m music_assistant --log-level debug

Your provider will appear under Music Sources in the UI. Configure the API key and verify that search, browsing, and streaming function correctly.

Core Methods Reference

The MusicProviderBase abstract class in music_assistant/models/provider.py defines the following critical methods:

  • async_initialize – Called once during provider startup. Use this to initialize HTTP clients, validate credentials, and set up connection pools.
  • search(query: str, limit: int) – Must return a list of MediaItem objects matching the search query. This powers the global search functionality.
  • get_item(item_id: str) – Retrieves full metadata for a specific track ID. Essential for resolving items from URLs or library references.
  • stream_url(item: MediaItem) – Returns a string URL that the player can directly access to retrieve audio bytes. This can be a direct file URL or a time-limited signed URL from your API.
  • get_playlist(playlist_id: str) – Optional. Returns a Playlist object containing tracks if your source supports playlist entities.
  • get_image(item: MediaItem) – Optional. Returns image bytes for artwork, or None if unavailable.

Summary

Frequently Asked Questions

What is the minimum code required to implement a custom music provider?

You must create a class inheriting from MusicProviderBase that implements at least async_initialize, search, get_item, and stream_url. Additionally, you need a valid manifest.json file with type, id, name, and config_schema fields. Place these in a folder under music_assistant/providers/ and the framework will load it automatically.

Can I use external HTTP libraries like httpx or aiohttp in my provider?

Yes. List any required PyPI packages in the dependencies array of your manifest.json. Music Assistant uses httpx internally, but you can import aiohttp or any other async-compatible library by declaring it as a dependency and importing it normally in your provider.py.

How does Music Assistant discover and load custom providers?

The framework scans the music_assistant/providers/ directory for subdirectories containing manifest.json files. The loader routine in music_assistant/providers/__init__.py reads each manifest, imports the module specified, and instantiates the provider class during server startup. No manual registration or import statements are required in the core codebase.

Where should I write unit tests for my custom music provider?

Add your test files under the tests/ directory at the repository root. Use pytest-asyncio for async test support and libraries like respx to mock external HTTP APIs. Import your provider class from music_assistant.providers.your_provider_name.provider and test each method independently, ensuring proper error handling for API failures.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →