Skip to content

File API ​

The file namespace provides synchronous and asynchronous file system operations, directory management, and batch task execution.

Path length ​

foobar2000 does not opt in to Windows long paths, constraining the host to classic Win32 limits: an expanded path can have at most 259 characters, and a created folder at most 247. Synchronous methods reject longer paths with OPERATION_FAILED and details.value: 206 (the Win32 ERROR_FILENAME_EXCED_RANGE code) rather than reporting them as missing. Atomic file.write also requires room in the target folder for its temporary file (.~<process id>-<sequence>.tmp). Batch operations (file.copyAsync, file.moveAsync, and file.deleteAsync) report paths exceeding this limit as failed with reason: "path-too-long", including when a path inside a copied folder exceeds the limit.

file ​

file.cancelOp ​

Stop an operation started by file.copyAsync, file.moveAsync or file.deleteAsync. The entries it has not reached are reported as skipped / cancelled, and the run still ends with file:opComplete.

ParameterTypeRequiredDescription
operationIdstringYesId from the receipt of file.copyAsync, file.moveAsync or file.deleteAsync. Must not be empty.

Returns

FieldTypeDescription
cancelledbooleantrue when the operation was still running and has been told to stop; false when it had already finished or never existed, which are deliberately indistinguishable.

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

cancelled: false indicates that the operation had already completed or did not exist (the two cases are intentionally indistinguishable). A cancelled copy or move aborts the active file and deletes its partial copy; a cancelled delete stops before the next entry. Already processed entries retain their outcomes, remaining entries are reported as skipped / cancelled, and the operation concludes with a final file:opComplete event carrying cancelled: true.

Closing a popup window that initiated an operation cancels it automatically, as does quitting foobar2000. A panel host does not have an automatic cancellation hook: its operations run to completion unless stopped with file.cancelOp, and their events are delivered through the fallback described in Async file operation events.

js
const res = await fb2k.invoke('file.cancelOp', { operationId });
if (res.success === false) throw new Error(res.error);
const { cancelled } = res;

file.copy ​

Copy a file or a whole directory tree, blocking until the copy is done. A source that does not exist fails with NOT_FOUND. Prefer file.copyAsync for anything large.

ParameterTypeRequiredDescription
sourcestringYesFile or directory to copy; a directory is copied with everything below it. Must not be empty.
destinationstringYesTarget path. Its parent directory has to exist already. Must not be empty.
overwritebooleanNoReplace files that already exist at the target. Otherwise they are left as they are and the call still succeeds. Default: false.

Returns

FieldTypeDescription
sourcestringThe source as given.
destinationstringThe destination as given.

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

js
await fb2k.invoke('file.copy', {
	source: 'C:\\Music\\song.flac',
	destination: '%profile%\\backup\\song.flac',
});

file.copyAsync ​

Start a cancellable batch copy on a worker thread and return a receipt at once. The results arrive in batches on file:opProgress, followed by one file:opComplete. At most 8 batch operations run at a time across the process; beyond that the call fails with OPERATION_FAILED.

ParameterTypeRequiredDescription
itemsFileOpEntry[]YesEntries to copy, reported one result each, in this order. An entry that is not an object holding exactly the string members source and destination fails the call with INVALID_PARAMS. Every path is checked before anything starts, source for reading and destination for writing; one refused path, an empty string included, fails the whole call with PERMISSION_DENIED and no operationId. Must not be empty.
items[].sourcestringYesFile or directory to copy or move. Echoed back verbatim, %variable% placeholders unexpanded, in the file:opProgress results. Must not be empty.
items[].destinationstringYesTarget path; a missing parent directory is created. When it names an existing directory and source is a file, the file is placed inside it under its own name. Echoed back verbatim like source. Must not be empty.
overwritebooleanNoReplace an existing file destination instead of reporting the entry as skipped / already-exists. A directory copied onto an existing directory is merged into it either way; the files already inside are skipped, without being reported, unless this is set. Default: false.

Returns

