How the Configuration System Is Stored and Managed in Music Assistant

Music Assistant stores all persistent settings in a single JSON file (settings.json) managed by the ConfigController class in music_assistant/controllers/config.py, which provides encrypted, hierarchical, and batched access to configuration values.

The music-assistant/server repository implements a centralized configuration architecture that balances simplicity with security. All settings persist to a single JSON file in the user's data directory, while the ConfigController class provides an async-compatible interface for reading, writing, and encrypting sensitive values. This design ensures that providers, players, and core controllers can store typed configuration data without direct filesystem access.

Configuration Storage Architecture

The settings.json File Structure

Music Assistant persists all configuration data to $HOME/.musicassistant/settings.json. This single file stores a nested dictionary representing the entire server state, including core settings, provider instances, player configurations, and DSP presets. The storage format uses hierarchical keys (e.g., providers/<instance_id>/values/<key>) that map to nested JSON objects, allowing the system to group related settings by domain while maintaining a flat file structure.

Hierarchical Key Organization

The configuration system organizes data through slash-delimited paths that mirror the dictionary structure. For example, accessing players/player123/enabled traverses the nested objects to retrieve the specific value. This hierarchy enables clean separation between different subsystems:

  • core/ - Global server settings
  • providers/<instance_id>/ - Music provider configurations
  • players/<player_id>/ - Player-specific settings
  • dsp/ - DSP preset definitions

The ConfigController Implementation

Core Access Methods

Located in music_assistant/controllers/config.py, the ConfigController class exposes four primary methods for configuration manipulation:

  • get(path, default=None) - Retrieves values using dot or slash notation
  • set(path, value) - Writes values and triggers delayed persistence
  • remove(path) - Deletes configuration entries and cleans up empty parent objects
  • set_default(path, value) - Sets values only if the key does not exist

These methods automatically handle intermediate object creation, ensuring that setting providers/spotify/enabled creates the necessary nested dictionaries without manual initialization.

Encryption and Security

The system implements transparent encryption for sensitive configuration values. During first startup, Music Assistant generates a server-wide UUID stored as CONF_SERVER_ID. The first 32 bytes of this UUID derive a Fernet symmetric encryption key. Any configuration entry marked with an "encrypt" suffix in the ConfigEntry model (defined in music_assistant_models/config_entries.py) automatically encrypts before storage and decrypts upon retrieval. This mechanism protects API keys and passwords while keeping the configuration file portable.

Change Coalescing and Persistence

To minimize disk I/O, the configuration system implements write coalescing with a DEFAULT_SAVE_DELAY of 5 seconds. When set() is called, an asyncio.TimerHandle schedules the actual write operation. Subsequent changes within the window reset the timer, batching multiple updates into a single file operation. The save(immediate=True) method bypasses this delay for critical operations like onboarding completion, forcing an immediate write to settings.json.

Typed Configuration Entries

ConfigEntry Model

Each configurable item throughout Music Assistant implements the ConfigEntry model from music_assistant_models/config_entries.py. These entries define:

  • Data type - String, integer, boolean, or secure string
  • Default values - Fallback when user preferences are unset
  • Validation rules - Constraints checked before persistence
  • UI hints - Labels and descriptions for the frontend

The model also includes flags indicating whether values require encryption or represent sensitive credentials.

Provider and Player Configuration

Providers and players contribute their own configuration schemas without modifying core code. For example, music_assistant/providers/spotify/config.py defines provider-specific entries for client IDs and secrets, while player implementations expose settings like volume normalization or output formats. The ConfigController aggregates these definitions on demand through get_provider_config_entries(), get_player_config_entries(), and get_core_config_entries(), building complete configuration UIs dynamically.

API Exposure and Management

The ConfigController exposes its functionality through the JSON-RPC API using the @api_command decorator. Frontend clients interact with endpoints like config/providers and config/players/get to read and modify settings remotely. When removing providers or players, the controller automatically cleans up orphaned configuration sections, preventing stale data accumulation in settings.json.

Working with the Configuration System

The following examples demonstrate how to interact with the configuration system through the MusicAssistant instance:


# Retrieve a value using hierarchical path notation

from music_assistant import MusicAssistant

mass: MusicAssistant = ...   # already running instance

default_volume = mass.config.get("core/volume/default")
print("Default volume:", default_volume)

# Set a value and force immediate persistence

mass.config.set("core/volume/default", 45)
mass.config.save(immediate=True)  # Bypasses the 5-second delay

# Retrieve a complete provider configuration

provider_cfg = await mass.config.get_provider_config("spotify--a1b2c3")
print(provider_cfg)

# Update DSP configuration with automatic encryption handling

from music_assistant.helpers.dsp import DSPConfig

dsp = await mass.config.get_player_dsp_config("player123")
dsp.enabled = True
dsp.preset = "bass_boost"
await mass.config.save_dsp_config("player123", dsp)

# Remove a provider and automatically clean up associated config

await mass.config.remove_provider_config("myprovider--xyz")

Summary

  • Single-file storage: All configuration persists to $HOME/.musicassistant/settings.json as a nested JSON dictionary.
  • Hierarchical access: The ConfigController uses dot/slash notation (e.g., providers/spotify/enabled) to navigate nested configuration objects.
  • Transparent encryption: Sensitive values automatically encrypt using a Fernet key derived from CONF_SERVER_ID, protecting credentials while stored on disk.
  • Write coalescing: Changes batch with a 5-second delay by default, reducing disk I/O through asyncio.TimerHandle scheduling.
  • Typed schemas: ConfigEntry models define validation, defaults, and UI metadata for all configurable parameters across providers and players.
  • Automatic cleanup: Removing providers or players triggers deletion of orphaned configuration sections, maintaining file hygiene.

Frequently Asked Questions

Where does Music Assistant store its configuration file?

Music Assistant stores all persistent configuration in a file named settings.json located in the user's data directory at $HOME/.musicassistant. This single JSON file contains the entire server state, including provider credentials, player settings, and DSP presets.

How does Music Assistant protect sensitive configuration data like API keys?

The configuration system automatically encrypts values marked as sensitive in their ConfigEntry definition. During first startup, the server generates a UUID (CONF_SERVER_ID) and derives a Fernet symmetric key from the first 32 bytes of this identifier. The ConfigController transparently encrypts data before writing to settings.json and decrypts it upon retrieval, ensuring passwords and API keys remain secure at rest.

What happens if multiple configuration changes occur in quick succession?

The implementation uses write coalescing to optimize disk I/O. When set() is called, the controller schedules a save operation using asyncio.TimerHandle with a DEFAULT_SAVE_DELAY of 5 seconds. Subsequent changes within this window reset the timer, batching updates into a single write operation. You can force immediate persistence by calling save(immediate=True) when necessary.

Can providers add their own configuration options without modifying core code?

Yes, the architecture supports extensible configuration through the ConfigEntry model. Providers define their configuration schemas in module-specific files (e.g., music_assistant/providers/spotify/config.py) and return these entries through standardized methods. The ConfigController aggregates these definitions dynamically via get_provider_config_entries(), allowing the frontend to render configuration forms without core code changes.

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 →