packages/docs/plugins/metadata.md
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 for a good reference implementation.
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:
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 tell Nuclear which methods your provider supports. Nuclear will never call a method you haven't declared a capability for.
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 %}
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[] |
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.
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:
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.
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.
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.
Every entity needs a source that ties it back to your provider. This is how Nuclear navigates between search results and detail pages.
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
};
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.
Plugins can also query the active metadata provider. Use api.Metadata.* to search and fetch details without knowing which provider is registered.
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);
}
},
};
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 %}