How to Add Music Providers to Music Assistant: A Developer's Guide

Adding a music provider to Music Assistant requires creating a Python module that subclasses MusicProvider from music_assistant/models/music_provider.py and implementing async methods for search, library synchronization, and streaming, alongside a manifest.json configuration file.

Music Assistant is an open-source media server that aggregates music from multiple sources into a unified library. To add music providers to Music Assistant, developers implement a plugin architecture that bridges external catalogs with the core system using standardized async interfaces. This guide walks through the exact file structure, required methods, and manifest configuration needed to integrate a new streaming service or local source.

Understand the MusicProvider Base Class

All music providers inherit from the MusicProvider base class defined in music_assistant/models/music_provider.py. This abstract class establishes the contract between external music sources and Music Assistant's central library, handling authentication, capability declaration, and data transformation.

The base class provides default implementations for browsing and library management, but requires you to override specific methods depending on your provider's capabilities. You interact with the core system through self.mass, which provides access to the database, cache, and task runner without requiring direct database manipulation.

Required Methods and Properties

Implement these key methods to enable full functionality:

  • setup() – Called once during provider instantiation. Handle authentication, token refresh, and initial connection here.
  • supported_features – Property returning a list of ProviderFeature enums (e.g., ProviderFeature.SEARCH, ProviderFeature.LIBRARY_TRACKS, ProviderFeature.STREAM).
  • search(search_query, media_types, limit) – Return a SearchResults object containing matching artists, albums, tracks, or playlists.
  • get_library_artists(), get_library_albums(), get_library_tracks(), get_library_playlists() – Async generators yielding respective media items from the user's library.
  • get_artist(), get_album(), get_track(), get_playlist() – Fetch full metadata for a specific item by provider ID.
  • get_stream_details(item_id, media_type) – Return StreamDetails including protocol, URL, and audio format.
  • get_audio_stream(streamdetails, seek_position) – Async generator yielding audio bytes for playback.

Optional write-back methods include library_add(), library_remove(), and set_favorite() if your provider supports modifying the external catalog.

Create the Provider Manifest

Every provider requires a manifest.json file located at music_assistant/providers/<your_provider>/manifest.json. This file declares metadata, configuration requirements, and Python dependencies.

Key fields include:

  • type – Must be "music" for music providers.
  • domain – Unique identifier (e.g., "spotify").
  • name – Human-readable display name.
  • config_entries – Array of configuration items defining key, type (string, secure_string, boolean), label, and defaults. Access values via self.config.get_value(key).
  • requirements – Pip-style dependencies (e.g., ["spotipy==2.23.0"]) installed automatically by the setup script.
  • multi_instances – Boolean indicating whether users can configure multiple accounts.
  • documentation – URL to usage instructions.

Implement the Provider Step-by-Step

1. Set Up the Provider Directory

Create the provider folder structure:

mkdir -p music_assistant/providers/my_provider
touch music_assistant/providers/my_provider/__init__.py
touch music_assistant/providers/my_provider/manifest.json

2. Implement the Core Class

Subclass MusicProvider and declare supported features:


# music_assistant/providers/my_provider/__init__.py

from __future__ import annotations

from music_assistant.models.music_provider import MusicProvider
from music_assistant.models.provider import ProviderFeature
from music_assistant.helpers.util import async_log_exception

class MyProvider(MusicProvider):
    """Custom music provider implementation."""

    @property
    def supported_features(self) -> list[ProviderFeature]:
        """Declare capabilities."""
        return [
            ProviderFeature.SEARCH,
            ProviderFeature.LIBRARY_ARTISTS,
            ProviderFeature.LIBRARY_ALBUMS,
            ProviderFeature.LIBRARY_TRACKS,
            ProviderFeature.STREAM,
        ]

    async def setup(self) -> None:
        """Initialize connection and authenticate."""
        self._api_key = self.config.get_value("api_key")
        # Validate credentials here

3. Handle Search and Library Sync

Implement search and library enumeration methods:

    @async_log_exception
    async def search(self, search_query: str, media_types: list[MediaType], limit: int = 5):
        """Search the external catalog."""
        # Call external API

        results = await self._api.search(query=search_query, limit=limit)
        # Transform to SearchResults model

        return SearchResults(...)

    @async_log_exception
    async def get_library_artists(self):
        """Yield artists from user's library."""
        async for raw in self._api.library_artists():
            yield Artist(
                item_id=raw["id"],
                provider=self.instance_id,
                name=raw["name"],
                # Populate additional metadata

            )

Implement get_library_albums(), get_library_tracks(), and get_library_playlists() similarly using async generators.

4. Enable Audio Streaming

