Skip to content

Discovery API ​

The discovery namespace provides methods to enumerate services registered in the running foobar2000 process (v1.1.3+), including menu commands, formats, UI elements, DSPs, output devices, and preference pages.

discovery ​

discovery.executeContextMenuByPath ​

Run a context-menu command addressed by its slash-separated label path, such as Playback Statistics/Rating/5. Each segment must match one label exactly after normalization (mnemonic &, trailing ellipsis, accelerator text and ASCII case are ignored). A path matching several commands is refused with INVALID_PARAMS and the candidates listed; one matching none is NOT_FOUND. Giving both trackPath and a target other than auto is INVALID_PARAMS.

ParameterTypeRequiredDescription
pathstringYesSlash-separated label path of the command, such as Playback Statistics/Rating/5. Must not be empty.
trackPathstringNoRun against this track instead of the current target. A |subsong:N suffix selects a subsong; without one the first subsong of the file is used.
target"auto" | "selection" | "nowPlaying" | "playlistSelection"NoWhich tracks the command runs against when trackPath is absent. Default: "auto".

Returns

FieldTypeDescription
pathstringThe path as given.
foundNamestringFull name of the command that ran.
match"unique"Always unique on success: exactly one command matched.
candidateCountintegerNumber of commands the path matched; 1 on success.
itemCountintegerHow many tracks the command ran against.

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

js
await fb2k.invoke('discovery.executeContextMenuByPath', { path: 'Playback Statistics/Rating/5' });

Matching is never a substring test, so Rating/1 cannot resolve to Rating/10. A path that matches several commands is refused instead of guessed, because duplicated labels are common in real hosts: the failure carries match: "ambiguous", candidateCount and a candidates list of full names. Use it to refine the path, or address the command by GUID through discovery.executeContextMenuCommand, which is the only stable identifier. A path that matches nothing fails with NOT_FOUND and match: "notFound".

discovery.executeContextMenuCommand ​

Run a context-menu command by GUID against the tracks target names; by default the playing track, or the active playlist selection when nothing is playing. A command the host would never draw (FORCE_OFF) is refused with NOT_SUPPORTED unless force is set; no target at all is NO_ACTIVE_ITEM.

ParameterTypeRequiredDescription
guidstringYesCommand GUID in {...} form, as discovery.getContextMenuCommands reports it. Must not be empty.
forcebooleanNoDispatch even a command the host would never draw. Default: false.
target"auto" | "selection" | "nowPlaying" | "playlistSelection"NoWhich tracks the command runs against. Default: "auto".

Returns

FieldTypeDescription
guidstringThe GUID that was executed.
namestringLabel of the command.
hiddenbooleanWhether the host would never draw the command (FORCE_OFF).
resolvedbooleanWhether a registered item owns the GUID.
forcebooleanThe force that applied.
itemCountintegerHow many tracks the command ran against.

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

A FORCE_OFF command is refused rather than dispatched: the SDK treats that state as keyboard-shortcut only, so the host never renders it and running it would perform an action unavailable in the UI. The refusal fails with NOT_SUPPORTED and carries hidden: true, name, resolved, and force. DEFAULT_OFF commands (hidden unless Shift is held) remain reachable and are never refused. A GUID that no registered item owns fails with NOT_FOUND and resolved: false.

The refusal and the success response both carry hidden and resolved, so their absence means that the build does not report these fields.

js
const res = await fb2k.invoke('discovery.getContextMenuCommands');
if (res.success === false) throw new Error(res.error);
const { commands } = res;
const rate5 = commands.find((c) => c.name === 'Rating/5');
await fb2k.invoke('discovery.executeContextMenuCommand', { guid: rate5.guid });

name is the label the host reports, so it is localized on a translated build. Cache the guid rather than repeating the label match.

discovery.executeMainMenuCommand ​

Run a main-menu command by GUID. A child of a dynamic submenu is addressed by its parent's guid plus its own subGuid. The address must identify one current leaf; dynamic containers cannot be executed. Missing addresses fail with NOT_FOUND, ambiguous ones with MENU_MATCH_AMBIGUOUS, disabled leaves with MENU_ITEM_DISABLED, and unreadable state with OPERATION_FAILED.

ParameterTypeRequiredDescription
guidstringYesCommand GUID in {...} form. For a dynamic child, its parent command's GUID. Must not be empty.
subGuidstringNosubGuid of a dynamic child, as discovery.getMainMenuCommands reports it. Empty means absent.

Returns

