---
title: Musicbrainz Stage
description: Enhancing Scrobbles with Musicbrainz
toc_min_heading_level: 2
toc_max_heading_level: 5
---
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

The **Musicbrainz** [Stage](/configuration/transforms#stage) matches your Play data with the [MusicBrainz](https://musicbrainz.org/) database. If the match score is high enough then Multi-Scrobbler uses the match to correct and fill-in missing information in your Play data.

**This Stage is useful for standardizing your Scrobble's Play data, regardless of the Source it is coming from.**

Even if you have have strong opinions about the way your existing music is already tagged/organized you should consider adding the Musicbrainz Stage to your Sources, if only to benefit from adding [MBIDs to scrobbled metadata.](#using-partial-match)

<details>

<summary>Why Should I Prefer MusicBrainz?</summary>

<h3>More Than Mainstream Data</h3>

The [MusicBrainz database contains](https://musicbrainz.org/statistics)

* 37 million+ tracks
* 5 million+ albums
* 2.7 million+ artists

in over 200 languages, contributed by 10,000+ editors each year with many more users voting on edits. 

The database covers a much wider range of music than most commercial services since it isn't restricted by licensing or incentivized to prioritize mainstream music due to business interests. You are much more likely to find data for obscure, indie, and international artists.

<h3>As The Artist Intended</h3>

While some large services (Spotify) may be more consistent in data structure, the MusicBrainz equivalent data is likely to be more true to the way the Artist originally intended since individuals can devote effort to individual artists. A fan of an artist is much more likely to "get it right" for their favorite band than a corporate business trying to do the least amount of work for the largest result.

</details>

:::tip

If you are using [ENV Config](/configuration?config-type=env#configuration-types) for multi-scrobbler and just want a quick and easy setup, skip to [**ENV Configuration**](#env-configuration).

:::

:::tip

Set up [Valkey Caching](/configuration?cachedThings=metadata#caching) to cache Musicbrainz API calls for faster processing.

:::

## Configuration

### API Setup

To avoid rate limiting, MusicBrainz requires API users to identify themself using a [User-Agent that contains an email or URL.](https://wiki.musicbrainz.org/MusicBrainz_API/Rate_Limiting#How_can_I_be_a_good_citizen_and_be_smart_about_using_the_Web_Service?) You must set this in the `data` for your [stage configuration](/configuration/transforms/#configuring-stages) in your [AIO Config](/configuration?configType=aio#configuration-types):

```json5 title="config.json"
{
  // ...
  "transformers": [
    {
      "type": "musicbrainz",
      "name": "MyMB",
      "data": {
        "apis": [
          {
            "contact": "contact@mydomain.com"
          }
        ]
      },
    }
  ]
}
```

<details>

<summary>Additional Musicbrainz Servers</summary>

Other, or additional, Musicbrainz Servers/Mirrors can be added to the API configuration. If more than one server is defined then multi-scrobbler will round-robin load balance API calls.

Use `url` to define the base URL of the Musicbrainz server to use. If `url` is not defined multi-scrobbler assumes it is the primary Musicbrainz server, `https://musicbrainz.org`.

Example of multiple servers:

```json5 title="config.json"
{
  // ...
  "transformers": [
    {
      "type": "musicbrainz",
      "name": "MyMB",
      "data": {
        "apis": [
          {
            "contact": "contact@mydomain.com"
            // uses default Musicbrainz server https://musicbrainz.org
          },
          // additional server
          {
            "contact": "contact@mydomain.com",
            "url": "https://my.mb.mirror.domain.com"
          }
        ]
      },
    }
  ]
}
```

</details>

### Stage Configuration

All of the properties found in [**Matching with Musicbrainz**](#matching-with-musicbrainz) section are configured in [Stage Configuration](/configuration/transforms#configuring-stages) as `defaults`.

Example:

```json5 title="config.json"
{
  // ...
  "transformers": [
    {
      "type": "musicbrainz",
      "name": "MyMB",
      "data": {
        "apis": [
          {
            "contact": "contact@mydomain.com"
          }
        ],
        "defaults": {
          "releaseStatusPriority": ["official"],
          "releaseAllowEmpty": true
        }
      },
    }
  ]
}
```

### Rules and Hooks

[Add your Stage](/configuration/transforms/#stage) to a Source or Client by specifying it in a [Hook](/configuration/transforms/#hook):

```json5 title="subsonic.json"
[
  { 
    "name": "MySubsonic",
    "data": { /* ... */},
    "options": {
      "playTransform": {
        "preCompare": [
          {
            "type": "musicbrainz",
            "name": "MyMB"
          }
        ]
      }
    }
  }
]
```

Each [**Stage Rule**](/configuration/transforms#stage-rules) should be either a boolean, specifying if the transformed data should be used for this field, or a [`when` condition](/configuration/transforms#conditional-modification):

<details>

<summary>Example</summary>

```json5 title="subsonic.json"
[
  { 
    "name": "MySubsonic",
    "data": { /* ... */},
    "options": {
      "playTransform": {
        "preCompare": [
          {
            "type": "musicbrainz",
            "name": "MyMB"
            // ...
            "title": false, // will not apply any changes to Play title
            "artists": {
              "when": {/* ... */}, // will only apply changes to Play artists if "when" is satisfied
            /* ... */
            },
            "album": true // will always apply changes to Play album
            "meta": true // adds MusicBrainz MBIDs to scrobble data
          }
        ]
      }
    }
  }
]
```

</details>

:::tip[Per Component Override]

The `defaults` you set in [Stage Configuration](#stage-configuration) [can be overriden/added  to](/configuration/transforms/#overriding-configuration) (per property) in each Hook.

<details>

<summary>Example</summary>

```json5 title="subsonic.json"
[
  { 
    "name": "MySubsonic",
    "data": { /* ... */},
    "options": {
      "playTransform": {
        "preCompare": [
          {
            "type": "musicbrainz",
            "name": "MyMB",
            "releaseStatusPriority": ["psuedo-release"], // override from defaults
            "searchOrder": ["freetext"] // add new property, combined with defaults
          }
        ]
      }
    }
  }
]
```

</details>

:::

## ENV Configuration

The general configuration shown above can also be configured from a selection of *presets* using [ENV Config](/configuration?configType=env#configuration-types) for individual Sources/Clients.

You must **always** include the `MB_CONTACT` ENV to [identify your application](#api-setup):

```ini
MB_CONTACT=contact@mydomain.com
```

To configure [stage defaults](#stage-configuration) use `MB_PRESETS` with a comma-delimited list of presets you wish to apply. More than one preset can be applied, in which case they combine. You must choose at least one of the presets below:

* `default` - Adds the default [`searchOrder`](#search-methods) `isrc`+`basic` (same as using no presets that affect `searchOrder`)
* `id` - Adds [`searchOrder`](#search-methods) methods that priorites searching by ID: `isrc` + `mbidrecording` + `basicOrIds` + `basic`
* `native` - Adds **Extracted Artists** (`artist`) in `native` mode to [`searchOrder`](#search-methods)
* `aggressive` - Adds **Free Text Search** (`freetext`) to [`searchOrder`](#search-methods)
* `fields` - Applies [Field Scoring](#field-scoring) to match sorting so match text is baised towards similarity to original scrobble
* `sensible` - Applies the [Sensible Default](#sensible-default) configuration for [sorting releases](#sorting-releases)

Finally, use ENV `*_TRANSFORMS=musicbrainz` on each Source/Client you wish to apply this stage to. This applies the stage in the [`preTransform` Hook](/configuration/transforms#lifecycle-hooks) with all [Rules](#rules-and-hooks) enabled.

The `*` stands for the prefix used for each Source/Client's ENV keys. Refer to the individual Source/Client Configuration sections to find this. Example:

* All [Subsonic Source](/configuration/sources/subsonic) ENVs look like `SUBSONIC_USER=myuser` etc...
* Use `SUBSONIC_TRANSFORMS=musicbrainz`

<details>

<summary>Example Full Docker Deploy with ENV Configuration</summary>

Using [Jellyfin + Maloja](/quickstart#create-docker-compose-file) example from Quickstart:

```yaml
services:
  multi-scrobbler:
    image: foxxmd/multi-scrobbler
    container_name: multi-scrobbler
    environment:
      // highlight-start
      - MB_CONTACT=contact@mydomain.com
       # searches with sensible release sorting with the searchOrder
       # isrc => basic => artist (native mode)
      - MB_PRESETS=default,sensible,native
      // highlight-end
      - JELLYFIN_URL=192.168.0.110:8096
      - JELLYFIN_APIKEY=c9fae8756fbf481ebd9c5bb56b
      - JELLYFIN_USER=MyUser
      // highlight-start
       # applies musicbrainz Stage to preTransform of Jellyfin source
      - JELLYFIN_TRANSFORMS=musicbrainz
      // highlight-end

      # maloja receives enhanced scrobble from Jellyfin
      - MALOJA_URL=http://192.168.0.100:42010
      - MALOJA_API_KEY=myApiKey


    volumes:
      - "./config:/config"
    ports:
      - "9078:9078"
    restart: unless-stopped
```

</details>

## Matching with Musicbrainz

:::note

**All properties found in this section are optional.**

If you just want a sensible default for configuraion refer to the [Best Practices](#best-practices) section.

:::

Matching your Scrobble's Play data with a result from Musicbrainz is comprised of two steps:

* [**Searching**](#searching) Musicbrainz using parts of your Scrobble as queries
* [**Refining**](#refining) matched results to select the desired/allowed match

Both steps have separate configuration.

### Searching

#### Should MS Search?

Before MS begins a search it checks if your Scrobble data already contains:

* [MBIDs](https://musicbrainz.org/doc/MusicBrainz_Identifier) for [artists](https://musicbrainz.org/doc/Artist), [album](https://musicbrainz.org/doc/Release), and specific [track/recording](https://musicbrainz.org/doc/Recording)
* and duration

If it already has all required data types then the entire Musicbrainz Stage [is **skipped**.](/configuration/transforms/#flow-control) If any are missing then a search is performed.

Define which data types are required using:

* `searchWhenMissing` (defaults to all) - A list containing any of: `artists` `title` `album` `duration`
* `forceSearch` (default `false`) - Force searching even if all required data is present

<details>

<summary>Example</summary>

[Stage Configuration](/configuration/transforms#configuring-stages) example:

```json5
// ...
"defaults": {
    // only search if MBIDs for artists or album is missing
    "searchWithMissing": ["artists", "album"]

    // uncomment to make the stage always search, even if above data is present
    //forceSearch: true
  }
```

</details>

If required data is missing then the next step is making [search queries to the Musicbrainz database](https://musicbrainz.org/doc/MusicBrainz_API/Search) to try to get potential candidates.

#### Search Methods

Multi-scrobbler can search for Musicbrainz matches using **multiple** methods. You control:

* the search methods that are used
* **the order** in which search methods are executed

All search methods will be run in the order they were defined until one method returns at least one match.

**The methods you choose, and the order they run in, can drastically change how many API calls are needed to get a match, and what types of matches are returned.** See [**Search Considerations**](#search-considerations) for guidance on how to decide on methods based on Source.

To set search methods and their order use the `searchOrder` option in your [Stage Configuration](/configuration/transforms#configuring-stages):

```json5
// ...
"defaults": {
    // only searches using these methods, in the order they are listed here
    "searchOrder": ["isrc", "mbidrecording", "basic","freetext"]
  }
```

:::tip[Default Search Methods]

If `searchOrder` is undefined then Multi-scrobbler will default to using `isrc` then `basic` method.

:::

<h4>Available Methods</h4>

<details>

<summary>ISRC (`isrc`)</summary>

If your Scrobble data contains an [ISRC](https://musicbrainz.org/doc/ISRC) (usually from Spotify) then Multi-scrobbler can search using this ID, only.

[Stage Configuration](/configuration/transforms#configuring-stages) example:

```json5
// ...
"defaults": {
    // will only search using isrc
    "searchOrder": ["isrc"]
  }
```

</details>

<details>

<summary>Track MBID (`mbidrecording`)</summary>

If your Scrobble data contains an [Track (Recording) MBID](https://musicbrainz.org/doc/Recording) then Multi-scrobbler can search using this MBID, only.

[Stage Configuration](/configuration/transforms#configuring-stages) example:

```json5
// ...
"defaults": {
  // will only search using recording mbid
    "searchOrder": ["mbidrecording"]
  }
```

</details>

<details>

<summary>Album, Artist, and Title Fields (`basic`)</summary>

Will search Musicbrainz using any/all available text fields from your scrobble: Album, Artists(s), and Title. Does not leverage any existing MBIDs.

[Stage Configuration](/configuration/transforms#configuring-stages) example:

```json5
// ...
"defaults": {
    // will only search using album, artist(s), and title
    "searchOrder": ["basic"]
  }
```

</details>

<details>

<summary>Album/Artist/Title OR MBIDs (`basicOrIds`)</summary>

This is the same as the above **Album, Artist, and Title Fields** search except that if the scrobble data contains any corresponding MBIDs it will use those instead of the plain text field.

[Stage Configuration](/configuration/transforms#configuring-stages) example:

```json5
// ...
"defaults": {
    // will only search using album, artist(s), and title
    "searchOrder": ["basicOrIds"]
  }
```

Example:

```json
{
    "title": "Endless Possibility (feat. Wheatus)",
    "artists": ["Bowling For Soup"]
    "albumMbid": "c9f91cdc-984e-4303-9a51-4ac0dfa2348f",
    "album": "My Cool Album"
}
```

Multi-scrobbler will search Musicbrainz using `title`, `artists`, and `albumMbid` but NOT `album`.

</details>

<details>

<summary>Album And Title Only (`album`)</summary>

If your Scrobble data contains a title, artist(s), and an album (all three fields) then Multi-scrobbler can search using only title and album by using `album` for `searchOrder`:

[Stage Configuration](/configuration/transforms#configuring-stages) example:

```json5
// ...
"defaults": {
    // will only search using only title + album name
    "searchOrder": ["album"]
  }
```

</details>

<details>

<summary>Extracted Artists (`artist`)</summary>

If your scrobble data contains only **one** artist string then Multi-scrobbler can attempt to extract multiple artists from your artist and track string.

Use `artist` with `searchOrder` and optionally specify the extraction mode with `searchArtistMethod`:

[Stage Configuration](/configuration/transforms#configuring-stages) example:

```json5
// ...
"defaults": {
    // will only search using title/album + extracted artists
    "searchOrder": ["artist"],
    //searchArtistMethod: 'native' // optional
  }
```

`searchArtistMethod` can be set to **Native** or **Naive** mode:

<Tabs groupId="searchArtistMethod" queryString>
    <TabItem value="native" label="Native (Recommended)">

    **Native** (`native`) mode uses an aggressive configuration of the [Native Stage](/configuration/transforms/native) to extract artists using common delimiters and common "joined" artist patterns from the artist and title string of your Scrobble data.


    :::tip

    If you already have a Native Stage configured you should use that instead, running it before the Musicbrainz stage.

    :::

    <details>

    <summary>Native Mode Example</summary>

    ```json
    {
        "title": "Endless Possibility (feat. Wheatus)",
        "artists": ["Bowling For Soup & Punk Rock Factory, My Cool Band"]
    }
    ```

    * Extracts `Bowling For Soup` `Punk Rock Factory` `My Cool Band` from artist string
    * Extracts `Wheatus` from title string
      * Removes `(feat. Wheatus)` from title string because it found an artist there

    Resulting data used for Musicbrainz search:

    ```json
    {
        "title": "Endless Possibility",
        "artists": ["Bowling For Soup", "Punk Rock Factory", "My Cool Band", "Wheatus"]
    }
    ```

    </details>

    [Stage Configuration](/configuration/transforms#configuring-stages) example:

    ```json5
    // ...
    "defaults": {
      // will only search using title/album + native extracted artists
      "searchOrder": ["artist"],
      "searchArtistMethod": "native"
      }
    ```

    </TabItem>
    <TabItem value="naive" label="Naive">

    **Naive** (`naive`) mode looks for the first found common delimiter in the artist string. If it finds one then it uses the preceding value as the only artist in the Musicbrainz search. It does not try to extract additional artists from the artist string, or extract anything from the title string.

    <details>

    <summary>Naive Mode Example</summary>

    ```json
    {
        "title": "Endless Possibility (feat. Wheatus)",
        "artists": ["Bowling For Soup, Punk Rock Factory & Wheatus"]
    }
    ```

    * Finds `&` as first common delimiter, extracts "Bowling For Soup"

    Resulting data used for Musicbrainz search:

    ```json
    {
        "title": "Endless Possibility (feat. Wheatus)",
        "artists": ["Bowling For Soup"]
    }
    ```
    </details>

    [Stage Configuration](/configuration/transforms#configuring-stages) example:

    ```json5
    // ...
    "defaults": {
      // will only search using title/album + naive extracted artists
      "searchOrder": ["artist"],
      "searchArtistMethod": "naive"
      }
    ```

    </TabItem>
</Tabs>

</details>

<details>

<summary>Free Text (`freetext`)</summary>

This will search Musicbrainz for **all** text of your Scrobble data artist/title/album, without constraint.

:::warning

Free text search is **unconstrained** which means that Musicbrainz will match text in **any** part of a Recording (artist, release, or recording field), regardless of where it is found. This may lead to results that are unexpected and undesired.

If you know your Source's Play data is well organized you **should not** enable this. Free Text search should only be used if:

* your music is not well organized or
* there may be many alternative titles/artists for the music you listen to (such as with [psuedo-releases](https://wiki.musicbrainz.org/Release#Status))
* you can tolerate that matches may not be accurate for releases.
  * Generally, if you keep [score](#score) high then matched releases should *at least* be the right artist or what you would expect within a reason, as a match. The release may not be the one you actually listened to but it would be "close enough".

:::

[Stage Configuration](/configuration/transforms#configuring-stages) example:

```json5
// ...
"defaults": {
    // ...
    // will only search using free text search of all artist/album/title
    "searchOrder": ["freetext"],
  }
```

</details>

:::note

If all defined search methods do not return any results then the stage is marked as [**failed** (`onFailure`) for **Flow Control**](/configuration/transforms/#flow-control).

:::

### Refining

### Score

Each match returned by MusicBrainz contains a numeric score representing how close it was to the search parameters. Set `score` in configuration to set a minimum score that must be met by matches. Default is `90`.

```json5
{
  // ...
  "score": 90 // matches must score 90 or higher to be considered
}
```

### Filtering

There are several attributes associated with the [Release](https://wiki.musicbrainz.org/Release) a [Recording](https://wiki.musicbrainz.org/Recording) (individual Track/song) belongs to that you may be interested in controlling. MS can use these attributes to filter what the final Recording selected to match against your Play data is.

An easy way to think about this:

* Do you prefer Albums, Singles, or EPs?
* Do you always want to use an official album release, or are bootleg or cancelled albums okay to use?
* Should a track that belongs to a Compilation album be allowed to match?
* What country do you prefer an album release to be from?

##### Release Attributes

These attributes (`attribute_name` in config) are:

* [Release **Status**](https://wiki.musicbrainz.org/Release#Status) (`releaseStatus`) - Is this release/album/ep official, promotional, bootleg, etc...
* [Release Group **Primary Type**](https://wiki.musicbrainz.org/Release_Group/Type#Primary_types) (`releaseGroupPrimaryType`) - Is this release an Album, Single, EP, etc...
* [Release Group **Secondary Type**](https://wiki.musicbrainz.org/Release_Group/Type#Secondary_types) (`releaseGroupSecondaryType`) - Is this release a compilation, soundtrack, remix, etc...
* [Release **Country**](https://beta.musicbrainz.org/doc/Release/Country) (`releaseCountry`) - What ISO2 country was this released in? US, GB, MX, etc...
  * MusicBrainz uses the special code `XW` to represent a "Worldwide" release AKA release not made specifically for a country

Each of the above attributes can be used to **filter** matches using `allow` (explicitly include) or `deny` (exclude) terms. The correct property name for each is like so:

```
[attribute_name]Allow = [...]
[attribute_name]Deny

EX to explicitly allow only release status "official"

"releaseStatusAllow": ["official"]

EX to exclude compilations

"releaseGroupSecondaryTypeDeny": ["compilation"]
```

#### Empty Releases

Sometimes a Recording may not have any associated Releases. This may be because there is not enough information about the Recording yet, or it was never included on an actual Release.

You may want to filter the majority of your matches by releases but allow matching a Recording that has no releases to begin with. To allow this set `releaseAllowEmpty` to `true` in configuration:

```json5
// ...
"defaults": {
  // ...
  "releaseAllowEmpty": true // don't remove a match during filtering just because it has no releases
}
```

### Sorting


*After* matches have been [filtered](#filtering), the remaining matches *and their releases** can be sorted. Matches/Releases with an attribute that does not match are **not** removed, but they are sorted lower than matches that do match.

Sorting can be a good alternative to filtering: with filters there is a possibility your filters may eliminate all matches; if you want to ensure that **some** match will be used then sorting can ensure that the **best choice** out of those given will always be used, without accidentally ending up with **no choice**.

Sorting can be used instead of filters, or in conjunction with filters, it's your choice.

#### Release Attributes {#sorting-releases}

Each [Release Attribute](#release-attributes) has one additional property that can be used to **rank** releases based on the order of the values you give it. This is the `priority` property.

In configuration:

```
[attribute_name]Priority = [...]

EX to prefer "official" releases over everything else

"releaseStatusPriority": ["official"]

EX to prefer albums, then singles, over everything else

"releaseGroupPrimaryTypePriority": ["album", "single"]
```

#### Field Scoring

Matches can additionally be sorted/ranked based on how similar their fields are to the original Scrobble. Use these properties for sorting when you want the final Musicbrainz match to be as closely matched to the original Scrobble, as opposed to what may be the most "correct" match based on the Musicbrainz score or suggested Release.

<details>

<summary>Example Scenario</summary>

You scrobble [this track from spotify](https://open.spotify.com/track/21trRQA61afzljSK9V0SHX), which has an English title, Album name, and Artist name.

```json                    
{ 
  "track": "Price",
  "artists": ["ATLUS Sound Team"],
  "album": "PERSONA5 ORIGINAL SOUNDTRACK"
}
```

However, the [Recording found by Musicbrainz](https://beta.musicbrainz.org/search?query=isrc%3AJPK651601515&type=recording&limit=25&method=advanced) has multiple Releases (albums). The first suggested release is a *related* Album with similar name (`Persona 25th Anniversary Deluxe Vinyl Box Set`) but it's not the same as your album. Additionally, the artists for this album are in Japanese, rather than English.

You have used `releaseStatusPriority` to prioritize `psuedo-release` which does work but there are still multiple releases chosen, and some are non-English (`『ペルソナ5』オリジナル・サウンドトラック`).

Using `"albumWeight": 0.4` and `"artistWeight": 0.3` ensures that the releases that have an album name, and artist names, more similar to your original scrobble are ranked higher. Resulting in [the correct Release (`PERSONA5 ORIGINAL SOUNDTRACK`)](https://beta.musicbrainz.org/release/82de33b1-1cd6-4236-b116-561d0ecc8acf) being chosen as the final match.

</details>

Add one or more of these weight properties to your [Stage Configuration](/configuration/transforms#configuring-stages) to enable Field Scoring:

```json5
// ...
"defaults": {
    // ...
    // enables album text similiarity scoring
    "albumWeight": 0.33,
    // enables title text similiarity scoring
    "titleWeight": 0.33,
    // enables artist text similiarity scoring
    "artistWeight": 0.33
  }
```

While not enforced, it's a good idea to keep these weights under `1`. And if using more than one weight, they should be add up to `1`.

<details>

<summary>How Scoring is Combined</summary>

The score similarity from `albumWeight` will be added to the score accumulated by [Release Attributes](#sorting-releases) to affect the final ranking of each Release for a Match/Recording.

In the final sorting of Recordings, title score + artist score is combined with the top ranked release album score. The top ranked score is then used to choose the matched Recording.

</details>

## Best Practices

### Caching

You **should** setup [metadata caching](/configuration/transforms#caching) to reduce API calls, improve transform performance, and reduce memory usage when using this stage.

### Sensible Default

Generally, the Musicbrainz Stage can be used without any of the [optional configuration](#configuration) and you should still see good results from matches. The top [scored](#score) match is, anecdotally, good enough for correcting and filling in surface-level play data like Title and Artist names.

For a more opinionated match that will mirror what you would expect from data from large music services:

```json5
{
    // use official release over anything else
    "releaseStatusPriority": ["official"],
    // prefer album, then single, then ep
    "releaseGroupPrimaryTypePriority": ["album", "single", "ep"],
    // prefer worldwide release
    "releaseCountryPriority": ["XW"]
}
```

Consider adding [Artist Extraction (`artist`)](#search-methods) in **Native Mode** if any of your Sources do not support multiple artists or your music collection is not tagged well.

<details>

```json5
{
  // use official release over anything else
  "releaseStatusPriority": ["official"],
  // prefer album, then single, then ep
  "releaseGroupPrimaryTypePriority": ["album", "single", "ep"],
  // prefer worldwide release
  "releaseCountryPriority": ["XW"],
  // search using extracted artist as last search attempt
  "searchOrder": ["isrc", "basic", "artist"],
  "searchArtistMethod": "native"
}
```

</details>

### Search Considerations

How complete, and accurate, your scrobble data is from a certain Source should dictate what [Search Methods](#search-methods) you should use for matching.

Musicbrainz agressively normalizes **artist, album, and title** fields in its database which means that if your Scrobble data contains major innaccuracies or phrases fields in a non-normal way, it's possible Musicbrainz won't find any matches. This is a more common occurrence when trying to match Scrobble data from Sources that don't support multiple artist data, like [Subsonic](/configuration/sources/subsonic) and [Last.fm](/configuration/sources/lastfm-source).

<details>

<summary>Examples of Bad Data</summary>

Last.fm Scrobble returns an Artist in the title and combines two artists into one string:

```json
{
    "title": "Endless Possibility (feat. Wheatus)",
    "artists": ["Bowling For Soup & Punk Rock Factory"]
}
```

The corresponding [Musicbrainz Recording](https://musicbrainz.org/release/85bf284b-8b39-414e-8ad9-61f593072588) has all artists separated and no artist in the title:

```json
{
    "title": "Endless Possibility",
    "artists": ["Bowling For Soup", "Punk Rock Factory", "Wheatus"]
}
```

</details>

**In general, you should prioritize methods that search by unique identifiers** (`isrc` `mbidRecording` `basicOrIds`) if you know your Sources may contain *accurate* data for these. Such as: Spotify, Jellyfin, Plex, and Listenbrainz (Source).

Use field-based methods if your Sources do not return IDs consistently (or at all). Add more field-based methods the more inaccurate your scrobble data may be, but be aware that more aggressive/broad searches may return less accurate results.

<DetailsAdmo type="tip" summary="Matching Scrobbles from Last.fm">

Last.fm is inconsistent when it comes to matching your scrobbles. It will accept (almost) any fields for track/title/album and aggressively attempt to match/correct these, regardless of your intentions.

Unless the client scrobbling to Last.fm is also sending an `mbid`, it's likely your scrobble will not be **consistently** matched correctly and so the `mbid` supplied by last.fm in its scrobble data will be wrong.

Therefore, you should *avoid* using `mbidRecording` and `basicOrIds` as the primary search methods (or understand the risks of using them). Instead, prefer to use field-based methods *before* falling back to id-based methods.

</DetailsAdmo>

### Filter Considerations

When using your own filters consider:

* Prefer `deny` over `allow`
  * Releases come in all kinds of formats. Since `allow` is explicit you may filter out your desired match without realizing it (correct data except for release type). Or the Musicbrainz data for a higher scored match may be appropriate but you did not include it, exhaustively.
* Prefer [Sorting](#sorting) over [Filtering](#filtering)
  * Sorting does not eliminate any matches. It is, generally, better to get **some** match than it is to have your Play data completely uncorrected because filtering eliminated all matches

### Using Partial Match

Use [Rules](#rules-and-hooks) to apply MusicBrainz match data selectively.

If you know that your music collection is well organized and you do not want to change the artists/title/album etc... sent to a [Client](/configuration/clients), you can still benefit from matches by only applying MBIDs using `meta` so that any Client that supports Musicbrainz data ([Koito](/configuration/clients/koito), [Tealfm](/configuration/clients/tealfm), [Listenbrainz](/configuration/clients/listenbrainz), [Rocksky](/configuration/clients/rocksky)) can still get that data.

<details>

<summary>Example</summary>

```json5 title="subsonic.json"
[
  { 
    "name": "MySubsonic",
    "data": { /* ... */},
    "options": {
      "playTransform": {
        "preCompare": [
          {
            "type": "musicbrainz",
            "name": "MyMB",
            "title": false,
            "artists": false,
            "album": false,
            "albumArtists": false,
            "meta": true
          }
        ]
      }
    }
  }
]
```

</details>

## Logging

If

* Musicbrainz is not returning matches
* Multi-scrobbler is using the wrong match
* or the resulting enhanced Scrobble is not what you expected

Enable logging by turning on [**Debug Mode**](/configuration#debug-mode) to help diagnose any issues with the Musicbrainz API and Scrobble enhancement. **Before creating an issue** please enable logging and include any logs with your issue as this is needed to debug.

If you have multiple Modification Stages and need to see the diff for your Play between each Stage, enable `"log":  "all"` in the individual [Modification Stage](#rules-and-hooks).

<details>

<summary>Example</summary>

In a [Subsonic](/configuration/sources/subsonic) [File Config](/configuration?configType=file#configuration-types):

```json5 title="subsonic.json"
[
  { 
    "name": "MySubsonic",
    "data": { /* ... */},
    "options": {
      "playTransform": {
        // highlight-start
        "log": "all",
        // highlight-end
        "preCompare": [
          {
            "type": "musicbrainz",
            "name": "MyMB",
          }
        ]
      }
    }
  }
]
```

</details>

## Examples

### Minimal

<details>

<summary>Example</summary>

Your [AIO Config](/configuration?configType=aio#configuration-types):

```json5 title="config.json"
{
  // ...
  "transformers": [
    {
      "type": "musicbrainz",
      "name": "MyMB",
      "data": {
        "apis": [
          {
            "contact": "contact@mydomain.com"
          }
        ]
      },
    }
  ]
}
```

In a [Subsonic](/configuration/sources/subsonic) [File Config](/configuration?configType=file#configuration-types):

```json5 title="subsonic.json"
[
  { 
    "name": "MySubsonic",
    "data": { /* ... */},
    "options": {
      "playTransform": {
        "preCompare": [
          {
            "type": "musicbrainz",
            "name": "MyMB"
          }
        ]
      }
    }
  }
]
```
</details>

### Sensible Default

Using the config shown in [Sensible Default](#sensible-default) with File/AIO config, instead of [ENV Config](#env-configuration).

<details>

<summary>Example</summary>

Your [AIO Config](/configuration?configType=aio#configuration-types):

```json5 title="config.json"
{
  // ...
  "transformers": [
    {
      "type": "musicbrainz",
      "name": "MyMB",
      "data": {
        "apis": [
          {
            "contact": "contact@mydomain.com"
          }
        ]
      },
      "defaults": {
        "releaseStatusPriority": ["official"],
        "releaseGroupPrimaryTypePriority": ["album", "single", "ep"],
        "releaseCountryPriority": ["XW"],
        "searchArtistMethod": "native"
        "searchOrder": ["isrc", "basic", "artist"]
      }
    }
  ]
}
```

In a [Subsonic](/configuration/sources/subsonic) [File Config](/configuration?configType=file#configuration-types):

```json5 title="subsonic.json"
[
  { 
    "name": "MySubsonic",
    "data": { /* ... */},
    "options": {
      "playTransform": {
        "preCompare": [
          {
            "type": "musicbrainz",
            "name": "MyMB"
          }
        ]
      }
    }
  }
]
```
</details>

### Add Metadata Only (No Scrobble Modification)

If you do not want your scrobble data to be modified (artists/album/title) but still want to benefit from associating the scrobble with a Musicbrainz match (for Scrobble [Clients](/configuration/clients) like [Tealfm](/configuration/clients/tealfm), [Rocksky](/configuration/clients/rocksky), and [Koito](/configuration/clients/koito)), then use [Partial Match](#using-partial-match) rules to specify only `meta` to be updated.

<details>

<summary>Example</summary>

Your [AIO Config](/configuration?configType=aio#configuration-types):

```json5 title="config.json"
{
  // ...
  "transformers": [
    {
      "type": "musicbrainz",
      "name": "MyMB",
      "data": {
        "apis": [
          {
            "contact": "contact@mydomain.com"
          }
        ]
      },
      "defaults": {
        // maybe something like Sensible Default?
      }
    }
  ]
}
```

In a [Jellyfin](/configuration/sources/jellyfin) [File Config](/configuration?configType=file#configuration-types):

```json5 title="jellyfin.json"
[
  { 
    "name": "MyJellyfin",
    "data": { /* ... */},
    "options": {
      "playTransform": {
        "preCompare": [
          {
            "type": "musicbrainz",
            "name": "MyMB",
            "title": false,
            "artists": false,
            "album": false,
            "albumArtists": false,
            "meta": true // only update scrobble metadata with MBIDs
          }
        ]
      }
    }
  }
]
```
</details>

### Use Native Stage if no Musicbrainz Matches

* Musicbrainz Stage configured with
  * [Sensible Defaults](#sensible-default)
  * Fallback search with [Artist Extraction](#search-methods)
* [Default Native Stage](/configuration/transforms/native/#default-stage)
* Uses [Flow Control](/configuration/transforms#flow-control) to run Native only if there are no Musicbrainz matches

<details>

<summary>Example</summary>

Your [AIO Config](/configuration?configType=aio#configuration-types):

```json5 title="config.json"
{
  // ...
  "transformers": [
    {
      "type": "musicbrainz",
      "name": "MyMB",
      "data": {
        "apis": [
          {
            "contact": "contact@mydomain.com"
          }
        ]
      },
      "defaults": {
        "releaseStatusPriority": ["official"],
        "releaseGroupPrimaryTypePriority": ["album", "single", "ep"],
        "releaseCountryPriority": ["XW"],
        "searchArtistMethod": "native",
        "searchOrder": ["isrc","basic", "artist"]
      }
    }
  ]
}
```

In a [Jellyfin](/configuration/sources/jellyfin) [File Config](/configuration?configType=file#configuration-types):

```json5 title="jellyfin.json"
[
  { 
    "name": "MyJellyfin",
    "data": { /* ... */},
    "options": {
      "playTransform": {
        "preCompare": [
          {
            // if musicbrainz is successful then do NOT run native,
            // only run native if musicbrainz fails to find a match (onFailure)
            "type": "musicbrainz",
            "name": "MyMB"
            "onSuccess": "stop",
            "onFailure": "continue"
          },
          {
            "type": "native"
          }
        ]
      }
    }
  }
]
```
</details>

### Different Search Methods Based on Source (Last.fm and Spotify) {#override-stage-configuration-example}

[Last.fm has unreliable MBIDs](#search-considerations) and Spotify provides an [ISRC](https://musicbrainz.org/doc/ISRC) and consistent formatting.

* For Spotify, we want to rely on ID [searches](#search-methods) and matches that are [close to original scrobble text](#field-scoring)
* For Last.fm, we want to use only text searches and take whatever correction is given to us

Use [**Per Component Overrides**](#rules-and-hooks) with the default (or [sensible](#sensible-default)) [Stage Configuration](#stage-configuration) to specify different search behavior for each Source.

<details>

<summary>Example</summary>

Stage Configuration

```json5 title="config.json"
{
  // ...
  "transformers": [
    {
      "type": "musicbrainz",
      "name": "MyMB",
      "data": {
        "apis": [
          {
            "contact": "contact@mydomain.com"
          }
        ],
        "defaults": {} // maybe use sensible?
      },
    }
  ]
}
```

```json5 title="lastfm.json"
[
  { 
    "name": "MyLFM",
    "configureAs": "source"
    "data": { /* ... */},
    "options": {
      "playTransform": {
        "preCompare": [
          {
            "type": "musicbrainz",
            "name": "MyMB",
            "searchArtistMethod": "native"
            // does not use any ID searches
            // fallback to more aggressive/broad searches
            "searchOrder": ["basic", "artist", "freetext"]
          }
        ]
      }
    }
  }
]
```

```json5 title="spotify.json"
[
  { 
    "name": "MySpotify",
    "data": { /* ... */},
    "options": {
      "playTransform": {
        "preCompare": [
          {
            "type": "musicbrainz",
            "name": "MyMB",
            // use ISRC as first search
            "searchOrder": ["isrc", "basic"]
            // bias matches towards similarity to original data
            "albumWeight": 0.33,
            "titleWeight": 0.33,
            "artistWeight": 0.33
          }
        ]
      }
    }
  }
]
```

</details>

### Use Metadata for Discord Album Art

The [Discord](/configuration/clients/discord) Client can fetch album art images from [Cover Art Archive](http://coverartarchive.org/) if the Scrobble data contains a Release MBID. Use the Musicbrainz Stage on the `preCompare` hook for your Discord client so that missing Musicbrainz IDs are populated for this functionality.

<details>

<summary>Example</summary>

<Tabs groupId="configType" queryString>
    <TabItem value="env" label="ENV">
    Using the [ENV Configuration](#env-configuration) from above, add these fields to your docker compose `environment:`

    ```yaml
    - MB_CONTACT=contact@mydomain.com
    - MB_PRESETS=id,native,sensible
    - DISCORD_TRANSFORMS=musicbrainz
    ```
    </TabItem>
    <TabItem value="file" label="File">
    Your [AIO Config](/configuration?configType=aio#configuration-types):

    ```json5 title="config.json"
    {
      // ...
      "transformers": [
        {
          "type": "musicbrainz",
          "name": "MyMB",
          "data": {
            "apis": [
              {
                "contact": "contact@mydomain.com"
              }
            ]
          },
          "defaults": {
            // sensible defaults
            "releaseStatusPriority": ["official"],
            "releaseGroupPrimaryTypePriority": ["album", "single", "ep"],
            "releaseCountryPriority": ["XW"],
            "searchArtistMethod": "native",
            "searchOrder": ["isrc","basic", "artist"]
          }
        }
      ]
    }
    ```

    In your [Discord](/configuration/clients/discord) [File Config](/configuration?configType=file#configuration-types):

    ```json5 title="discord.json"
    [
      { 
        "name": "MS",
        "data": { /* ... */},
        "options": {
          "playTransform": {
            "preCompare": [
              {
                "type": "musicbrainz",
              }
            ]
          }
        }
      }
    ]
    ```
    </TabItem>
    <TabItem value="aio" label="AIO">
    Your [AIO Config](/configuration?configType=aio#configuration-types):

    ```json5 title="config.json"
    {
      // ...
      "transformers": [
        {
          "type": "musicbrainz",
          "name": "MyMB",
          "data": {
            "apis": [
              {
                "contact": "contact@mydomain.com"
              }
            ]
          },
          "defaults": {
            // sensible defaults
            "releaseStatusPriority": ["official"],
            "releaseGroupPrimaryTypePriority": ["album", "single", "ep"],
            "releaseCountryPriority": ["XW"],
            "searchArtistMethod": "native",
            "searchOrder": ["isrc","basic", "artist"]
          }
        }
      ],
      "sources": [
        { 
          "name": "MS",
          "type": "discord",
          "data": { /* ... */},
          "options": {
            "playTransform": {
              "preCompare": [
                {
                  "type": "musicbrainz",
                }
              ]
            }
          }
        }
      ]
    }
    ```
    </TabItem>
</Tabs>

</details>