> For the complete documentation index, see [llms.txt](https://docs.nuclearplayer.com/nuclear/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.nuclearplayer.com/nuclear/plugins/metadata.md).

# Metadata

## Metadata Providers

Metadata providers supply Nuclear with information about artists, albums, and tracks. When a user searches for music or opens an artist page, Nuclear delegates to whatever metadata provider is active. Without one, there's nothing to search and nothing to display.

Plugins can either add new providers, or use data from existing providers.

Most plugins will add a new provider. Check out the [Discogs plugin](https://github.com/NuclearPlayer/nuclear-plugin-discogs) for a good reference implementation.

***

## Implementing a provider

### Minimal example

A metadata provider is an object that you register with `api.Providers.register()`. At minimum, it needs an `id`, `kind: 'metadata'`, a `name`, and at least one search method:

```typescript
import type { MetadataProvider, NuclearPlugin, NuclearPluginAPI } from '@nuclearplayer/plugin-sdk';

const provider: MetadataProvider = {
  id: 'my-metadata',
  kind: 'metadata',
  name: 'My Metadata Source',

  searchCapabilities: ['artists', 'albums'],

  async searchArtists(params) {
    // Call your API, return ArtistRef[]
  },
  async searchAlbums(params) {
    // Call your API, return AlbumRef[]
  },
};

const plugin: NuclearPlugin = {
  onEnable(api: NuclearPluginAPI) {
    api.Providers.register(provider);
  },
  onDisable(api: NuclearPluginAPI) {
    api.Providers.unregister('my-metadata');
  },
};

export default plugin;
```

{% hint style="warning" %}
Always unregister your provider in `onDisable`. If you don't, it stays registered and Nuclear may still route queries to it after the plugin is disabled.
{% endhint %}

### Capabilities

Capabilities tell Nuclear which methods your provider supports. Nuclear will never call a method you haven't declared a capability for.

#### Search capabilities

Declared via `searchCapabilities`. Controls which search methods Nuclear calls:

| Capability    | Method called             | Returns         |
| ------------- | ------------------------- | --------------- |
| `'artists'`   | `searchArtists(params)`   | `ArtistRef[]`   |
| `'albums'`    | `searchAlbums(params)`    | `AlbumRef[]`    |
| `'tracks'`    | `searchTracks(params)`    | `Track[]`       |
| `'playlists'` | `searchPlaylists(params)` | `PlaylistRef[]` |
| `'unified'`   | `search(params)`          | `SearchResults` |

The search params for individual methods (`searchArtists`, `searchAlbums`, etc.) contain `query` and `limit`. For `search` (unified), there's an additional `types` array so you know which categories were requested.

{% hint style="info" %}
Most providers should implement individual search methods (`searchArtists`, `searchAlbums`, etc.). Use `'unified'` only if your API has a single endpoint that returns all types at once.
{% endhint %}

#### Artist metadata capabilities

Declared via `artistMetadataCapabilities`. Controls artist detail pages:

| Capability               | Method called                         | Returns             |
| ------------------------ | ------------------------------------- | ------------------- |
| `'artistBio'`            | `fetchArtistBio(artistId)`            | `ArtistBio`         |
| `'artistSocialStats'`    | `fetchArtistSocialStats(artistId)`    | `ArtistSocialStats` |
| `'artistTopTracks'`      | `fetchArtistTopTracks(artistId)`      | `TrackRef[]`        |
| `'artistAlbums'`         | `fetchArtistAlbums(artistId)`         | `AlbumRef[]`        |
| `'artistPlaylists'`      | `fetchArtistPlaylists(artistId)`      | `PlaylistRef[]`     |
| `'artistRelatedArtists'` | `fetchArtistRelatedArtists(artistId)` | `ArtistRef[]`       |

#### Album metadata capabilities

Declared via `albumMetadataCapabilities`:

| Capability       | Method called                | Returns |
| ---------------- | ---------------------------- | ------- |
| `'albumDetails'` | `fetchAlbumDetails(albumId)` | `Album` |

You don't have to support everything. Declare only what your source can provide. Nuclear adapts the UI based on what's available - for example, if you don't declare `'artistTopTracks'`, the top tracks section won't appear on artist pages.

### Streaming provider pairing

If your metadata provider only works with a specific streaming provider (e.g. both come from the same plugin and the same service), declare this with `streamingProviderId`:

```typescript
const provider: MetadataProvider = {
  id: 'my-metadata',
  kind: 'metadata',
  name: 'My Source',
  streamingProviderId: 'my-streaming',
  // ...
};
```

When the user selects this metadata provider in the Sources view, the streaming provider is locked to the one you specified. If the required streaming provider isn't installed, the Sources view shows a warning.

Only set this when the pairing is a hard requirement. If your metadata provider works with any streaming provider, leave it out.

***

## Return types

Your provider methods must return objects matching types from `@nuclearplayer/model` (also exported from `@nuclearplayer/plugin-sdk`). Every entity has a `source` field (`ProviderRef`) that identifies where it came from.

## Thumbnails and artwork

Nuclear picks the best artwork for each context. These include thumbnails, cover art, background images, or artist profile pictures. Provide multiple sizes when you can.

### ProviderRef

Every entity needs a `source` that ties it back to your provider. This is how Nuclear navigates between search results and detail pages.

```typescript
type ProviderRef = {
  provider: string;  // Must match your provider's id
  id: string;        // Your provider's identifier for this entity
  url?: string;      // Optional link to the source page
};
```

#### Compound IDs

The `id` can be anything your provider understands. You'll receive it back in `fetchArtistBio(id)`, `fetchAlbumDetails(id)`, etc.

Example: The Discogs plugin uses compound IDs like `master:12345` to encode both the release type (release/master) and ID in a single string.

***

## Using metadata

Plugins can also query the active metadata provider. Use `api.Metadata.*` to search and fetch details without knowing which provider is registered.

```typescript
export default {
  async onEnable(api: NuclearPluginAPI) {
    const results = await api.Metadata.search({
      query: 'Radiohead',
      types: ['artists', 'albums'],
      limit: 10,
    });

    if (results.artists?.length) {
      const artistId = results.artists[0].source.id;
      const providerId = results.artists[0].source.provider;

      // Fetch full details (routes to the same provider that returned the search result)
      const artist = await api.Metadata.fetchArtistBio(artistId, providerId);
      const albums = await api.Metadata.fetchArtistAlbums(artistId, providerId);
    }
  },
};
```

### Consumer reference

```typescript
api.Metadata.search(params: SearchParams, providerId?: string): Promise<SearchResults>
api.Metadata.fetchArtistBio(artistId: string, providerId?: string): Promise<ArtistBio>
api.Metadata.fetchArtistSocialStats(artistId: string, providerId?: string): Promise<ArtistSocialStats>
api.Metadata.fetchArtistAlbums(artistId: string, providerId?: string): Promise<AlbumRef[]>
api.Metadata.fetchArtistTopTracks(artistId: string, providerId?: string): Promise<TrackRef[]>
api.Metadata.fetchArtistPlaylists(artistId: string, providerId?: string): Promise<PlaylistRef[]>
api.Metadata.fetchArtistRelatedArtists(artistId: string, providerId?: string): Promise<ArtistRef[]>
api.Metadata.fetchAlbumDetails(albumId: string, providerId?: string): Promise<Album>
```

All methods accept an optional `providerId`. If omitted, Nuclear uses the active metadata provider (the one selected in the Sources view).

{% hint style="warning" %}
The `artistId` and `albumId` values are provider-specific. Always pass the `providerId` from the same `ProviderRef` that gave you the ID. Mixing IDs from one provider with a different `providerId` will produce errors or wrong results.
{% endhint %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.nuclearplayer.com/nuclear/plugins/metadata.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