FieldTypeDescription
guidstringThe GUID that was executed.
subGuidstringThe subGuid that was executed; only on the dynamic path.
dynamicbooleanWhether the dynamic execution path was taken.

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('discovery.getMainMenuCommands');
if (res.success === false) throw new Error(res.error);
const { commands } = res;
const prefs = commands.find((c) => c.path === 'File/Preferences');
await fb2k.invoke('discovery.executeMainMenuCommand', { guid: prefs.guid });

path is built from host labels, so it is localized on a translated build. Cache the guid rather than repeating the path match. The NOT_FOUND failure echoes guid, dynamic and, on the dynamic path, subGuid.

discovery.getAllServices ​

Count the discoverable service families in the running process: how many main-menu commands, context-menu commands, groups, input formats, UI elements, DSPs, output backends, preference pages and components are registered. Both menu families are counted through the same walks the listing endpoints use, filtered to what the host would show.

This method takes no parameters.

Returns

FieldTypeDescription
servicesDiscoveryServiceCountsCount per service family.
services.mainMenuCommandsintegerMain-menu commands the host would show, dynamic children included.
services.mainMenuDynamicCommandsintegerHow many of mainMenuCommands were expanded from dynamic submenus.
services.mainMenuGroupsintegerMain-menu groups.
services.contextMenuCommandsintegerContext-menu commands the host would show.
services.inputFormatsintegerPlayable file types.
services.uiElementsintegerRegistered UI elements.
services.dspEntriesintegerRegistered DSPs.
services.outputDevicesintegerRegistered output backends.
services.preferencePagesintegerPreference pages.
services.componentsintegerInstalled components.
contextMenuHiddenFilteredintegerHow many context-menu entries the "would the host show it" filter removed.
stateKnownbooleanWhether context-menu state was observable; false when nothing was selected or playing.
totalServicesintegerSum of every family except mainMenuDynamicCommands, which mainMenuCommands already includes.

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

services.contextMenuCommands counts context-menu commands through the same walk discovery.getContextMenuCommands performs, and is included in totalServices. Both menu families are filtered to what the host would actually show, so the counts stay comparable with the listing endpoints.

js
const result = await fb2k.invoke('discovery.getAllServices');

discovery.getComponents ​

List the installed components.

This method takes no parameters.

Returns

FieldTypeDescription
componentsDiscoveryComponentInfo[]The components.
components[].filenamestringDLL file name.
components[].namestringComponent name.
components[].versionstringVersion string as the component reports it.
components[].aboutstringAbout text.
countintegerNumber of entries in components.

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('discovery.getComponents');

discovery.getContextMenuCommands ​

List the registered context-menu commands, flattened, with their state for the current target. enabled and checked are only observable with a track selected or playing; check stateKnown before trusting them.

ParameterTypeRequiredDescription
includeHiddenbooleanNoAlso list entries the host would not show (FORCE_OFF, shortcut-list-only). Default: false.

Returns

FieldTypeDescription
commandsDiscoveryContextMenuCommand[]The commands.
commands[].enabledbooleanWhether the entry can be clicked. Meaningless while stateKnown is false.
commands[].checkedbooleanWhether the entry shows a check mark (a radio mark counts too).
commands[].radioCheckedbooleanWhether the check mark is a radio mark.
commands[].hiddenbooleanWhether the host would not draw the entry.
commands[].stateKnownbooleanWhether enabled and checked were observed. False when the state could not be evaluated, such as a context-menu entry with nothing selected or playing.
commands[].flagsintegerThe raw display flags the component reported. Main menu: 1 disabled, 2 checked, 4 radio, 8 default-hidden. Context menu: 1 checked, 2 disabled, 4 grayed, 8 radio.
commands[].namestringLabel as the host reports it.
commands[].descriptionstringDescription the component provides; empty when it has none.
commands[].guidstringCommand GUID.
commands[].parentGuidstringGUID of the parent group; the null GUID for a service without one.
commands[].indexintegerIndex of the command within its service.
commands[].source"mainmenu_static" | "mainmenu_dynamic" | "contextmenu_static" | "contextmenu_dynamic" | "hmenu_fallback"Where the entry came from.
commands[].executablebooleanWhether the entry can be executed.
commands[].unaddressableReason"" | "separator" | "dynamicParent" | "noStableIdentifier" | "emptyNode"Why it cannot be executed; empty when it can.
countintegerNumber of entries in commands.
includeHiddenbooleanThe includeHidden that applied.
hiddenFilteredintegerHow many entries the filter removed; 0 when includeHidden is set.
stateKnownbooleanWhether enabled and checked were observed: true only with a track selected or playing.
selectionCountintegerHow many tracks the state was evaluated against.

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