FieldTypeDescription
operationIdstringBatch operation task identifier, prefixed with fileop_. Related lifecycle events carry this ID, and it can be passed to file.cancelOp to abort the operation.
totalCountintegerNumber of entries accepted; the events report it as total.

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

The return value is a dispatch receipt; batch results arrive via file:opProgress and complete with file:opComplete. Outcomes are reported per entry rather than per file: a directory entry completes after its entire subtree is traversed. At most 8 operations may run concurrently across the host process; additional requests fail with OPERATION_FAILED.

Copying a directory onto an existing directory merges contents: the destination is not rejected as already-exists. Files already present in the target directory are skipped without individual progress items (unless overwrite: true is set), and the directory entry reports status: "ok". The already-exists skip applies exclusively to file destinations.

js
const receipt = await fb2k.invoke('file.copyAsync', {
	items: [{ source: 'C:\\Music\\Album', destination: 'D:\\Backup\\Album' }],
});

file.delete ​

Delete a file or directory, to the Recycle Bin unless moveToTrash is false. A path that does not exist fails with NOT_FOUND.

ParameterTypeRequiredDescription
pathstringYesFile or directory to delete. Must not be empty.
moveToTrashbooleanNoSend it to the Recycle Bin. false deletes it permanently, which fails on a directory that is not empty. Default: true.

Returns

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

js
await fb2k.invoke('file.delete', { path: '%profile%\\cache\\stale.json' });

file.deleteAsync ​

Start a cancellable batch delete, with the same receipt and events as file.copyAsync; its results carry no destination. Recycle Bin deletes run on the main thread in batches of 16, permanent deletes on a worker thread.

ParameterTypeRequiredDescription
pathsstring[]YesPaths to delete, reported one result each, in this order. An entry that is not a string fails the call with INVALID_PARAMS. Every path is checked for writing before anything starts; one refused path, an empty string included, fails the whole call with PERMISSION_DENIED and no operationId. Must not be empty.
moveToTrashbooleanNoSend each entry to the Recycle Bin. false deletes permanently and removes directories that are not empty, which the synchronous file.delete refuses to do. Default: true.

Returns

FieldTypeDescription
operationIdstringBatch operation task identifier, prefixed with fileop_. Related lifecycle events carry this ID, and it can be passed to file.cancelOp to abort the operation.
totalCountintegerNumber of entries accepted; the events report it as total.

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

The return value is a dispatch receipt; batch results arrive via file:opProgress and conclude with file:opComplete. Recycle Bin deletes run on the main thread in batches of 16 as required by Windows shell APIs. Permanent deletes run on a background worker thread and can remove non-empty directories, whereas synchronous file.delete with moveToTrash: false rejects non-empty directories.

js
const receipt = await fb2k.invoke('file.deleteAsync', {
	paths: ['%profile%\\cache\\a.json', '%profile%\\cache\\old'],
	moveToTrash: false,
});

file.exists ​

Check whether a path exists and whether it is a file or a directory.

ParameterTypeRequiredDescription
pathstringYesPath to check. Must not be empty.

Returns

FieldTypeDescription
existsbooleanWhether the path exists.
isFilebooleanWhether the path is a regular file.
isDirectorybooleanWhether the path is a directory.

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('file.exists', {
	path: 'C:\\Music\\song.flac',
});
if (res.success === false) throw new Error(res.error);
const { exists, isFile } = res;

file.getInfo ​

Describe a file or directory. A path that does not exist is a success with exists: false and no other field.

ParameterTypeRequiredDescription
pathstringYesFile or directory to describe. Must not be empty.

Returns

FieldTypeDescription
existsbooleanWhether the path exists; when false, no other field is present.
isDirectorybooleanWhether the path is a directory.
isFilebooleanWhether the path is a regular file.
sizeintegerSize in bytes; 0 for a directory.
modifiedintegerLast write time as a JavaScript timestamp in milliseconds, truncated to whole seconds.
namestringFile name with its extension.
extensionstringExtension with its leading dot, such as .flac; empty when there is none.
parentstringParent directory of the path after %variable% expansion. The path is not normalized, so a doubled separator left by the expansion stays.

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

