Skip to content

Library API ​

Methods for browsing and searching library tracks, albums, and artists, managing root directory indices, incremental sync, and caches.

library ​

library.addToPlaylist ​

Append tracks to a playlist by path. A path does not have to be in the library, and a file that does not exist is added all the same. A locked playlist fails with LOCKED and nothing is added.

ParameterTypeRequiredDescription
pathsstring[]YesPaths to add, in this order; a |subsong:N suffix selects a subsong. Must not be empty.
playlistintegerNoIndex of the target playlist; omitted, the active playlist. An index past the last playlist fails with INVALID_INDEX; with this omitted and no active playlist the call fails with NO_ACTIVE_ITEM. At least 0.
playlistGuidstringNoPersistent unique identifier of a playlist (bracketed GUID, case-insensitive), used in place of a mutable index. Mutually exclusive with playlist; passing both or an invalid format fails with INVALID_PARAMS. Fails with NOT_FOUND if the playlist does not exist, never falling back. Invalid-format and not-found failures return the given GUID in details.playlistGuid.

Returns

FieldTypeDescription
addedintegerTracks added.

success is true on success. On failure the response is { success: false, error, code }; see Error codes for code.

js
// Minimal call: append to the active playlist
const res = await fb2k.invoke('library.addToPlaylist', {
    paths: ['C:\\Music\\song.flac', 'C:\\Music\\other.mp3']
});
if (res.success === false) throw new Error(res.error);
const { added } = res;

// Target a specific playlist by index
await fb2k.invoke('library.addToPlaylist', {
    paths: ['C:\\Music\\song.flac'],
    playlist: 0
});

library.browseDirectory ​

List the library by path prefix: the folders one level below path and, with includeFiles, every track under it at any depth. The prefix is matched against the paths as foobar2000 stores them, so a local folder has to be written as file:// followed by its path; a plain absolute path matches nothing and succeeds with empty lists. library.browseTree walks the real library roots instead. Fails with LIBRARY_DISABLED when the library is disabled.

ParameterTypeRequiredDescription
pathstringNoPath prefix, compared with ASCII letters case-insensitive; empty matches every track. Default: "".
includeFilesbooleanNoAdd every track under path as files. On by default, so an empty path returns the whole library. Default: true.

Returns

FieldTypeDescription
directoriesstring[]Folders one level below path, as stored paths, in byte order.
itemsstring[]The same list as directories.
filesLibraryTrack[]Tracks under path in library order; index is the position in the library. Empty without includeFiles.
files[].…TrackEvery field of Track.
files[].indexintegerRow number; the list the row is in says what it counts.

success is true on success. On failure the response is { success: false, error, code }; see Error codes for code.

js
// Top-level directories only
const res = await fb2k.invoke('library.browseDirectory', {
    includeFiles: false
});
if (res.success === false) throw new Error(res.error);
const { directories } = res;

// Descend into one directory, including its tracks
const result = await fb2k.invoke('library.browseDirectory', {
    path: 'C:\\Music'
});

library.browseTree ​

One folder under a library root: its subfolders and, with includeFiles, its tracks. Reads the directory tree index library.getRoots uses, building it first when needed. An unknown rootId or pathId fails with NOT_FOUND; a failed index build fails with OPERATION_FAILED.

ParameterTypeRequiredDescription
rootIdstringYesid of a root from library.getRoots, compared case-insensitively. Must not be empty.
pathIdstringNopathId of the folder to open, as a directories entry reports it; empty opens the root. Folder names are separated by /, so a path written with \ is not found. Default: "".
includeFilesbooleanNoAdd the folder's tracks as files. Default: false.
recursiveFilesbooleanNoWith includeFiles, add the tracks of every folder below as well. Default: false.

Returns

FieldTypeDescription
rootLibraryRootInfoThe root.
root.idstringStable identifier of the root, currently its absolutePath; library.browseTree takes it.
root.displayNamestringThe folder name, or the full path when two roots share a name.
root.rawPathstringCurrently the same as absolutePath.
root.absolutePathstringCanonical local path of the folder.
root.trackCountintegerLibrary tracks under the folder.
pathIdstringThe pathId opened.
absolutePathstringLocal path of the folder.
directoriesLibraryDirectoryNodeInfo[]Subfolders directly inside, ordered by displayName and then absolutePath, ignoring case.
directories[].idstringrootId, then ::, then pathId.
directories[].rootIdstringThe root the folder is under.
directories[].pathIdstringPath below the root with / between folder names; pass it to library.browseTree to open the folder.
directories[].parentPathIdstringpathId of the parent folder; "" directly under the root.
directories[].namestringFolder name.
directories[].displayNamestringCurrently the same as name.
directories[].rawPathstringCurrently the same as absolutePath.
directories[].absolutePathstringLocal path of the folder.
directories[].relativePathstringCurrently the same as pathId.
directories[].depthintegerFolder names in pathId: 1 directly under the root.
directories[].trackCountintegerLibrary tracks in the folder and every folder below it.
directories[].childDirectoryCountintegerSubfolders directly inside.
directories[].hasChildrenbooleanWhether childDirectoryCount is above 0.
filesLibraryTrack[]The folder's tracks in library order, followed with recursiveFiles by those of the folders below in no fixed order; empty without includeFiles. index is the position in the library when the tree index was built.
files[].…TrackEvery field of Track.
files[].indexintegerRow number; the list the row is in says what it counts.
fromCachebooleantrue when the tree index already existed before this call.

success is true on success. On failure the response is { success: false, error, code }; see Error codes for code.

js
const res = await fb2k.invoke('library.getRoots');
if (res.success === false) throw new Error(res.error);
const { roots } = res;

// Minimal call: directory structure of a root, no files
const tree = await fb2k.invoke('library.browseTree', {
    rootId: roots[0].id
});
if (tree.success === false) throw new Error(tree.error);

// Descend into a subdirectory and include its tracks
const withFiles = await fb2k.invoke('library.browseTree', {
    rootId: roots[0].id,
    pathId: tree.directories[0].pathId,
    includeFiles: true
});

library.getAlbumTracks ​

The tracks of one library.getAlbums row, named by the row's name and albumArtist. Tracks are grouped exactly as library.getAlbums groups them, so total equals the row's trackCount. They are sorted by disc number, then track number, then library order. The grouping is kept until the library changes and library.getAlbums builds and reads the same one, so a call after it, or after an earlier call, does not read the library again. A name and album artist that no row has, and a disabled library, succeed with no tracks and no row. Pass the row's albumId as well when present; it takes precedence over the names. A malformed ID fails with INVALID_PARAMS and details.param albumId.

ParameterTypeRequiredDescription
albumIdstringNoThe row's opaque album identity. When provided, it takes precedence over both names; empty or malformed strings fail rather than falling back to a name lookup. As for other optional parameters, JSON null is treated as omitted.
albumstringYesThe row's name: the first album value of its tracks, compared byte for byte.
albumArtiststringYesThe row's albumArtist: the first album artist value of its tracks, or their first artist value when they have no album artist, or "" when they have neither. Compared byte for byte, so the row's artist is not a substitute.

Returns