enabled and checked are observable only when a track is selected or playing, because item_get_display_data_root() evaluates display data against a metadb_handle_list. Check stateKnown first: when it is false, those two fields carry no observation and only hidden remains meaningful, as FORCE_OFF is a constant item property rather than a per-selection state.

js
const result = await fb2k.invoke('discovery.getContextMenuCommands');

discovery.getContextMenuTree ​

Dump the context menu for the current target as a nested tree, keeping submenus and separators. Depth and children per node are bounded; any clipping is reported through the truncated flags rather than left silent.

This method takes no parameters.

Returns

FieldTypeDescription
treeDiscoveryContextMenuTreeNodeThe root node.
tree.namestringLabel as the host reports it.
tree.type"command" | "popup" | "separator" | "unknown"Node kind.
tree.depthintegerDepth below the root, which is 0.
tree.enabledbooleanWhether the entry can be clicked. Absent on a separator.
tree.checkedbooleanWhether the entry shows a check mark. Absent on a separator.
tree.radioCheckedbooleanWhether the check mark is a radio mark. Absent on a separator.
tree.hiddenbooleanWhether the host would not draw the entry; always false here, because the tree is built from what the host shows. Absent on a separator.
tree.stateKnownbooleanWhether the state was observed; always true here. Absent on a separator.
tree.flagsintegerThe raw display flags. Absent on a separator.
tree.fullNamestringFull slash-separated name; only on a command node.
tree.childCountintegerThe host's real number of children; only on a popup node.
tree.childrenReturnedintegerHow many children this response holds; only on a popup node.
tree.childrenDiscoveryContextMenuTreeNode[]The children, in menu order; only on a popup node.
tree.truncatedbooleanWhether anything below this node was clipped.
tree.depthExceededbooleanWhether the depth limit clipped something below this node.
tree.childrenExceededbooleanWhether the per-node children limit clipped something below this node.
truncatedbooleanWhether anything in the tree was clipped.
depthExceededbooleanWhether the depth limit clipped something.
childrenExceededbooleanWhether the per-node children limit clipped something.
maxDepthintegerThe depth limit that applied.
maxChildrenPerNodeintegerThe per-node children limit that applied.
itemCountintegerHow many tracks the menu was built for.

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