js
const info = await fb2k.invoke('file.getInfo', {
	path: 'C:\\Music\\song.flac',
});

file.list ​

List the files and subdirectories of a directory. A path that does not exist fails with NOT_FOUND, and one that is not a directory with INVALID_PATH.

ParameterTypeRequiredDescription
pathstringYesDirectory to list. Must not be empty.
patternstringNoFilter for files; directories are always listed. Only *, *.* and a single extension such as *.flac are understood, and nothing is refused: a pattern that does not start with *. (song*, ?.txt) matches every file, while a *.ext pattern is compared, ignoring case, with the text from the file name's last dot on, so *.{flac,mp3} matches nothing and no *.ext matches a file without an extension. Default: "*".
recursivebooleanNoWalk the subdirectories as well; the entries are then full paths instead of names. A subdirectory that cannot be read makes the call fail with OPERATION_FAILED. Default: false.

Returns

FieldTypeDescription
filesstring[]Files that match pattern: names, or full paths when recursive is set.
directoriesstring[]Subdirectories, whatever pattern says: names, or full paths when recursive is set.
itemsstring[]The same list as files; kept for callers that read this name.

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('file.list', {
	path: 'C:\\Music',
	pattern: '*.flac',
});
if (res.success === false) throw new Error(res.error);
const { files } = res;

file.mkdir ​

Create a directory together with any missing parents. A directory that already exists is a success with created: false; a file at the path fails with INVALID_PATH.

ParameterTypeRequiredDescription
pathstringYesDirectory to create. Must not be empty.

Returns

FieldTypeDescription
createdbooleanWhether this call created the directory; false when it already existed.
messagestringDirectory already exists when the directory was already there; absent otherwise.

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

js
await fb2k.invoke('file.mkdir', { path: '%profile%\\my-panel\\cache' });

file.move ​

Move a file or directory, blocking until the move is done. A file can move to another volume; a directory cannot, and that fails with NOT_SUPPORTED and details.reason: "cross-volume". A source that does not exist fails with NOT_FOUND.

ParameterTypeRequiredDescription
sourcestringYesFile or directory to move. Must not be empty.
destinationstringYesTarget path. An existing file there is replaced; moving a file onto an existing directory, or under a parent directory that does not exist, fails. Must not be empty.

Returns

FieldTypeDescription
sourcestringThe source as given.
destinationstringThe destination as given.

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

Moving a file across volumes succeeds — Windows copies and deletes it. Moving a directory across volumes fails with code: "NOT_SUPPORTED" and details.reason: "cross-volume".

js
await fb2k.invoke('file.move', {
	source: '%profile%\\inbox\\song.flac',
	destination: 'C:\\Music\\song.flac',
});

file.moveAsync ​

Start a cancellable batch move on a worker thread, with the same receipt and events as file.copyAsync. Within one volume a move is a rename; across volumes an entry is copied and its source then deleted.

ParameterTypeRequiredDescription
itemsFileOpEntry[]YesEntries to move, reported one result each, in this order. An entry that is not an object holding exactly the string members source and destination fails the call with INVALID_PARAMS. Every path is checked for writing before anything starts, source included, since a move deletes it; one refused path, an empty string included, fails the whole call with PERMISSION_DENIED and no operationId. Must not be empty.
items[].sourcestringYesFile or directory to copy or move. Echoed back verbatim, %variable% placeholders unexpanded, in the file:opProgress results. Must not be empty.
items[].destinationstringYesTarget path; a missing parent directory is created. When it names an existing directory and source is a file, the file is placed inside it under its own name. Echoed back verbatim like source. Must not be empty.
overwritebooleanNoReplace an existing file destination instead of reporting the entry as skipped / already-exists; the synchronous file.move always replaces one. An existing directory destination is never replaced: Windows cannot swap a directory in place, so on the same volume such an entry ends as skipped or failed. Default: false.

Returns

FieldTypeDescription
operationIdstringBatch operation task identifier, prefixed with fileop_. Related lifecycle events carry this ID, and it can be passed to file.cancelOp to abort the operation.
totalCountintegerNumber of entries accepted; the events report it as total.

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