FieldTypeDescription
albumstringThe album name, as given.
albumArtiststringThe album artist, as given.
rowAlbumInfoThe album's row as library.getAlbums returns it, without coverDataUrl and tracks; absent when no row has this name and album artist.
row.albumIdstringOpaque identity for an album whose original name or album artist is not valid UTF-8. Pass it to library.getAlbumTracks; display text alone cannot identify such an album. Absent for ordinary names and for the differently grouped library.getArtistAlbums rows.
row.namestringAlbum name.
row.artiststringalbumArtist when it is set, otherwise the first artist value found among the tracks.
row.albumArtiststringThe first album artist value found among the tracks, falling back to the track's artist; empty when neither exists.
row.trackCountintegerTracks counted into the row.
row.discCountintegerDistinct disc numbers among those tracks; 1 when none carries one.
row.durationnumberSummed length of those tracks, in seconds.
row.yearstringThe first date value found among the tracks; empty when none has one.
row.genrestringThe first genre value found among the tracks; empty when none has one.
row.labelstringThe first publisher value found among the tracks, or else the first label value; empty when none has either.
row.firstTrackPathstringPath of the first track counted, as foobar2000 stores it; it can be a file-relative:// URI.
row.firstTrackAbsolutePathstringfirstTrackPath as a native file path, the form to pass to artwork.getForTrack; absent when there is no first track path.
row.coverDataUrlstringFront cover as a data:image/... URL; only library.getAlbums with includeCover fills it, and only when a cover exists.
row.tracksAlbumTrackRef[]Tracks in track-number order. library.getAlbums fills it with includeTracks; library.getArtistAlbums fills it with the tracks credited to the requested artist in this group. Use these references to keep groups with equal display names apart.
row.tracks[].subsongintegerSubsong index; combine with path when addressing a track in a multi-track file.
row.tracks[].trackNumberintegerTrack number; 0 when the track has none.
row.tracks[].pathstringTrack path, as foobar2000 stores it.
row.tracks[].absolutePathstringpath as a native file path.
tracksLibraryTrack[]The tracks, sorted by disc number, then track number, then library order; index is the position in this list.
tracks[].…TrackEvery field of Track.
tracks[].indexintegerRow number; the list the row is in says what it counts.
itemsLibraryTrack[]The same list as tracks.
items[].…TrackEvery field of Track.
items[].indexintegerRow number; the list the row is in says what it counts.
totalintegerNumber of tracks.

success is true on success. On failure the response is { success: false, error, code }; see Error codes for code.

js
// Name the album by a library.getAlbums row
const page = await fb2k.invoke('library.getAlbums', { query: 'Abbey Road', limit: 1 });
if (page.success === false) throw new Error(page.error);
const album = page.albums[0];
if (album) {
    const res = await fb2k.invoke('library.getAlbumTracks', {
        album: album.name,
        albumArtist: album.albumArtist
    });
    if (res.success === false) throw new Error(res.error);
    console.log(res.total === album.trackCount); // true
}

A compilation without album artist tags is several rows in library.getAlbums, one per first artist value, and each row gets only its own tracks here. To get the tracks of one artist across albums, use library.getArtistTracks.

library.getAlbums ​

Albums of the whole library, grouped by album name plus album artist; tracks without an album tag are skipped. A complete list (offset 0, no includeTracks, every album within limit) is kept until the library changes, and a later call with the same query, sort and includeCover from offset 0 without includeTracks is answered from it. The grouping behind the list is kept the same way and shared with library.getAlbumTracks, so other calls do not read the library again either until it changes. With the library disabled it succeeds with no albums.

ParameterTypeRequiredDescription
sortstringNoname, artist (the album artist), year or trackCount; any other value sorts by name. Default: "name".
querystringNoKeep only albums whose name or artist contains this text, compared case-insensitively for ASCII letters; empty keeps every album. Default: "".
offsetintegerNoAlbums to skip. At least 0. Default: 0.
limitintegerNoMost albums to return. At least 0. Default: 100.
includeTracksbooleanNoAdd each album's tracks as tracks. Default: false.
includeCoverbooleanNoAdd each album's front cover as coverDataUrl, read from its first track. Default: false.
coverMaxSizeintegerNoLargest cover to inline, in KiB; a larger cover is left out. 0 or less inlines any size. Default: 500.
useCachebooleanNoAnswer from the kept list when there is one; false always scans the library, and the result is still kept. Default: true.

Returns

FieldTypeDescription
albumsAlbumInfo[]The albums of this page, sorted by sort.
albums[].albumIdstringOpaque identity for an album whose original name or album artist is not valid UTF-8. Pass it to library.getAlbumTracks; display text alone cannot identify such an album. Absent for ordinary names and for the differently grouped library.getArtistAlbums rows.
albums[].namestringAlbum name.
albums[].artiststringalbumArtist when it is set, otherwise the first artist value found among the tracks.
albums[].albumArtiststringThe first album artist value found among the tracks, falling back to the track's artist; empty when neither exists.
albums[].trackCountintegerTracks counted into the row.
albums[].discCountintegerDistinct disc numbers among those tracks; 1 when none carries one.
albums[].durationnumberSummed length of those tracks, in seconds.
albums[].yearstringThe first date value found among the tracks; empty when none has one.
albums[].genrestringThe first genre value found among the tracks; empty when none has one.
albums[].labelstringThe first publisher value found among the tracks, or else the first label value; empty when none has either.
albums[].firstTrackPathstringPath of the first track counted, as foobar2000 stores it; it can be a file-relative:// URI.
albums[].firstTrackAbsolutePathstringfirstTrackPath as a native file path, the form to pass to artwork.getForTrack; absent when there is no first track path.
albums[].coverDataUrlstringFront cover as a data:image/... URL; only library.getAlbums with includeCover fills it, and only when a cover exists.
albums[].tracksAlbumTrackRef[]Tracks in track-number order. library.getAlbums fills it with includeTracks; library.getArtistAlbums fills it with the tracks credited to the requested artist in this group. Use these references to keep groups with equal display names apart.
albums[].tracks[].subsongintegerSubsong index; combine with path when addressing a track in a multi-track file.
albums[].tracks[].trackNumberintegerTrack number; 0 when the track has none.
albums[].tracks[].pathstringTrack path, as foobar2000 stores it.
albums[].tracks[].absolutePathstringpath as a native file path.
totalintegerAlbums matching query, before offset and limit.
offsetintegerThe offset applied.
limitintegerThe limit applied.
hasMorebooleanWhether albums remain after this page.
includeCoverbooleanThe includeCover applied.
fromCachebooleantrue when the answer came from the kept list.

success is true on success. On failure the response is { success: false, error, code }; see Error codes for code.

Albums are grouped by album name plus the first value of album artist, falling back to the first value of artist when that tag is absent. In library.getAlbums, both artist and albumArtist reflect the aggregated group value: on an album without an album artist tag, both fields report the first credited artist of the first track seen rather than per-track credits; a multi-value album artist contributes only its first value to grouping. To retrieve albums associated with a contributing artist, use library.getArtistAlbums.

Performance Optimization

Setting includeCover: true retrieves album covers in batch along with album metadata, avoiding separate calls to artwork.getForTrack. Covers support JPEG, PNG, GIF, and WebP formats with automatic MIME detection.

js
// Minimal call: first 100 albums sorted by name
const res = await fb2k.invoke('library.getAlbums');
if (res.success === false) throw new Error(res.error);
const { albums, total, hasMore } = res;

// Second page, newest first, with cover thumbnails
const page2 = await fb2k.invoke('library.getAlbums', {
    limit: 50,
    offset: 100,
    sort: 'year',
    includeCover: true,
    coverMaxSize: 300
});

// Filter by album name or album artist
const filtered = await fb2k.invoke('library.getAlbums', { query: 'Beatles' });

library.getAll ​

Tracks of the whole library in library order, one page at a time. A request from offset 0 that covers every track is kept until the library changes, and a later call from offset 0 with useCache is answered from it. With asyncResult, such a request, when it is not answered from the kept list, is built off the main thread: the call answers { pending: true, requestId } at once and the page arrives as the library:getAllResult event on the calling window.

ParameterTypeRequiredDescription
offsetintegerNoTracks to skip. At least 0. Default: 0.
limitintegerNoMost tracks to return. At least 0. Default: 100.
useCachebooleanNoFrom offset 0, answer from the kept list when there is one. A request from offset 0 that covers every track is kept either way. Default: true.
asyncResultbooleanNoBuild a request from offset 0 that covers every track off the main thread and deliver it as the library:getAllResult event; takes effect only with useCache, and not when the kept list answers. Default: false.

Returns

FieldTypeDescription
pendingbooleantrue when the page will arrive as the library:getAllResult event; the page fields are then absent.
requestIdstringId the library:getAllResult event carries; present with pending.
tracksLibraryTrack[]The page in library order; index is the position in the library.
tracks[].…TrackEvery field of Track.
tracks[].indexintegerRow number; the list the row is in says what it counts.
itemsLibraryTrack[]The same list as tracks.
items[].…TrackEvery field of Track.
items[].indexintegerRow number; the list the row is in says what it counts.
totalintegerTracks in the library.
offsetintegerThe offset applied.
limitintegerThe limit applied.
fromCachebooleantrue when the page came from the kept list.

success is true on success. On failure the response is { success: false, error, code }; see Error codes for code.

artists provides the array of individual strings behind artist, satisfying artists.join(', ') === artist. Multi-value tags can be fully recovered from artists. It is included on every shared Track row (media-library track APIs, playlist.getTracks, playback.getCurrentTrack, queue.get, and track events) and on the flat response of library.getByPath; artwork payloads do not carry it. albumArtists does the same for albumArtist wherever a Track row is returned: its first element (falling back to artists[0] when empty) represents the album artist used by library.getAlbums for grouping.

js
// Minimal call: first 100 tracks
const res = await fb2k.invoke('library.getAll');
if (res.success === false) throw new Error(res.error);
const { items, total } = res;

// Explicit page
const page2 = await fb2k.invoke('library.getAll', { limit: 50, offset: 100 });

library.getArtistAlbums ​

The albums an artist appears on. trackCount, duration and discCount count only the tracks of this artist, so an album the artist appears on only partly reports smaller figures than in library.getAlbums. Rows are grouped by album name alone; tracks without an album tag fall under (Unknown Album). Fails with LIBRARY_DISABLED when the library is disabled.

ParameterTypeRequiredDescription
artiststringYesArtist name, compared byte for byte against each value of the artist tag, so a name from library.getArtists matches even when it is not the first of several credited artists. Must not be empty.
limitintegerNoMost albums to return, applied after grouping; there is no offset. At least 0. Default: 100.
sortstringNoname, artist, year or trackCount, as in library.getAlbums; any other value sorts by name. Default: "name".
match"exact" | "substring"Noexact needs a whole tag value to equal artist; substring matches a tag value that contains it, and then a lowercase letter in artist matches either case while an uppercase one matches only uppercase. Default: "exact".

Returns

FieldTypeDescription
artiststringThe artist, as given.
albumsAlbumInfo[]The albums, sorted by sort, with tracks referencing this artist's members of each group. Rows do not carry coverDataUrl or a canonical albumId.
albums[].albumIdstringOpaque identity for an album whose original name or album artist is not valid UTF-8. Pass it to library.getAlbumTracks; display text alone cannot identify such an album. Absent for ordinary names and for the differently grouped library.getArtistAlbums rows.
albums[].namestringAlbum name.
albums[].artiststringalbumArtist when it is set, otherwise the first artist value found among the tracks.
albums[].albumArtiststringThe first album artist value found among the tracks, falling back to the track's artist; empty when neither exists.
albums[].trackCountintegerTracks counted into the row.
albums[].discCountintegerDistinct disc numbers among those tracks; 1 when none carries one.
albums[].durationnumberSummed length of those tracks, in seconds.
albums[].yearstringThe first date value found among the tracks; empty when none has one.
albums[].genrestringThe first genre value found among the tracks; empty when none has one.
albums[].labelstringThe first publisher value found among the tracks, or else the first label value; empty when none has either.
albums[].firstTrackPathstringPath of the first track counted, as foobar2000 stores it; it can be a file-relative:// URI.
albums[].firstTrackAbsolutePathstringfirstTrackPath as a native file path, the form to pass to artwork.getForTrack; absent when there is no first track path.
albums[].coverDataUrlstringFront cover as a data:image/... URL; only library.getAlbums with includeCover fills it, and only when a cover exists.
albums[].tracksAlbumTrackRef[]Tracks in track-number order. library.getAlbums fills it with includeTracks; library.getArtistAlbums fills it with the tracks credited to the requested artist in this group. Use these references to keep groups with equal display names apart.
albums[].tracks[].subsongintegerSubsong index; combine with path when addressing a track in a multi-track file.
albums[].tracks[].trackNumberintegerTrack number; 0 when the track has none.
albums[].tracks[].pathstringTrack path, as foobar2000 stores it.
albums[].tracks[].absolutePathstringpath as a native file path.
totalintegerAlbums found, before limit.
hasMorebooleanWhether limit cut the list short.

success is true on success. On failure the response is { success: false, error, code }; see Error codes for code.

Each row carries name, artist, albumArtist, trackCount, discCount, duration, year, genre, label, firstTrackPath, firstTrackAbsolutePath and tracks: the keys of a library.getAlbums row except coverDataUrl and the canonical albumId. tracks lists the tracks credited to this artist in the group. For cover art, pass firstTrackAbsolutePath to artwork.getForTrack; firstTrackPath can be a file-relative:// URI, which that method rejects. A failure reported by the method itself (library disabled, failed search) still includes an empty albums; the response to a parameter error has no albums.

trackCount, duration and discCount count only the tracks this artist appears on, not the whole album. If the artist appears on only part of an album, values reported here are smaller than the complete album metrics from library.getAlbums. To render complete album cards, query library.getAlbums instead.

total reflects total matching albums before limit truncation. This endpoint does not support offset pagination; when hasMore is true, retrieve remaining items by increasing limit.

A name from library.getArtists matches here even when it is not the first of several credited artists, because the host compares each atomic tag value. The joined artist string on a track object is not a valid key — pass one artist name. Under substring, a short name also returns albums by other artists whose name contains it, and its case rule is asymmetric: a lowercase letter in the query matches either case in the tag, while an uppercase letter demands uppercase in the same position — camellia finds Camellia, CAMELLIA does not. exact is free of this.

Rows are grouped by album name alone, so identically titled albums by different artists collapse into one row. Tracks whose album tag is missing are grouped under (Unknown Album); a tag present but empty forms its own group with an empty name. library.getAlbums differs on both counts: it keys on album plus album artist, and skips a track whose album is missing or empty.

js
const res = await fb2k.invoke('library.getArtistAlbums', {
    artist: 'The Beatles'
});
if (res.success === false) throw new Error(res.error);
const { albums } = res;

// Discography newest first, capped at 20 albums
const recent = await fb2k.invoke('library.getArtistAlbums', {
    artist: 'The Beatles',
    sort: 'year',
    limit: 20
});

library.getArtistTracks ​

Tracks an artist is credited on, in library order. artist is matched as library.getArtistAlbums matches it under match: 'exact'. An empty artist and a disabled library both succeed with no tracks.

ParameterTypeRequiredDescription
artiststringNoArtist name, compared byte for byte against each value of the artist tag; empty, the call succeeds with no tracks. Default: "".
limitintegerNoMost tracks to return; the first ones in library order are kept. At least 0. Default: 500.

Returns