Truncation is explicit rather than silent. A popup node reports childCount (the host's real count) alongside childrenReturned (what this response contains), so the two can be reconciled without counting the array. Any node whose subtree was clipped carries truncated, with depthExceeded / childrenExceeded distinguishing the cause; the flags propagate upward, so the response's top-level truncated covers the whole tree. maxDepth and maxChildrenPerNode echo the limits that were applied.

js
const result = await fb2k.invoke('discovery.getContextMenuTree');

discovery.getDspEntries ​

List the registered DSP entries.

This method takes no parameters.

Returns

FieldTypeDescription
entriesDiscoveryDspEntryInfo[]The DSPs.
entries[].guidstringDSP GUID.
entries[].namestringDisplay name.
countintegerNumber of entries in entries.

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('discovery.getDspEntries');

discovery.getInputFormats ​

List the playable input file types, each with a display name and a filename mask.

This method takes no parameters.

Returns

FieldTypeDescription
fileTypesDiscoveryInputFormatType[]The file types.
fileTypes[].namestringDisplay name, such as FLAC.
fileTypes[].maskstringFilename mask, such as *.FLAC.
fileTypes[].indexintegerIndex within the service that registered it.
countintegerNumber of entries in fileTypes.

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('discovery.getInputFormats');

discovery.getMainMenuCommands ​

List the main-menu commands, flattened, with their label path, GUID and display state. Submenus that components build at runtime (mainmenu_commands_v2) are expanded by default, so their child commands appear next to the parent slot.

ParameterTypeRequiredDescription
expandDynamicbooleanNoExpand submenus that components build at runtime. false lists the static registry only. Default: true.
includeHiddenbooleanNoAlso list entries the host would not show. Default: false.

Returns

FieldTypeDescription
commandsDiscoveryMainMenuCommand[]The commands, static slots first within each service, followed by their expanded children.
commands[].enabledbooleanWhether the entry can be clicked. Meaningless while stateKnown is false.
commands[].checkedbooleanWhether the entry shows a check mark (a radio mark counts too).
commands[].radioCheckedbooleanWhether the check mark is a radio mark.
commands[].hiddenbooleanWhether the host would not draw the entry.
commands[].stateKnownbooleanWhether enabled and checked were observed. False when the state could not be evaluated, such as a context-menu entry with nothing selected or playing.
commands[].flagsintegerThe raw display flags the component reported. Main menu: 1 disabled, 2 checked, 4 radio, 8 default-hidden. Context menu: 1 checked, 2 disabled, 4 grayed, 8 radio.
commands[].namestringLabel as the host reports it (localized on a translated build).
commands[].descriptionstringDescription the component provides; empty when it has none.
commands[].guidstringCommand GUID. For an entry expanded from a dynamic submenu this is the owning command's GUID.
commands[].parentGuidstringGUID of the group the command is filed under.
commands[].indexintegerIndex of the command within its service.
commands[].pathstringSlash-separated label path; for a static slot just its label.
commands[].isDynamicbooleanWhether the entry belongs to a dynamic submenu, as its parent slot or as an expanded child.
commands[].isDynamicParentbooleanWhether the entry is the parent slot of a dynamic submenu: a container that cannot be executed on its own.
commands[].subGuidstringGUID of the child node inside a dynamic submenu; pass it with guid to discovery.executeMainMenuCommand. Absent on static entries.
commands[].source"mainmenu_static" | "mainmenu_dynamic" | "contextmenu_static" | "contextmenu_dynamic" | "hmenu_fallback"Where the entry came from.
commands[].executablebooleanWhether the entry can be executed.
commands[].unaddressableReason"" | "separator" | "dynamicParent" | "noStableIdentifier" | "emptyNode"Why it cannot be executed; empty when it can.
countintegerNumber of entries in commands.
expandDynamicbooleanThe expandDynamic that applied.
includeHiddenbooleanThe includeHidden that applied.
dynamicCountintegerHow many entries were expanded from dynamic submenus.

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

Dynamic submenus registered through mainmenu_commands_v2 (used by SMP-era components such as ESLyric) expand by default. A parent slot has isDynamicParent: true and serves only as a container; entries expanded from its subtree carry isDynamic: true and subGuid. To execute an expanded entry, pass its subGuid to discovery.executeMainMenuCommand.

js
const result = await fb2k.invoke('discovery.getMainMenuCommands');

discovery.getMainMenuGroups ​

List the groups main-menu commands are filed under.

This method takes no parameters.

Returns

FieldTypeDescription
groupsDiscoveryMainMenuGroup[]The groups.
groups[].guidstringGroup GUID.
groups[].parentGuidstringGUID of the parent group.
groups[].namestringDisplay name; empty for a group that is not a popup.
groups[].sortPriorityintegerSort priority within the parent.
countintegerNumber of entries in groups.

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('discovery.getMainMenuGroups');

discovery.getOutputDevices ​

List the registered output backends by GUID.

This method takes no parameters.

Returns

FieldTypeDescription
devicesDiscoveryOutputDeviceEntry[]The backends.
devices[].guidstringBackend GUID.
countintegerNumber of entries in devices.

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('discovery.getOutputDevices');

discovery.getPreferencePages ​

List the pages of the Preferences dialog.

This method takes no parameters.

Returns

FieldTypeDescription
pagesDiscoveryPreferencePageInfo[]The pages.
pages[].guidstringPage GUID.
pages[].parentGuidstringGUID of the parent page.
pages[].namestringDisplay name.
countintegerNumber of entries in pages.

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('discovery.getPreferencePages');

discovery.getUIElements ​

List the registered UI elements.

This method takes no parameters.

Returns

FieldTypeDescription
elementsDiscoveryUIElementInfo[]The elements.
elements[].guidstringElement GUID.
elements[].subclassGuidstringSubclass GUID.
elements[].namestringDisplay name.
elements[].descriptionstringDescription.
elements[].isUserAddablebooleanWhether a user may add it to a layout.
countintegerNumber of entries in elements.

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('discovery.getUIElements');

discovery.searchCommands ​

Search command names, descriptions and menu paths across both menu families, case-insensitively (ASCII folding only). Each hit carries the same state fields the listing endpoints return, so a caller can tell whether it is invocable without a second call.

ParameterTypeRequiredDescription
querystringYesText to look for in names, descriptions and paths; ASCII case is ignored. Must not be empty.
expandDynamicbooleanNoAlso search commands expanded from dynamic submenus. Default: true.
scope"all" | "mainmenu" | "contextmenu"NoWhich menu family to search. Default: "all".
includeHiddenbooleanNoAlso search entries the host would not show. Default: false.

Returns

FieldTypeDescription
querystringThe query as given.
resultsDiscoverySearchResult[]The hits, main menu first.
results[].enabledbooleanWhether the entry can be clicked. Meaningless while stateKnown is false.
results[].checkedbooleanWhether the entry shows a check mark (a radio mark counts too).
results[].radioCheckedbooleanWhether the check mark is a radio mark.
results[].hiddenbooleanWhether the host would not draw the entry.
results[].stateKnownbooleanWhether enabled and checked were observed. False when the state could not be evaluated, such as a context-menu entry with nothing selected or playing.
results[].flagsintegerThe raw display flags the component reported. Main menu: 1 disabled, 2 checked, 4 radio, 8 default-hidden. Context menu: 1 checked, 2 disabled, 4 grayed, 8 radio.
results[].namestringLabel as the host reports it.
results[].descriptionstringDescription; empty when the component has none.
results[].guidstringCommand GUID; for a dynamic child, the owning command's GUID.
results[].pathstringSlash-separated label path. A context-menu entry is registered flat, so its path is just its label.
results[].isDynamicbooleanWhether the hit was expanded from a dynamic submenu.
results[].type"mainmenu" | "contextmenu"Which menu family the hit belongs to.
results[].subGuidstringGUID of the dynamic child node; pass it with guid to discovery.executeMainMenuCommand.
results[].source"mainmenu_static" | "mainmenu_dynamic" | "contextmenu_static" | "contextmenu_dynamic" | "hmenu_fallback"Where the entry came from.
results[].executablebooleanWhether the entry can be executed.
results[].unaddressableReason"" | "separator" | "dynamicParent" | "noStableIdentifier" | "emptyNode"Why it cannot be executed; empty when it can.
countintegerNumber of entries in results.
expandDynamicbooleanThe expandDynamic that applied.
scope"all" | "mainmenu" | "contextmenu"The scope that applied.
includeHiddenbooleanThe includeHidden that applied.
mainMenuHitsintegerHits from the main menu.
contextMenuHitsintegerHits from the context menu.
stateKnownbooleanWhether context-menu state was observed. False when the context family was searched with nothing selected or playing; the hits' enabled and checked must not be filtered on then.

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

Dynamic parent slots are skipped because their expanded children carry the executable identity. Entries produced by a dynamic submenu carry subGuid; pass both guid and subGuid to discovery.executeMainMenuCommand. Context-menu items are registered flat and placed by the host, so their path matches their label.

js
const res = await fb2k.invoke('discovery.searchCommands', { query: 'rating' });
if (res.success === false) throw new Error(res.error);
const { results } = res;

Discovery scope and execution rules ​

  • Results enumerate services registered in the current foobar2000 process; counts and names vary with installed components and host configuration.
  • discovery.executeMainMenuCommand and discovery.executeContextMenuCommand require a well-formed GUID; a malformed one fails with INVALID_PARAMS, and a GUID that no command owns fails with NOT_FOUND. A context command applies to the tracks target names: by default the now-playing item when available, otherwise the active playlist selection; selection, nowPlaying and playlistSelection pick one source. With no track there it fails with NO_ACTIVE_ITEM.
  • Components that build their main-menu subtree at runtime (mainmenu_commands_v2, for example ESLyric) are expanded by discovery.getMainMenuCommands and discovery.searchCommands. Expanded entries are identified by guid plus subGuid, and discovery.executeMainMenuCommand dispatches them through the dynamic execution path when subGuid is supplied.
  • Dynamic submenus are a snapshot of the moment the call is made: their contents can depend on the current track, selection, or component state.
  • discovery.executeContextMenuByPath requires path; optional trackPath is subject to media-read security. Without it, target picks the tracks as for executeContextMenuCommand; giving both trackPath and a target other than auto fails with INVALID_PARAMS.
  • discovery.getContextMenuTree provides diagnostic output and requires an active target item. Recursion depth and per-node child counts are both bounded, and any clipping is reported through truncated / depthExceeded / childrenExceeded rather than left silent; applied limits are returned in maxDepth and maxChildrenPerNode.
  • discovery.searchCommands requires a non-empty query and performs a case-insensitive match over command names, descriptions, and menu paths across both the main menu and the context menu. Case folding is ASCII-only, so UTF-8 multi-byte sequences pass through unchanged. scope accepts only all, mainmenu, and contextmenu; any other value fails with INVALID_PARAMS.