How to Create a Custom Music Provider for Music Assistant

Music Assistant loads custom music providers as Python modules under music_assistant/providers/, requiring only a manifest.json for metadata and a provider.py class inheriting from MusicProviderBase that implements async methods like search(), get_item(), and stream_url().

Creating a custom music provider for Music Assistant allows you to integrate any third-party music service into your home media ecosystem. The Music Assistant server architecture uses a plugin-based system where providers live as self-contained Python packages under the music_assistant/providers/ directory. By following the established contract defined in the core models, you can expose new music sources to the UI with minimal boilerplate.

Provider Architecture and Core Components

Every custom music provider follows a strict file structure and implements a specific interface defined in the core codebase.

Required File Structure

Place your provider in a new folder under music_assistant/providers/<your_provider_name>/. The directory must contain:

  • manifest.json – Declares metadata including the provider ID, display name, configuration schema, and required dependencies.
  • provider.py – Contains the concrete class implementing the provider logic.
  • __init__.py – Makes the directory a Python package; typically re-exports the provider class.

Optional assets like icon.svg or helper modules (e.g., constants.py) can reside in the same folder.

The Base Class Contract

The abstract base class that defines the provider contract lives in music_assistant/models/provider.py. Every custom provider must inherit from MusicProviderBase and implement the following async methods:

  • async search(self, query: str, limit: int) → List[MediaItem] – Returns search results matching the query string.
  • async get_item(self, item_id: str) → MediaItem – Retrieves a specific track or album by its unique identifier.
  • async get_playlist(self, playlist_id: str) → Playlist – Fetches playlist metadata and contents.
  • async stream_url(self, item: MediaItem) → str – Returns a direct URL that Music Assistant can pass to the player for audio streaming.
  • async get_image(self, item: MediaItem) → bytes | None – Retrieves thumbnail or cover art bytes.

Step-by-Step Implementation Guide

Follow these steps to create a custom music provider from scratch.

Step 1: Copy the Demo Template

The repository includes a minimal, fully-functional example at music_assistant/providers/_demo_player_provider/. Copy this folder and rename it to your provider identifier (e.g., my_custom_music). This template contains working stubs for all required methods, a sample manifest.json, and an icon file.

Step 2: Configure the manifest.json

Edit the manifest.json to define your provider's metadata and configuration schema:

{
    "type": "music",
    "id": "my_custom_music",
    "name": "My Custom Music",
    "description": "A custom music source built on XYZ API.",
    "config_schema": {
        "api_key": {"type": "string", "title": "API Key", "required": true}
    },
    "dependencies": ["httpx"]
}

The config_schema uses the Home Assistant config flow format, defining the settings users will see in the UI. The dependencies array lists PyPI packages required for your provider.

Step 3: Implement the Provider Class

Open provider.py and replace the demo logic. Your class must inherit from MusicProviderBase and implement the required async methods:

from music_assistant.models.provider import MusicProviderBase, MediaItem, Playlist

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

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

    async def get_item(self, item_id: str) -> MediaItem:
        resp = await self.client.get(f"/tracks/{item_id}", params={"key": self.api_key})
        resp.raise_for_status()
        data = resp.json()
        return MediaItem(
            item_id=data["id"],
            name=data["title"],
            artist=data["artist"],
            duration=data["duration"]
        )

    async def stream_url(self, item: MediaItem) -> str:
        return f"https://stream.example.com/{item.item_id}?key={self.api_key}"

The async_initialize() method is the entry point for setup, called before other methods. Store the configuration via self.config and initialize any persistent clients here.

Step 4: Testing and Registration

No manual registration code is required. The loading routine in music_assistant/providers/__init__.py automatically discovers providers by scanning for manifest.json files on server startup.

Test your implementation locally:

scripts/setup.sh               # Install dependencies if not already present

python -m music_assistant --log-level debug

The UI will now display your provider under Music Sources. Configure the API key through the settings interface and verify that search, browsing, and streaming function correctly.

Step 5: Write Unit Tests

Add tests under tests/ that mock external API calls. Use respx or pytest-asyncio to isolate your provider logic:

import pytest
import respx
from music_assistant.providers.my_custom_music.provider import MyCustomMusicProvider

@pytest.mark.asyncio
async def test_search():
    async with respx.mock:
        respx.get("/search").mock(
            return_value=respx.Response(200, json={"tracks": [{"id": "1", "title": "Test"}]})
        )
        prov = MyCustomMusicProvider(...)
        results = await prov.search("test")
        assert len(results) == 1

Required Async Methods Deep Dive

To create a fully functional custom music provider for Music Assistant, implement these core methods:

  • async_initialize() – Sets up persistent resources like HTTP sessions or database connections. This runs once when the provider loads.
  • search() – Parses the query string, calls your external API, and maps results to MediaItem objects.
  • get_item() – Retrieves single item metadata. Critical for playback queue resolution.
  • stream_url() – Must return a direct, playable URL or a local path that the player can consume. This is called when the user hits play.
  • get_playlist() – Required only if your service supports playlists; return a Playlist object containing MediaItem references.

Summary

  • Music Assistant discovers providers automatically by scanning music_assistant/providers/ for valid manifest.json files.
  • Every provider must inherit from MusicProviderBase in music_assistant/models/provider.py and implement the async API contract.
  • The demo template at _demo_player_provider/ provides a complete starting point for new providers.
  • Configuration schemas in manifest.json define the UI settings using the Home Assistant config flow format.
  • Testing requires mocking external APIs using tools like respx to ensure your provider handles network failures gracefully.

Frequently Asked Questions

What is the minimum viable implementation for a custom music provider?

At minimum, you must provide a manifest.json with type, id, and name fields, and a provider.py containing a class inheriting from MusicProviderBase that implements async_initialize(), search(), get_item(), and stream_url(). While get_playlist() and get_image() are recommended for full functionality, they are optional if your source does not support playlists or artwork.

How does Music Assistant discover new providers?

The framework uses the loading routine in music_assistant/providers/__init__.py to scan the providers/ directory on startup. It imports any folder containing a manifest.json and registers the class specified in the manifest. No additional registration code is required beyond placing the files in the correct location.

Can I add external dependencies to my custom provider?

Yes. List any PyPI packages in the dependencies array within your manifest.json. Music Assistant will attempt to install these dependencies when the provider loads. For development, you can also add them to the project's pyproject.toml and run scripts/setup.sh to ensure they are available in your environment.

How do I handle authentication in my provider?

Store sensitive credentials like API keys in the config_schema of your manifest.json. These values become available at runtime via self.config.get("key_name") inside your provider class. Initialize the authentication client in async_initialize(), and implement token refresh logic within your helper methods if your API uses expiring tokens.

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 →