FieldTypeDescription
artiststringThe artist, as given.
tracksLibraryTrack[]The tracks in library order; index is the position in this list.
tracks[].…TrackEvery field of Track.
tracks[].indexintegerRow number; the list the row is in says what it counts.
itemsLibraryTrack[]The same list as tracks.
items[].…TrackEvery field of Track.
items[].indexintegerRow number; the list the row is in says what it counts.
totalintegerThe same as count; there is no count before limit, so a full page means there may be more.
countintegerTracks returned.

success is true on success. On failure the response is { success: false, error, code }; see Error codes for code.

js
const res = await fb2k.invoke('library.getArtistTracks', {
    artist: 'The Beatles'
});
if (res.success === false) throw new Error(res.error);
const { items } = res;

library.getArtists ​

Every credited artist with participation counts: each value of a multi-value artist gets its own row. The scan is kept until the library changes. Fails with LIBRARY_DISABLED when the library is disabled.

ParameterTypeRequiredDescription
sortstringNoname, trackCount or albumCount (both largest first); any other value keeps name order. Default: "name".
limitintegerNoMost artists to return. At least 0. Default: 1000.
includeAlbumsbooleanNoAdd each artist's albums as albums. Default: false.

Returns

FieldTypeDescription
itemsArtistInfo[]The artists, sorted by sort.
items[].namestringThe artist.
items[].albumCountintegerDistinct album names among the artist's tracks.
items[].trackCountintegerTracks the artist is credited on.
items[].totalDurationnumberSummed length of those tracks, in seconds.
items[].albumsArtistAlbumRef[]The albums the artist is credited on, sorted by name and then artist; present only with includeAlbums, and never cut by limit.
items[].albums[].albumIdstringThe album row's opaque identity when its original grouping fields contain invalid UTF-8.
items[].albums[].namestringAlbum name.
items[].albums[].artiststringThe first album artist value, or the first artist value when there is none.
countintegerRows returned, after limit; there is no total, so a full page means there may be more.

success is true on success. On failure the response is { success: false, error, code }; see Error codes for code.

Each items entry is { name, albumCount, trackCount, totalDuration }, plus albums: [{ name, artist }] when includeAlbums is true.

Every credited artist gets its own entry, so a track tagged with several artists is counted under each of them. trackCount is a participation count and the entries add up to more than the total number of tracks; albumCount and totalDuration are counted per artist the same way.

An entry name is a single atomic value and can be passed straight to library.getArtistAlbums or library.getArtistTracks. The joined artist string on a track object cannot: the host compares each atomic value, so on a multi-value track the joined string matches nothing. On a single-value track it happens to equal the atomic value, which makes it look usable — do not rely on it.

albums gives the "artist → albums" mapping for the whole library in the one scan this endpoint already performs, where calling library.getArtistAlbums per artist would scan the library once per artist. Each element's (name, artist) is the identity library.getAlbums groups by — artist is the first album artist value, falling back to the first artist value — so it matches one library.getAlbums row when no albumId is present. If an ID is present, match by it instead of the display names. The matched row's firstTrackAbsolutePath leads to the cover. A track without an album tag contributes nothing here, as in library.getAlbums. Elements are de-duplicated by the original (name, artist) bytes and sorted by name, then artist, in byte order. albumCount keeps its own rule and de-duplicates by album name only: two same-named albums by different album artists count as 1 in albumCount and appear as 2 elements in albums. Without includeAlbums the response is unchanged from earlier releases — the key is not present.

js
// Minimal call: up to 1000 artists sorted by name
const res = await fb2k.invoke('library.getArtists');
if (res.success === false) throw new Error(res.error);
const { items } = res;

// Top 50 artists by track count
const top = await fb2k.invoke('library.getArtists', {
    limit: 50,
    sort: 'trackCount'
});

// Artist -> albums mapping in one call (raise limit above the artist count)
const res2 = await fb2k.invoke('library.getArtists', {
    includeAlbums: true,
    limit: 100000
});
if (res2.success === false) throw new Error(res2.error);
const { items: credited } = res2;
for (const artist of credited) {
    for (const album of artist.albums) {
        // album.name / album.artist pair up with a library.getAlbums row
    }
}

library.getByPath ​

Look up one file in the library and return its main fields as a flat object. A file that is not in the library succeeds with found: false.

ParameterTypeRequiredDescription
pathstringYesFile path. It is looked up as the file's first subsong; a |subsong:N suffix is not recognized. Must not be empty.

Returns

FieldTypeDescription
foundbooleanWhether the file is in the library; the other fields besides path are present only when it is.
pathstringThe track path as foobar2000 stores it when found; otherwise the path as given.
absolutePathstringThe track path as a native file path.
titlestringThe first title value; empty when absent.
artiststringEvery artist value joined with , in their original order.
artistsstring[]Every artist value; artists.join(', ') equals artist.
albumstringThe first album value; empty when absent.
durationnumberLength in seconds.
trackNumberstringThe first tracknumber value as written in the tag, such as 2 or 02/12; empty when absent.
genrestringEvery genre value joined with , .
datestringThe first date value; empty when absent.

success is true on success. On failure the response is { success: false, error, code }; see Error codes for code.

Multi-value tags in artist / albumArtist / genre / composer (only the fields this API actually returns) are joined with , in their original order, without de-duplication.

artists provides the array of individual strings behind artist, satisfying artists.join(', ') === artist. Multi-value tags can be fully recovered from artists. It is included on every shared Track row (media-library track APIs, playlist.getTracks, playback.getCurrentTrack, queue.get, and track events) and on the flat response of library.getByPath; artwork payloads do not carry it. albumArtists does the same for albumArtist wherever a Track row is returned: its first element (falling back to artists[0] when empty) represents the album artist used by library.getAlbums for grouping. This API is a flat object, so artists is the only array key it gains.

js
const res = await fb2k.invoke('library.getByPath', {
    path: 'C:\\Music\\song.flac'
});
if (res.success === false) throw new Error(res.error);
const { found, title } = res;

library.getCacheStats ​

Counters of the host's library caches and of the directory tree index, for diagnostics.

This method takes no parameters.

Returns

FieldTypeDescription
validbooleanWhether any library result was kept since the cache was last dropped.
lastModifiedintegerWhen the cache was last dropped, in milliseconds since the Unix epoch.
albumsCacheEntriesintegerKept library.getAlbums lists, one per query, sort and includeCover.
tracksCachedbooleanWhether a full library.getAll result is kept.
artistsCachedbooleanWhether the library.getArtists scan is kept.
genresCachedbooleanAlways false; genres are not kept.
statsCachedbooleanWhether the library.getStatus answer is kept.
coversCachedintegerAlways 0; covers are not kept.
coverCacheBytesintegerAlways 0; covers are not kept.
coverCacheMBnumberAlways 0; covers are not kept.
cacheHitsintegerLookups answered from a kept result since the host started.
cacheMissesintegerLookups that found nothing kept since the host started.
treeIndexValidbooleanWhether the directory tree index is built.
rootsCachedintegerRoots in the tree index; 0 when it is not built.
treeIndexedTracksintegerTracks placed in the tree index at its last build.
treeSkippedTracksintegerTracks left out of the tree index at its last build.
treeLastBuiltintegerWhen the tree index was last built, in milliseconds since the Unix epoch; 0 before the first build.

success is true on success. On failure the response is { success: false, error, code }; see Error codes for code.

js
const result = await fb2k.invoke('library.getCacheStats');

library.getChanges ​

The library revision and tracks changed after revision since, for local incremental sync. The revision persists across restarts. When epoch matches and since is within the recorded range, returns changed and removed. Otherwise sets resync: true with full handles for the page to rebuild its copy. Changes reported after startup loading advance the revision, including play statistics from components like foo_playcount. Fails with OPERATION_PENDING (details.reason: 'libraryLoading') while the library is still loading. When disabled, returns resync: true with empty handles.

ParameterTypeRequiredDescription
epochstringNoThe epoch the page's copy was built under; omitted, the answer is a resync.
sinceintegerNoThe revision the page's copy is at; omitted, the answer is a resync. At least 0.

Returns

FieldTypeDescription
epochstringChanges when the host starts its record over, for example after an abnormal shutdown; copies from another epoch must be rebuilt.
revisionintegerThe revision this response brings the copy to; pass as since next time.
resyncbooleanWhether the page must rebuild its copy from handles.
changedstring[]Tracks added or modified after since that are still in the library; fetch via library.getTracks. Absent when resync is true.
removedstring[]Tracks removed from the library after since. Absent when resync is true.
handlesstring[]All tracks currently in the library. Absent when resync is false.

success is true on success. On failure the response is { success: false, error, code }; see Error codes for code.

See Keeping a local copy for how the two methods fit together.

library.getCount ​

Number of tracks in the library.

This method takes no parameters.

Returns

FieldTypeDescription
countintegerTracks in the library.

success is true on success. On failure the response is { success: false, error, code }; see Error codes for code.

js
const result = await fb2k.invoke('library.getCount');

library.getFieldValues ​

Every distinct value of one tag across the library with its track count, most used first. Fails with LIBRARY_DISABLED when the library is disabled.

ParameterTypeRequiredDescription
fieldstringYesTag name, such as genre. Must not be empty.
separatorstringNoSplits each tag value into several values at this string, trimming spaces and tabs around each; empty, values are taken whole. Default: "".
limitintegerNoMost values to return. At least 0. Default: 5000.

Returns

FieldTypeDescription
valuesLibraryValueCount[]The values, most tracks first.
values[].namestringThe value.
values[].trackCountintegerTracks carrying the value.
totalintegerDistinct values found, before limit.
fieldstringThe tag name, as given.

success is true on success. On failure the response is { success: false, error, code }; see Error codes for code.

js
// Values are returned sorted by descending trackCount
const res = await fb2k.invoke('library.getFieldValues', { field: 'genre' });
if (res.success === false) throw new Error(res.error);
const { values } = res;

// Split multi-value fields and cap the result
const artists = await fb2k.invoke('library.getFieldValues', {
    field: 'artist',
    separator: ';',
    limit: 50
});

library.getGenres ​

Every genre in the library with its track count, ordered by name. Every value of a multi-value genre gets its own entry, and a track tagged with several genres is counted under each. Fails with LIBRARY_DISABLED when the library is disabled.

This method takes no parameters.

Returns

FieldTypeDescription
genresLibraryValueCount[]Every genre with its track count.
genres[].namestringThe value.
genres[].trackCountintegerTracks carrying the value.

success is true on success. On failure the response is { success: false, error, code }; see Error codes for code.

js
const result = await fb2k.invoke('library.getGenres');

library.getRandomTracks ​

Tracks drawn at random from the whole library without repeats, a new draw on every call. A disabled or empty library succeeds with no tracks.

ParameterTypeRequiredDescription
countintegerNoTracks to draw; at most the library size is returned. At least 0. Default: 10.

Returns

FieldTypeDescription
tracksLibraryTrack[]The drawn tracks; index is the position in this list.
tracks[].…TrackEvery field of Track.
tracks[].indexintegerRow number; the list the row is in says what it counts.
countintegerTracks returned.

success is true on success. On failure the response is { success: false, error, code }; see Error codes for code.

js
const res = await fb2k.invoke('library.getRandomTracks', { count: 50 });
if (res.success === false) throw new Error(res.error);
const { tracks } = res;

library.getRecentlyAdded ​

The newest tracks of the library. added orders by the %added% field of foo_playcount; when no track has it, the call orders by file modification time instead and says so in fallback. The library is walked on every call.

ParameterTypeRequiredDescription
limitintegerNoMost tracks to return. At least 0. Default: 50.
sortBy"added" | "modified"Noadded orders by %added%, newest first, with tracks that lack it last; modified orders by file modification time, newest first. Default: "added".

Returns

FieldTypeDescription
tracksRecentLibraryTrack[]The tracks, newest first; index is the position in the library.
tracks[].…TrackEvery field of Track.
tracks[].indexintegerRow number; the list the row is in says what it counts.
tracks[].addedstringThe %added% value as foo_playcount formats it, such as 2024-05-01 12:34:56; present only when the list is ordered by it and the track has one.
tracks[].modifiedintegerFile modification time, in seconds since the Unix epoch; present only when the list is ordered by it and the time is known.
totalintegerTracks in the library, not the number returned.
limitintegerThe limit applied.
sortBy"added" | "modified"The order used: modified when added was asked for and no track has %added%.
fallbackbooleanWhether the call fell back from added to modified.

success is true on success. On failure the response is { success: false, error, code }; see Error codes for code.

js
// Minimal call: 50 most recently added tracks
const res = await fb2k.invoke('library.getRecentlyAdded');
if (res.success === false) throw new Error(res.error);
const { tracks, fallback } = res;

// Sort by file modification time instead
const byMtime = await fb2k.invoke('library.getRecentlyAdded', {
    limit: 50,
    sortBy: 'modified'
});

library.getRoots ​

The library's root folders. Only tracks that resolve to a stable local path count toward the roots; http://, file-relative://, unpack://, archive:// and similar ones are counted in skippedTracks. The first call builds the index synchronously and later calls reuse it until the library changes or library.invalidateCache is called. With the library disabled it succeeds with enabled: false and no roots; a failed build fails with OPERATION_FAILED.

This method takes no parameters.

Returns

FieldTypeDescription
enabledbooleanWhether the media library is enabled.
rootsLibraryRootInfo[]The root folders.
roots[].idstringStable identifier of the root, currently its absolutePath; library.browseTree takes it.
roots[].displayNamestringThe folder name, or the full path when two roots share a name.
roots[].rawPathstringCurrently the same as absolutePath.
roots[].absolutePathstringCanonical local path of the folder.
roots[].trackCountintegerLibrary tracks under the folder.
totalintegerNumber of roots.
indexedTracksintegerTracks placed under a root.
skippedTracksintegerTracks left out because they have no stable local path.
fromCachebooleantrue when the index already existed before this call.

success is true on success. On failure the response is { success: false, error, code }; see Error codes for code.

js
const result = await fb2k.invoke('library.getRoots');

library.getStats ​

Aggregate counts over the whole library. With the library disabled every count is 0. The library is walked on every call.

This method takes no parameters.

Returns

FieldTypeDescription
totalTracksintegerTracks in the library.
totalAlbumsintegerAlbums, counted by album name plus album artist, as library.getAlbums groups them.
totalArtistsintegerCredited artists: every value of a multi-value artist counts, matching the entry count of library.getArtists.
totalDurationnumberSummed length of all tracks, in seconds.
totalSizeintegerSummed file size, in bytes.
cacheValidbooleanWhether the host holds cached library results written since the cache was last dropped.
lastModifiedintegerWhen the cache was last dropped, in milliseconds since the Unix epoch.

success is true on success. On failure the response is { success: false, error, code }; see Error codes for code.

totalArtists counts credited artists — a track tagged with several artists contributes to each of them — so it matches the entry count of library.getArtists.

js
const result = await fb2k.invoke('library.getStats');

library.getStatus ​

Whether the library is enabled and how many tracks it holds. The answer is kept until the library changes; with the library disabled nothing is kept.

This method takes no parameters.

Returns

FieldTypeDescription
enabledbooleanWhether the media library is enabled.
initializedbooleanWhether foobar2000 has finished loading the library at startup. Prior to completion, library:itemsAdded announces loaded tracks rather than newly added ones.
scanningbooleanAlways false; the host does not report scanning.
itemCountintegerTracks in the library; 0 when it is disabled.
countintegerThe same as itemCount.

success is true on success. On failure the response is { success: false, error, code }; see Error codes for code.

js
const result = await fb2k.invoke('library.getStatus');

library.getTracks ​

Fetch library rows in the given handle order without reading media files. Missing handles (or all handles when disabled) are reported in missing. More than 5000 handles fail with INVALID_PARAMS. Rows are serialized off the main thread.

ParameterTypeRequiredDescription
handlesstring[]YesTrack handles to look up, as given by a track row's handle; at most 5000.
fieldsstring[]NoKeys to include in each row, as in library.query. Must not be empty.

Returns

FieldTypeDescription
tracksLibraryTrackPartial[]Found rows in the order of handles; index is the position in this list. Contains requested fields; albumId omitted when unavailable.
tracks[].handlestringCross-endpoint unique track identifier: formatted as absolutePath, appended with |subsong:N when subsong is non-zero. The same track yields the same handle from every method. Differs from standard filesystem paths; endpoints document whether handle identifiers are accepted in place of paths.
tracks[].pathstringPath as foobar2000 stores it: file:// for a local file, file-relative:// for a path stored relative to the foobar2000 folder (a portable install), or a remote URL. No subsong suffix.
tracks[].absolutePathstringNative filesystem path without the subsong suffix; the same as path for a remote URL.
tracks[].subsongintegerSubsong identifier the decoder assigns inside the file, not necessarily a sequence number; 0 for a whole file and for a remote stream.
tracks[].titlestringFirst TITLE value; empty when untagged.
tracks[].artiststringEvery ARTIST value joined with ", "; empty when untagged.
tracks[].artistsstring[]Every ARTIST value in tag order; empty when untagged.
tracks[].albumstringFirst ALBUM value; empty when untagged.
tracks[].albumIdstringOpaque album identity when its original name or album artist contains invalid UTF-8. Use this instead of the display names to match an album row; absent for ordinary names.
tracks[].albumArtiststringEvery ALBUM ARTIST value joined with ", "; empty when untagged.
tracks[].albumArtistsstring[]All ALBUM ARTIST values in tag order (albumArtists.join(", ") equals albumArtist); empty array when untagged. In library.getAlbums, a track that has an album is filed under the name album and the album artist albumArtists[0] (falling back to artists[0], or "" when both are empty); those are the name and albumArtist of that album's row. When albumId is present, match by albumId instead of display names to avoid collision from character replacement.
tracks[].genrestringEvery GENRE value joined with ", "; empty when untagged.
tracks[].datestringFirst DATE value as tagged, such as 2019 or 2019-05-01; empty when untagged.
tracks[].trackNumberintegerTRACKNUMBER read as an integer; 0 when absent or not a number.
tracks[].discNumberintegerDISCNUMBER read as an integer; 0 when absent or not a number.
tracks[].durationnumberLength in seconds; 0 when unknown.
tracks[].fileSizeintegerFile size in bytes; -1 when unknown, as for a remote stream.
tracks[].bitrateintegerAverage bitrate in kbit/s; 0 when unknown.
tracks[].sampleRateintegerSample rate in Hz; 0 when unknown.
tracks[].channelsintegerChannel count; 0 when unknown.
tracks[].codecstringCodec name as the decoder reports it, such as FLAC or MP3; empty when unknown.
tracks[].ratingintegerRating value from 0 to 5. Prefers the %rating% statistic (foo_playcount) if within 1..5; otherwise falls back to the RATING tag clamped to 0..5; 0 if neither is present.
tracks[].indexintegerRow number; the list the row is in says what it counts.
missingstring[]Handles not found in the library, in the order given.

success is true on success. On failure the response is { success: false, error, code }; see Error codes for code.

js
const res = await fb2k.invoke('library.getTracks', {
    handles: changed,
    fields: ['handle', 'title', 'artist', 'album', 'albumId', 'trackNumber', 'duration']
});
if (res.success === false) throw new Error(res.error);
const { tracks, missing } = res;

library.invalidateCache ​

Drop the host's cached library results and the directory tree index; the next call to an endpoint that uses them rebuilds them. They are also dropped whenever the library changes.

This method takes no parameters.

Returns

FieldTypeDescription
timestampintegerWhen the cache was dropped, in milliseconds since the Unix epoch.

success is true on success. On failure the response is { success: false, error, code }; see Error codes for code.

js
const result = await fb2k.invoke('library.invalidateCache');

library.isEnabled ​

Whether the foobar2000 media library is enabled, that is, whether any library folder is configured.

This method takes no parameters.

Returns

FieldTypeDescription
enabledbooleanWhether the media library is enabled.

success is true on success. On failure the response is { success: false, error, code }; see Error codes for code.

js
const result = await fb2k.invoke('library.isEnabled');

library.query ​

Tracks matching a foobar2000 query, optionally sorted by sort, up to limit. A query the parser rejects fails with INVALID_PARAMS and details.param query, and so does one carrying SORT BY; many malformed queries are not rejected but simply match nothing. The rows are written off the main thread. Fails with LIBRARY_DISABLED when the library is disabled.

ParameterTypeRequiredDescription
querystringYesfoobar2000 query expression. Must not be empty.
sortstringNoTitle Formatting expression to sort the matches by before limit applies; empty keeps library order, and an expression that fails to compile is ignored. Default: "".
limitintegerNoMost rows to return; total still counts every match. At least 0. Default: 100.
fieldsstring[]NoKeys each row carries; omitted, every key of a library track row. Names are matched case-sensitively against those keys: index, handle, title, artist, artists, album, albumId, albumArtist, albumArtists, genre, date, trackNumber, discNumber, duration, path, absolutePath, fileSize, bitrate, sampleRate, channels, codec, subsong and rating. An unknown name fails with INVALID_PARAMS and is listed in details.unknownFields. The optional albumId is omitted when the track has none, even when requested. Must not be empty.

Returns

FieldTypeDescription
tracksLibraryTrackPartial[]The matches, sorted by sort or in library order; index is the position in this list. Each row carries the fields asked for; albumId is omitted when unavailable.
tracks[].handlestringCross-endpoint unique track identifier: formatted as absolutePath, appended with |subsong:N when subsong is non-zero. The same track yields the same handle from every method. Differs from standard filesystem paths; endpoints document whether handle identifiers are accepted in place of paths.
tracks[].pathstringPath as foobar2000 stores it: file:// for a local file, file-relative:// for a path stored relative to the foobar2000 folder (a portable install), or a remote URL. No subsong suffix.
tracks[].absolutePathstringNative filesystem path without the subsong suffix; the same as path for a remote URL.
tracks[].subsongintegerSubsong identifier the decoder assigns inside the file, not necessarily a sequence number; 0 for a whole file and for a remote stream.
tracks[].titlestringFirst TITLE value; empty when untagged.
tracks[].artiststringEvery ARTIST value joined with ", "; empty when untagged.
tracks[].artistsstring[]Every ARTIST value in tag order; empty when untagged.
tracks[].albumstringFirst ALBUM value; empty when untagged.
tracks[].albumIdstringOpaque album identity when its original name or album artist contains invalid UTF-8. Use this instead of the display names to match an album row; absent for ordinary names.
tracks[].albumArtiststringEvery ALBUM ARTIST value joined with ", "; empty when untagged.
tracks[].albumArtistsstring[]All ALBUM ARTIST values in tag order (albumArtists.join(", ") equals albumArtist); empty array when untagged. In library.getAlbums, a track that has an album is filed under the name album and the album artist albumArtists[0] (falling back to artists[0], or "" when both are empty); those are the name and albumArtist of that album's row. When albumId is present, match by albumId instead of display names to avoid collision from character replacement.
tracks[].genrestringEvery GENRE value joined with ", "; empty when untagged.
tracks[].datestringFirst DATE value as tagged, such as 2019 or 2019-05-01; empty when untagged.
tracks[].trackNumberintegerTRACKNUMBER read as an integer; 0 when absent or not a number.
tracks[].discNumberintegerDISCNUMBER read as an integer; 0 when absent or not a number.
tracks[].durationnumberLength in seconds; 0 when unknown.
tracks[].fileSizeintegerFile size in bytes; -1 when unknown, as for a remote stream.
tracks[].bitrateintegerAverage bitrate in kbit/s; 0 when unknown.
tracks[].sampleRateintegerSample rate in Hz; 0 when unknown.
tracks[].channelsintegerChannel count; 0 when unknown.
tracks[].codecstringCodec name as the decoder reports it, such as FLAC or MP3; empty when unknown.
tracks[].ratingintegerRating value from 0 to 5. Prefers the %rating% statistic (foo_playcount) if within 1..5; otherwise falls back to the RATING tag clamped to 0..5; 0 if neither is present.
tracks[].indexintegerRow number; the list the row is in says what it counts.
totalintegerMatches, before limit.

