How to Create a Custom Music Provider Plugin for Music Assistant: A Complete Guide
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, packaging it with a 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 |
Declares metadata, configuration schema, and dependencies | music_assistant/providers/<your_provider>/manifest.json |
provider.py |
Contains the provider class implementing the async API | music_assistant/providers/<your_provider>/provider.py |
__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 can reside in the same folder.
The Base Provider Class
All providers must inherit from MusicProviderBase, defined in 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 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.
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, 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 to define your provider's metadata and configuration schema. The format follows Home Assistant's config flow specification:
{
"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 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:
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, the loader in music_assistant/providers/__init__.py automatically discovers and registers your provider on the next server start. Run the server in debug mode to verify:
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, 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 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:
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:
pre-commit run --all-files
Summary
- Music Assistant discovers custom music providers by scanning
music_assistant/providers/formanifest.jsonfiles and loading the corresponding Python modules. - Required files:
manifest.json(metadata and config),provider.py(implementation), and__init__.py(package marker). - Base class: Inherit from
MusicProviderBaseinmusic_assistant/models/provider.pyand implementasync_initialize,search,get_item, andstream_urlat minimum. - Template: Use
music_assistant/providers/_demo_player_provider/as your starting point to avoid boilerplate setup. - Testing: Mock external APIs using
respxorpytest-asyncioto 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 with type, id, and name fields, plus a 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 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 or parser.py) in the same directory as your 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.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →