# How to Create a Custom Music Provider for Music Assistant

> Learn to create a custom music provider for Music Assistant by developing Python modules implementing essential async methods. Integrate your unique music sources seamlessly.

- Repository: [Music Assistant/server](https://github.com/music-assistant/server)
- Tags: how-to-guide
- Published: 2026-06-21

---

**Music Assistant loads custom music providers as Python modules under `music_assistant/providers/`, requiring only a [`manifest.json`](https://github.com/music-assistant/server/blob/main/manifest.json) for metadata and a [`provider.py`](https://github.com/music-assistant/server/blob/main/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`](https://github.com/music-assistant/server/blob/main/manifest.json)** – Declares metadata including the provider ID, display name, configuration schema, and required dependencies.
- **[`provider.py`](https://github.com/music-assistant/server/blob/main/provider.py)** – Contains the concrete class implementing the provider logic.
- **[`__init__.py`](https://github.com/music-assistant/server/blob/main/__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`](https://github.com/music-assistant/server/blob/main/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`](https://github.com/music-assistant/server/blob/main/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`](https://github.com/music-assistant/server/blob/main/manifest.json), and an icon file.

### Step 2: Configure the manifest.json

Edit the [`manifest.json`](https://github.com/music-assistant/server/blob/main/manifest.json) to define your provider's metadata and configuration schema:

```json
{
    "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`](https://github.com/music-assistant/server/blob/main/provider.py) and replace the demo logic. Your class must inherit from `MusicProviderBase` and implement the required async methods:

```python
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`](https://github.com/music-assistant/server/blob/main/music_assistant/providers/__init__.py)** automatically discovers providers by scanning for [`manifest.json`](https://github.com/music-assistant/server/blob/main/manifest.json) files on server startup.

Test your implementation locally:

```bash
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:

```python
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`](https://github.com/music-assistant/server/blob/main/manifest.json) files.
- **Every provider must inherit from `MusicProviderBase`** in [`music_assistant/models/provider.py`](https://github.com/music-assistant/server/blob/main/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`](https://github.com/music-assistant/server/blob/main/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`](https://github.com/music-assistant/server/blob/main/manifest.json) with `type`, `id`, and `name` fields, and a [`provider.py`](https://github.com/music-assistant/server/blob/main/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`](https://github.com/music-assistant/server/blob/main/music_assistant/providers/__init__.py) to scan the `providers/` directory on startup. It imports any folder containing a [`manifest.json`](https://github.com/music-assistant/server/blob/main/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`](https://github.com/music-assistant/server/blob/main/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`](https://github.com/music-assistant/server/blob/main/pyproject.toml) and run [`scripts/setup.sh`](https://github.com/music-assistant/server/blob/main/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`](https://github.com/music-assistant/server/blob/main/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.