The return value is a dispatch receipt; batch results arrive via file:opProgress and conclude with file:opComplete. Within a single volume, a move is a rename regardless of size. Across volumes, the runtime copies data and then deletes the source; the entry reports status: "ok" with reason: "cross-volume" to indicate fallback overhead. Directories support cross-volume moves asynchronously, unlike synchronous file.move.

Unlike file.copyAsync, moving a directory onto an existing directory reports skipped with reason: "already-exists" rather than merging contents. Setting overwrite: true does not merge directories.

js
const receipt = await fb2k.invoke('file.moveAsync', {
	items: [{ source: 'C:\\Inbox\\Album', destination: 'D:\\Music\\Album' }],
});

file.read ​

Read a file as text, or as Base64 with encoding: "binary". A path that does not exist fails with NOT_FOUND, and one that is not a regular file with INVALID_PATH.

ParameterTypeRequiredDescription
pathstringYesFile to read. Must not be empty.
encodingstringNoOnly exactly binary reads the raw bytes and returns them as Base64; any other value reads the file as text. Default: "utf-8".

Returns

FieldTypeDescription
contentstringThe file's text; for a binary read, its bytes as plain Base64 without a base64: prefix.
sizeintegerSize of the file on disk in bytes; for a text read it can differ from the length of content.
encodingstringPresent only for a binary read, and then always base64.

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('file.read', {
	path: '%profile%\\my-panel\\settings.json',
});
if (res.success === false) throw new Error(res.error);
const { content } = res;

When encoding: 'binary', content contains a raw Base64 payload without a base64: prefix, and the response sets encoding: 'base64'. This is a transport representation rather than text or a Data URL. To write these bytes back using file.write, prepend the required base64: marker and keep encoding: 'binary'.

file.rename ​

Rename a file or directory within its parent directory. A path that does not exist fails with NOT_FOUND, and a name that is already taken with OPERATION_FAILED.

ParameterTypeRequiredDescription
pathstringYesFile or directory to rename. Must not be empty.
newNamestringYesNew name, kept in the same directory. A name containing / or \ fails with INVALID_PARAMS. Must not be empty.

Returns

FieldTypeDescription
oldPathstringThe path as given.
newPathstringThe parent directory of the expanded path, joined with newName.

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

js
await fb2k.invoke('file.rename', {
	path: 'C:\\Music\\track01.flac',
	newName: '01 - Intro.flac',
});

file.write ​

Write text, or bytes decoded from Base64, to a file. Missing parent directories are created first.

ParameterTypeRequiredDescription
pathstringYesFile to write; missing parent directories are created. Must not be empty.
contentstringNoWhat to write. With encoding: "binary" a value starting with base64: is decoded and the bytes are written: characters outside the Base64 alphabet are skipped and decoding stops at the first =. Any other value, plain Base64 or a Data URL included, is written as it is. An empty string empties the file unless append is set. Default: "".
encodingstringNoOnly exactly binary changes anything: the file is written in binary mode and a content starting with base64: is decoded. Any other value writes text. Default: "utf-8".
appendbooleanNoAdd to the end of the file instead of replacing its content. Default: false.
atomicbooleanNoWrite a temporary file next to the target, flush it to disk, then replace the target with one rename: readers see the old content or the new content, never a partly written file. If the replacement fails, for example because another program has the target open, the call fails and the target keeps its old content. The temporary file is named .~<process id>-<sequence>.tmp, and its path has to fit in 259 characters as well. Cannot be combined with append; passing both fails with INVALID_PARAMS. Default: false.

Returns

FieldTypeDescription
bytesWrittenintegerSize of the file after the write, in bytes; with append it is the whole file, not just the part added.

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

js
await fb2k.invoke('file.write', {
	path: '%profile%\\my-panel\\settings.json',
	content: JSON.stringify({ theme: 'dark' }),
});