success is true on success. On failure the response is { success: false, error, code }; see Error codes for code.

js
// Minimal call
const res = await fb2k.invoke('library.query', {
    query: '%rating% GREATER 3'
});
if (res.success === false) throw new Error(res.error);
const { tracks } = res;

// Sort matches with a titleformat pattern
const sorted = await fb2k.invoke('library.query', {
    query: 'artist HAS Beatles',
    sort: '%album% - %tracknumber%',
    limit: 50
});

// Project a single key: rows come back as { absolutePath } only
const paths = await fb2k.invoke('library.query', {
    query: '%codec% IS FLAC',
    limit: 100000,
    fields: ['absolutePath']
});

library.refresh ​

Same as library.rescan.

This method takes no parameters.

Returns

success is true on success. On failure the response is { success: false, error, code }; see Error codes for code.

js
const result = await fb2k.invoke('library.refresh');

library.rescan ​

Ask foobar2000 to rescan the library folders by calling library_manager::rescan(), which the foobar2000 SDK marks as obsolete and not to be called. The library follows file changes on its own.

This method takes no parameters.

Returns

success is true on success. On failure the response is { success: false, error, code }; see Error codes for code.

js
const result = await fb2k.invoke('library.rescan');

One page of the tracks matching a foobar2000 query, in library order: a SORT BY clause is accepted but not applied. A query the parser rejects fails with INVALID_PARAMS and details.param query. An empty query and a disabled library both succeed with no tracks. The rows are written off the main thread.

ParameterTypeRequiredDescription
querystringNofoobar2000 query expression; empty, the call succeeds with no tracks. Default: "".
offsetintegerNoMatches to skip. At least 0. Default: 0.
limitintegerNoMost rows to return. At least 0. Default: 100.
fieldsstring[]NoKeys each row carries, as for library.query. Must not be empty.

Returns

FieldTypeDescription
tracksLibraryTrackPartial[]This page of the matches in library order; index is the position among all matches. Each row carries the fields asked for; albumId is omitted when unavailable.
tracks[].handlestringCross-endpoint unique track identifier: formatted as absolutePath, appended with |subsong:N when subsong is non-zero. The same track yields the same handle from every method. Differs from standard filesystem paths; endpoints document whether handle identifiers are accepted in place of paths.
tracks[].pathstringPath as foobar2000 stores it: file:// for a local file, file-relative:// for a path stored relative to the foobar2000 folder (a portable install), or a remote URL. No subsong suffix.
tracks[].absolutePathstringNative filesystem path without the subsong suffix; the same as path for a remote URL.
tracks[].subsongintegerSubsong identifier the decoder assigns inside the file, not necessarily a sequence number; 0 for a whole file and for a remote stream.
tracks[].titlestringFirst TITLE value; empty when untagged.
tracks[].artiststringEvery ARTIST value joined with ", "; empty when untagged.
tracks[].artistsstring[]Every ARTIST value in tag order; empty when untagged.
tracks[].albumstringFirst ALBUM value; empty when untagged.
tracks[].albumIdstringOpaque album identity when its original name or album artist contains invalid UTF-8. Use this instead of the display names to match an album row; absent for ordinary names.
tracks[].albumArtiststringEvery ALBUM ARTIST value joined with ", "; empty when untagged.
tracks[].albumArtistsstring[]All ALBUM ARTIST values in tag order (albumArtists.join(", ") equals albumArtist); empty array when untagged. In library.getAlbums, a track that has an album is filed under the name album and the album artist albumArtists[0] (falling back to artists[0], or "" when both are empty); those are the name and albumArtist of that album's row. When albumId is present, match by albumId instead of display names to avoid collision from character replacement.
tracks[].genrestringEvery GENRE value joined with ", "; empty when untagged.
tracks[].datestringFirst DATE value as tagged, such as 2019 or 2019-05-01; empty when untagged.
tracks[].trackNumberintegerTRACKNUMBER read as an integer; 0 when absent or not a number.
tracks[].discNumberintegerDISCNUMBER read as an integer; 0 when absent or not a number.
tracks[].durationnumberLength in seconds; 0 when unknown.
tracks[].fileSizeintegerFile size in bytes; -1 when unknown, as for a remote stream.
tracks[].bitrateintegerAverage bitrate in kbit/s; 0 when unknown.
tracks[].sampleRateintegerSample rate in Hz; 0 when unknown.
tracks[].channelsintegerChannel count; 0 when unknown.
tracks[].codecstringCodec name as the decoder reports it, such as FLAC or MP3; empty when unknown.
tracks[].ratingintegerRating value from 0 to 5. Prefers the %rating% statistic (foo_playcount) if within 1..5; otherwise falls back to the RATING tag clamped to 0..5; 0 if neither is present.
tracks[].indexintegerRow number; the list the row is in says what it counts.
totalintegerMatches, before offset and limit.
offsetintegerThe offset applied.
limitintegerThe limit applied.
hasMorebooleanWhether matches remain after this page.

success is true on success. On failure the response is { success: false, error, code }; see Error codes for code.

js
// Minimal call
const res = await fb2k.invoke('library.search', {
    query: 'artist HAS Beatles'
});
if (res.success === false) throw new Error(res.error);
const { tracks, total, hasMore } = res;

// Second page
const page2 = await fb2k.invoke('library.search', {
    query: '%rating% GREATER 3',
    limit: 50,
    offset: 100
});

// Project two keys; the narrowed rows arrive under `tracks`
const albums = await fb2k.invoke('library.search', {
    query: 'artist HAS Beatles',
    limit: 500,
    fields: ['absolutePath', 'album']
});

Keeping a local copy ​

A page can maintain a local library cache (such as in IndexedDB) keyed by each track's handle, pulling only incremental changes. Both handle values and the revision reported by library.getChanges persist across restarts.

js
const PAGE = 5000;
const FIELDS = ['handle', 'title', 'artist', 'album', 'duration'];

async function fetchRows(handles) {
    for (let i = 0; i < handles.length; i += PAGE) {
        const res = await fb2k.invoke('library.getTracks', { handles: handles.slice(i, i + PAGE), fields: FIELDS });
        if (res.success === false) throw new Error(res.error);
        await store.put(res.tracks);
        await store.delete(res.missing);
    }
}

