# How to Create a Custom Music Provider Plugin for Music Assistant: A Complete Guide

> Create a custom music provider plugin for Music Assistant by inheriting from MusicProviderBase and adding a manifest.json. Discover how to integrate your unique music sources easily.

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

---

**You can create a custom music provider plugin for Music Assistant by implementing a Python class that inherits from `MusicProviderBase` in [`music_assistant/models/provider.py`](https://github.com/music-assistant/server/blob/main/music_assistant/models/provider.py), packaging it with a [`manifest.json`](https://github.com/music-assistant/server/blob/main/manifest.json) metadata file, and placing it in the `music_assistant/providers/` directory where the framework automatically discovers it on startup.**

Music Assistant is an open-source music server that aggregates libraries from multiple sources into a unified interface. According to the `music-assistant/server` repository, the provider system uses a plug-in architecture that lets you add support for any external music API by following a well-defined contract. This guide walks you through the exact file structure, required methods, and implementation patterns needed to build a fully functional provider.

## Understanding the Provider Architecture

The provider system in Music Assistant relies on a consistent directory structure and a minimal set of required files. Each provider lives as a Python package under `music_assistant/providers/<provider_name>/` and must expose specific entry points for the framework to consume.

### Core Components

Every custom music provider requires three essential files:

| File | Purpose | Location |
|------|---------|----------|
| **[`manifest.json`](https://github.com/music-assistant/server/blob/main/manifest.json)** | Declares metadata, configuration schema, and dependencies | `music_assistant/providers/<your_provider>/manifest.json` |
| **[`provider.py`](https://github.com/music-assistant/server/blob/main/provider.py)** | Contains the provider class implementing the async API | `music_assistant/providers/<your_provider>/provider.py` |
| **[`__init__.py`](https://github.com/music-assistant/server/blob/main/__init__.py)** | Makes the directory a Python package; typically re-exports the provider class | `music_assistant/providers/<your_provider>/__init__.py` |

Optional assets such as `icon.svg` or helper modules like [`constants.py`](https://github.com/music-assistant/server/blob/main/constants.py) can reside in the same folder.

### The Base Provider Class

All providers must inherit from **`MusicProviderBase`**, defined in [`music_assistant/models/provider.py`](https://github.com/music-assistant/server/blob/main/music_assistant/models/provider.py). This abstract class establishes the contract between your implementation and the Music Assistant core. The framework discovers providers by scanning `music_assistant/providers/` for valid [`manifest.json`](https://github.com/music-assistant/server/blob/main/manifest.json) files, then imports the module specified in the manifest to register the concrete class. This loading routine is handled in [`music_assistant/providers/__init__.py`](https://github.com/music-assistant/server/blob/main/music_assistant/providers/__init__.py).

## Step-by-Step Implementation Guide

### Step 1: Clone the Demo Template

The repository ships with a minimal, fully-functional example at **`music_assistant/providers/_demo_player_provider/`**. This folder contains a working [`manifest.json`](https://github.com/music-assistant/server/blob/main/manifest.json), a simple provider class, and stub implementations for all required methods. Copy this directory and rename it to your provider identifier (e.g., `my_custom_music`). The folder name becomes the provider ID used in URLs and configuration.

### Step 2: Configure the Manifest

Edit [`manifest.json`](https://github.com/music-assistant/server/blob/main/manifest.json) to define your provider's metadata and configuration schema. The format follows Home Assistant's config flow specification:

```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` defines the settings UI that users will see when adding your provider. The `dependencies` array lists Python packages required for your implementation.

### Step 3: Implement the Provider Class

Open [`provider.py`](https://github.com/music-assistant/server/blob/main/provider.py) and replace the demo logic with your API integration. Your class must inherit from `MusicProviderBase` and implement the required async methods. Here is a minimal implementation:

```python
from music_assistant.models.provider import MusicProviderBase, MediaItem

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]:
        """Search for tracks matching the query string."""
        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:
        """Retrieve a specific track by ID."""
        resp = await self.client.get(f"/tracks/{item_id}", params={"key": self.api_key})
        data = resp.json()
        return MediaItem(
            item_id=data["id"],
            name=data["title"],
            album=data["album"],
            artist=data["artist"],
            duration=data["duration"],
            thumbnail=data["image"]
        )

    async def stream_url(self, item: MediaItem) -> str:
        """Return a direct URL that Music Assistant can give to the player."""
        return f"https://stream.example.com/{item.item_id}?key={self.api_key}"

```

### Step 4: Handle Async Initialization

The **`async_initialize`** method is the lifecycle hook where you perform setup tasks such as authenticating with external APIs, creating HTTP clients, or loading cached data. This method runs once when the provider is instantiated, before any search or playback operations occur.

### Step 5: Register and Test

No manual registration is required. Once your folder contains a valid [`manifest.json`](https://github.com/music-assistant/server/blob/main/manifest.json), the loader in [`music_assistant/providers/__init__.py`](https://github.com/music-assistant/server/blob/main/music_assistant/providers/__init__.py) automatically discovers and registers your provider on the next server start. Run the server in debug mode to verify:

```bash
python -m music_assistant --log-level debug

```

Your provider will appear under **Music Sources** in the UI. Configure it with your API credentials and test search, browsing, and streaming functionality.

## Required Methods and API Implementation

According to [`music_assistant/models/provider.py`](https://github.com/music-assistant/server/blob/main/music_assistant/models/provider.py), a complete music provider implementation should support these 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 metadata for a specific track.
- **`async get_playlist(self, playlist_id: str) -> Playlist`**: Fetches playlist metadata and contents.
- **`async stream_url(self, item: MediaItem) -> str`**: Generates a playable URL for the given media item.
- **`async get_image(self, item: MediaItem) -> bytes | None`**: Returns binary image data for album art or thumbnails.

The demo provider at [`music_assistant/providers/_demo_player_provider/provider.py`](https://github.com/music-assistant/server/blob/main/music_assistant/providers/_demo_player_provider/provider.py) contains stubs for all these methods, which you can replace with your API-specific logic.

## Testing Your Custom Provider

Add unit tests under the `tests/` directory to ensure your provider handles API responses correctly. Use `respx` or similar tools to mock external HTTP calls:

```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 Song"}]})
        )
        prov = MyCustomMusicProvider(config={"api_key": "test"})
        await prov.async_initialize()
        results = await prov.search("test", limit=1)
        assert len(results) == 1
        assert results[0].name == "Test Song"

```

After confirming functionality, run the pre-commit hooks to ensure code quality:

```bash
pre-commit run --all-files

```

## Summary

- **Music Assistant** discovers custom music providers by scanning `music_assistant/providers/` for [`manifest.json`](https://github.com/music-assistant/server/blob/main/manifest.json) files and loading the corresponding Python modules.
- **Required files**: [`manifest.json`](https://github.com/music-assistant/server/blob/main/manifest.json) (metadata and config), [`provider.py`](https://github.com/music-assistant/server/blob/main/provider.py) (implementation), and [`__init__.py`](https://github.com/music-assistant/server/blob/main/__init__.py) (package marker).
- **Base class**: Inherit from `MusicProviderBase` in [`music_assistant/models/provider.py`](https://github.com/music-assistant/server/blob/main/music_assistant/models/provider.py) and implement `async_initialize`, `search`, `get_item`, and `stream_url` at minimum.
- **Template**: Use `music_assistant/providers/_demo_player_provider/` as your starting point to avoid boilerplate setup.
- **Testing**: Mock external APIs using `respx` or `pytest-asyncio` to validate your provider logic without network dependencies.

## Frequently Asked Questions

### What is the minimum viable implementation for a Music Assistant provider?

The minimum implementation requires a [`manifest.json`](https://github.com/music-assistant/server/blob/main/manifest.json) with `type`, `id`, and `name` fields, plus a [`provider.py`](https://github.com/music-assistant/server/blob/main/provider.py) containing a class that inherits from `MusicProviderBase` and implements `async_initialize`, `search`, and `stream_url`. The demo template at `music_assistant/providers/_demo_player_provider/` provides exactly this skeleton.

### Does Music Assistant support synchronous API calls in providers?

No, all provider methods must be asynchronous. The `MusicProviderBase` class in [`music_assistant/models/provider.py`](https://github.com/music-assistant/server/blob/main/music_assistant/models/provider.py) defines all required methods as `async def`, and the framework expects coroutines for operations like `search` and `get_item` to prevent blocking the event loop during network I/O.

### How do I handle authentication tokens that expire in my provider?

Implement token refresh logic within `async_initialize` or create a helper method that checks token validity before each API call. Store tokens as instance variables (e.g., `self._token`) and refresh them when detecting HTTP 401 responses from your external API.

### Where should I place helper modules for my custom provider?

Place any utility modules (such as [`constants.py`](https://github.com/music-assistant/server/blob/main/constants.py) or [`parser.py`](https://github.com/music-assistant/server/blob/main/parser.py)) in the same directory as your [`provider.py`](https://github.com/music-assistant/server/blob/main/provider.py) and import them directly. For example, if your provider is at `music_assistant/providers/my_service/`, you can import helpers using `from .constants import API_BASE_URL`.