For playback support, implement the streaming interface:

    async def get_stream_details(self, item_id: str, media_type: MediaType) -> StreamDetails:
        """Return stream metadata."""
        return StreamDetails(
            protocol=StreamType.CUSTOM,
            url=self._api.get_stream_url(item_id),
            audio_format=AudioFormat(
                content_type=ContentType.MP3,
                sample_rate=44100,
                bit_depth=16,
                channels=2,
            ),
        )

    async def get_audio_stream(self, streamdetails: StreamDetails, seek_position: int = 0):
        """Stream audio bytes."""
        import aiohttp
        async with aiohttp.ClientSession() as session:
            headers = {"Range": f"bytes={seek_position}-"} if seek_position else {}
            async with session.get(streamdetails.url, headers=headers) as resp:
                async for chunk in resp.content.iter_any():
                    yield chunk

Code Examples

Minimal manifest.json

{
  "type": "music",
  "domain": "myprovider",
  "name": "MyProvider",
  "description": "Integration with MyProvider music catalog.",
  "codeowners": ["@yourusername"],
  "config_entries": [
    {
      "key": "api_key",
      "type": "secure_string",
      "label": "API Key",
      "default": ""
    }
  ],
  "requirements": ["aiohttp==3.9.0"],
  "documentation": "https://github.com/music-assistant/server/discussions/123",
  "multi_instances": false
}

Provider Implementation Skeleton

from music_assistant.models.music_provider import MusicProvider
from music_assistant.models.provider import ProviderFeature
from music_assistant.models.media_items import Artist, Album, Track, SearchResults
from music_assistant.helpers.util import async_log_exception
import aiohttp

class MyProvider(MusicProvider):
    @property
    def supported_features(self):
        return [
            ProviderFeature.SEARCH,
            ProviderFeature.LIBRARY_ARTISTS,
            ProviderFeature.LIBRARY_ALBUMS,
            ProviderFeature.LIBRARY_TRACKS,
            ProviderFeature.STREAM,
        ]

    async def setup(self):
        self._session = aiohttp.ClientSession()
        self._api_key = self.config.get_value("api_key")

    @async_log_exception
    async def search(self, search_query, media_types, limit=5):
        params = {"q": search_query, "limit": limit, "key": self._api_key}
        async with self._session.get("https://api.example.com/search", params=params) as resp:
            data = await resp.json()
        return self._parse_search_results(data)

    @async_log_exception
    async def get_library_artists(self):
        async with self._session.get(f"https://api.example.com/artists?key={self._api_key}") as resp:
            data = await resp.json()
            for item in data["artists"]:
                yield Artist(
                    item_id=item["id"],
                    provider=self.instance_id,
                    name=item["name"],
                )

Register and Test the Provider

Music Assistant automatically discovers providers by scanning the music_assistant/providers/ directory at startup. No manual registration code is required.

Install dependencies and register the provider:

scripts/setup.sh

Start the server locally and enable your provider through the web interface. Verify functionality by testing search queries, library synchronization, and audio playback. Add unit tests under tests/providers/my_provider/test_provider.py following existing patterns in the repository.

Summary

  • Inherit from MusicProvider in music_assistant/models/music_provider.py to create a new provider class.
  • Declare capabilities via the supported_features property using ProviderFeature enums.
  • Implement async methods for setup(), search(), library enumeration (get_library_*), item lookup (get_*), and streaming (get_stream_details(), get_audio_stream()).
  • Define metadata in manifest.json including domain, config_entries, and requirements.
  • Place files in music_assistant/providers/<your_provider>/ with __init__.py and manifest.json.
  • Run scripts/setup.sh to install dependencies and auto-register the provider.

Frequently Asked Questions

What is the MusicProvider base class in Music Assistant?

The MusicProvider base class is an abstract Python class defined in music_assistant/models/music_provider.py that standardizes how external music catalogs integrate with the server. It defines async methods for authentication, searching, library synchronization, metadata retrieval, and audio streaming that subclasses must implement to enable full functionality within the Music Assistant ecosystem.

How do I configure authentication for my music provider?

Implement the setup() method to handle authentication during provider initialization. Store API keys or tokens by retrieving them from self.config.get_value(key) where the key matches entries defined in your manifest.json config_entries array. Use secure_string type for sensitive credentials to ensure they are masked in the UI and logs.

Can a music provider support multiple instances?

Yes, set "multi_instances": true in your manifest.json to allow users to configure multiple accounts or endpoints for the same provider type. When enabled, Music Assistant instantiates separate provider objects for each configuration, each with unique instance_id values while sharing the same implementation code.

Where should I place unit tests for my new provider?

Create test files under tests/providers/<your_provider>/test_provider.py following the existing test patterns in the repository. Tests should mock external API calls and verify that your provider correctly implements MusicProvider methods, handles authentication errors gracefully, and returns properly formatted media items that match the expected Music Assistant models.

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 →