async function rebuild(handles) {
    await store.clear();
    const seen = new Set();
    let hasMore = true;
    for (let offset = 0; hasMore; offset += PAGE) {
        const res = await fb2k.invoke('library.search', { query: 'ALL', offset, limit: PAGE, fields: FIELDS });
        if (res.success === false) throw new Error(res.error);
        await store.put(res.tracks);
        for (const track of res.tracks) seen.add(track.handle);
        hasMore = res.hasMore;
    }
    await fetchRows(handles.filter((handle) => !seen.has(handle)));
}

async function sync() {
    const { epoch, revision } = await store.getMark();
    const res = await fb2k.invoke('library.getChanges', { epoch, since: revision });
    if (res.success === false) throw new Error(res.error);
    if (res.resync) {
        await rebuild(res.handles);
    } else {
        await store.delete(res.removed);
        await fetchRows(res.changed);
    }
    await store.setMark(res.epoch, res.revision);
}
  • On a cold start, render the UI using the local cache first, then call sync(). While the library is still loading at startup, calls fail with OPERATION_PENDING; inspect library.getStatus().initialized or wait for library:initialized before retrying.
  • During runtime, debounce calls to sync() on receiving any library:items* event. The sync protocol does not rely on the handles carried by the events, so truncated event batches do not cause missed updates.
  • library.getTracks resolves each handle on the main thread, so a large batch blocks the UI. Use it for small sets of changed rows, and page through library.search to rebuild the whole copy.
  • If tracks leave the library while paging, later rows move up and a page can skip some. After the last page, fetch any entries of handles that were not read.
  • Any changes that occur while rows are being fetched will have a higher revision than the current response and will be picked up on the next sync(); overwriting rows is idempotent.
  • During startup loading, foobar2000 announces existing library tracks via library:itemsAdded. These startup announcements do not advance the revision; offline changes made while foobar2000 was closed arrive after library:initialized.

Scenarios not tracked by revisions:

  • Changes made while this component was inactive, or profiles restored from backups. A restored profile may reuse previous revision numbers; rebuild the local cache after restoring a profile.
  • Changes undetected by foobar2000. Network shares and mapped WebDAV drives often lack file system change notifications; changes appear only after a startup check or calling library.rescan.
  • Abnormal terminations: epoch changes on abnormal exit, causing the next call to return resync: true.

Field projection ​

library.query and library.search accept an optional fields array that restricts which track keys each returned row carries. Omitting fields, or passing null, returns every key of a library track row.

When fields is specified, returned track rows contain only the requested keys and no others. Tracks with unreadable metadata populate non-optional keys with type defaults (empty string, zero). The optional albumId is omitted if no album identifier exists. Outer response properties (such as total, offset, and hasMore) remain unchanged.

Accepted key names (exact match, case-sensitive):

index, handle, title, artist, artists, album, albumId, albumArtist, albumArtists, genre, date, trackNumber, discNumber, duration, path, absolutePath, fileSize, bitrate, sampleRate, channels, codec, subsong, rating

Multi-value tags in artist / albumArtist / genre / composer (only the fields this API actually returns) are joined with , in their original order, without de-duplication.

artists provides the array of individual strings behind artist, satisfying artists.join(', ') === artist. Multi-value tags can be fully recovered from artists. It is included on every shared Track row (media-library track APIs, playlist.getTracks, playback.getCurrentTrack, queue.get, and track events) and on the flat response of library.getByPath; artwork payloads do not carry it. albumArtists does the same for albumArtist wherever a Track row is returned: its first element (falling back to artists[0] when empty) represents the album artist used by library.getAlbums for grouping. An array may be projected independently of its joined string. For tracks with unreadable metadata, a requested artists or albumArtists returns [].

Duplicate names are de-duplicated. rating is only computed when it is requested (or when fields is omitted), which is where most of the saving on tag-free projections comes from.

Validation always resolves — it never rejects the promise. A name outside the list produces:

js
const bad = await fb2k.invoke('library.query', {
    query: 'artist HAS Beatles',
    fields: ['absolutepath', 'Rating']   // wrong case
});
// {
//   success: false,
//   error: 'fields contains unknown field names',
//   code: 'INVALID_PARAMS',
//   details: { unknownFields: ['absolutepath', 'Rating'] }
// }

A value that is not an array, an empty array or a non-string element fails like any other malformed parameter: INVALID_PARAMS, with no details.

When to use it

ScenarioSuggested fields
Filtering tens of thousands of hits and only paths are needed['absolutePath']
Search results shown in a UI (hundreds of rows, all columns)omit fields
Filtering plus per-album grouping['absolutePath', 'album']

For an 80,000-row result set the single-key projection measured 45.1 MB down to 8.4 MB on the wire and JSON.parse in the page from 147 ms down to 37 ms.

Large result sets ​

The host's main thread is occupied in proportion to the response size. The cost is not in producing the rows — those are built on a worker thread — but in handing the finished response to the page, which measured 15–27 ms per MiB. Controlling how many bytes one call returns is therefore the only effective lever, and there are two:

LeverHowEffect
Projectionfields: [...]Full-field down to ['absolutePath'] is roughly 4.6× less payload (44.3 → 9.6 MiB at 80,000 rows)
Pagingoffset / limitOccupancy scales with the rows in that page, so it can be brought under any target

Supported access pattern: up to approximately 20,000 rows per call after paging or projection, keeping a single call's main-thread occupancy under 100 ms. For larger result sets, use pagination rather than requesting the entire collection in a single call.

32-bit (x86) hosts must avoid large result sets

A 32-bit process has roughly 2–4 GB of user address space, and a whole-library full-field response is resident in several forms at once while it is parsed (host-side UTF-8 string, wide string, materialized value in the renderer). Estimated instantaneous peaks:

RowsFull-fieldProjected to ['absolutePath']
80,000≈ 178 MB≈ 39 MB
165,306≈ 367 MB≈ 80 MB

Those figures are not automatically fatal, but they stack on top of the host's existing usage. On a 32-bit host, project or page when a query can match tens of thousands of rows; do not issue a full-field request for a whole library. A 64-bit host has no such address space limit, but the main-thread occupancy above applies equally.

The peaks are analytic upper bounds from the parse model, not measured working sets — treat them as guidance, not a budget.

Usage notes ​

  • asyncResult defaults to false. When it is true for a full-library request, the immediate result is { pending, requestId }; the completed { requestId, tracks, items, total, offset, limit, fromCache } payload is delivered to the calling WebView through library:getAllResult.
  • library.getRoots and library.browseTree are the typed library-navigation APIs. library.browseDirectory is the legacy path-prefix projection and does not represent the real root set.
  • library.getAlbums adds coverDataUrl only when includeCover is enabled and artwork is available. It is a data:image/... URL, not an fb2k:// URL.
  • library.search and library.query use foobar2000 query syntax. The implementation relies on search_filter_v2; clients should treat invalid expressions as a handler error rather than attempting to parse the syntax locally.
  • library.getStatus and library.getCount perform lightweight enumeration on the host without materializing a full handle list. The underlying callback invalidates query caches before broadcasting events. In library:itemsModified, the directory tree index used by library.getRoots and library.browseTree is preserved because file paths remain unchanged; notifications with fromHook: true clear only the cached full-library list. Each event payload names the changed tracks in handles, omitting them when truncated (truncated: true).

Library events ​

All four events are broadcast to every WebView.

EventPayload
library:itemsAdded{ count, handles, truncated, timestamp }
library:itemsRemoved{ count, handles, truncated, timestamp }
library:itemsModified{ count, handles, truncated, fromHook, timestamp }
library:initialized{ timestamp }