For binary writes, Base64 decoding occurs only when both conditions are met: encoding is 'binary' and content starts with base64:. The prefix is a Bridge protocol marker stripped prior to decoding. Raw Base64 strings, data:image/...;base64,... Data URLs, and fb2k:// URLs are not decoded; such input is written as its literal text, and the call can still report success: true.

js
const binary = await fb2k.invoke('file.read', {
	path: '%profile%\\data.bin',
	encoding: 'binary',
});
if (binary.success === false) throw new Error(binary.error);

await fb2k.invoke('file.write', {
	path: '%profile%\\data-copy.bin',
	content: `base64:${binary.content}`,
	encoding: 'binary',
});

Async file operation events ​

file.copyAsync, file.moveAsync, and file.deleteAsync report their outcome through two events. Both go to the window that started the operation, which is why their results may carry real paths; error envelopes and the console log still never contain one.

One exception: the host resolves the target window on every emit, so once that window has been destroyed it can no longer be found and the event falls back to the main instance. A popup cancels its own in-flight operations on close, which limits how many such events exist; a panel host has no equivalent hook.

file:opProgress ​

FieldTypeDescription
operationIdstringCorrelation id from the dispatch receipt
opstringcopy / move / delete
doneintegerEntries reported so far across all batches
totalintegerEntries accepted by the call; equals the receipt's totalCount
resultsarray<object>This batch only, not the cumulative list

Batched, never one event per entry: results accumulate until 64 are pending or 100 ms have passed since the previous batch, whichever comes first. The final partial batch always arrives before file:opComplete.

Each results entry:

FieldTypeDescription
sourcestringRequested source path, echoed verbatim with %variable% placeholders unexpanded
destinationstringRequested destination, echoed verbatim; absent for file.deleteAsync
statusstringok / skipped / failed
reasonstringAbsent when the entry succeeded outright; otherwise already-exists / not-found / permission / cross-volume / path-too-long / io-error / cancelled

skipped means the entry was deliberately not carried out - it already existed, or the run was cancelled before reaching it - so it is not an error. cross-volume is the one reason that accompanies status: "ok": the move succeeded through the copy-then-delete fallback.

file:opComplete ​

FieldTypeDescription
operationIdstringCorrelation id from the dispatch receipt
opstringcopy / move / delete
totalintegerEntries accepted by the call
successCountintegerEntries carried out, cross-volume fallbacks included
skippedCountintegerEntries deliberately not carried out
failureCountintegerEntries that failed
cancelledbooleanTrue when at least one entry was reported with reason: "cancelled"

The last event for an operationId when it arrives, and it is emitted on the cancelled and failed paths too. Two paths skip it entirely: the host shutting down mid-run, and an unexpected host-side failure in the worker. A listener that must not leak state should therefore carry its own timeout rather than wait on this event indefinitely.

Cancelling does not drop entries: the untouched remainder is still reported as skipped / cancelled, so the three counts normally add up to total.

js
fb2k.on('file:opProgress', ({ operationId, done, total, results }) => {
	// results[].status: 'ok' | 'skipped' | 'failed'
});
fb2k.on('file:opComplete', ({ operationId, successCount, cancelled }) => {
	// Last event for this operationId
});

File boundaries ​

  • File APIs expand %profile%, %component%, %music%, %APPDATA%, and %TEMP% before access. Each endpoint is validated against its registered SecurityLevel (Read for read endpoints, FileWrite for all file.* write endpoints); see the Permissions reference. file.write creates missing parent directories, while file.delete defaults to the Recycle Bin.
  • Every file.* failure returns a code alongside error. Failures originating in the filesystem also carry details.value (the raw Win32 error code). Neither error nor details ever contains a file path.
  • file.list returns bare names in non-recursive mode and full paths in recursive mode. file.getInfo returns exists: false as a successful result when a target does not exist.
  • Async batch operations (copyAsync, moveAsync, and deleteAsync) return a dispatch receipt; itemized results are delivered through file:opProgress and file:opComplete events targeted to the calling window. Event payloads include path references, whereas error envelopes and logs sanitize paths. Synchronous file.copy, file.move, and file.delete remain available for immediate operations.