System dialogs API
client.system.dialogs shows the desktop's own message boxes and file pickers, so an app can ask a question or let the user choose a file without drawing its own modal — and without being trusted to read the filesystem itself.
Availability
| Method | Permissions |
|---|---|
messageBox | system.dialogs |
openFile | system.dialogs, files.read |
saveFile | system.dialogs, files.read |
system.files.saveTextAs | system.dialogs, files.read, files.create; files.edit as well to allow replacing an existing file |
The file methods browse the connected workspace, so they need a connected host with file capabilities. messageBox works anywhere.
Interface
interface MessageBoxOptions {
title: string;
message: string;
kind?: "info" | "warning" | "error";
buttons?: readonly { id: string; label: string; destructive?: boolean }[];
defaultId?: string;
cancelId?: string;
}
interface OpenFileOptions { title?: string; directory?: string; kind?: "file" | "directory"; multiple?: boolean; extensions?: readonly string[] }
interface SaveFileOptions { title?: string; directory?: string; name?: string }
interface FileSaveSelection { parent: string; name: string; existing?: Readonly<FileEntry> }
interface SystemDialogs {
messageBox(options: MessageBoxOptions, control?: { signal?: AbortSignal }): Promise<string | null>;
openFile(options?: OpenFileOptions, control?: { signal?: AbortSignal }): Promise<readonly FileEntry[] | null>;
saveFile(options?: SaveFileOptions, control?: { signal?: AbortSignal }): Promise<FileSaveSelection | null>;
}
// client.system.files
saveTextAs(options: SaveFileOptions & { text: string; allowReplace?: boolean }, control?: { signal?: AbortSignal }): Promise<TextDocument | null>;Every method resolves to null when the user dismisses the dialog. messageBox resolves to the id of the button that was chosen.
Examples
Ask before discarding work:
const answer = await client.system.dialogs.messageBox({
title: "Discard changes?",
message: "Your note has unsaved changes. Discarding them cannot be undone.",
kind: "warning",
buttons: [
{ id: "discard", label: "Discard", destructive: true },
{ id: "keep", label: "Keep editing" },
],
defaultId: "keep",
cancelId: "keep",
});
if (answer === "discard") reset();Let the user pick files to work with:
const chosen = await client.system.dialogs.openFile({
title: "Choose a log file",
kind: "file",
multiple: false,
extensions: ["log", "txt"],
});
if (chosen?.length) await readWith(client.files, chosen[0]);A selection is a location, not permission to write: you still need files.read to read it, and the file APIs still check revisions.
Save text somewhere the user chooses, in one step:
const saved = await client.system.files.saveTextAs({
title: "Export notes",
name: "notes.md",
text: document.getText(),
});
if (saved) show(`Saved as ${saved.name}`);saveTextAs creates the file and returns the resulting document. Replacing an existing file happens only when your app holds files.edit and does not pass allowReplace: false; the desktop confirms the replacement with the user.
Errors
| Code | Message | Meaning |
|---|---|---|
invalid | Options must be an object. | Wrong argument type. |
invalid | Invalid option: <key> | An unknown field. |
invalid | Title and message are required. | Empty messageBox content. |
invalid | Text is required. | saveTextAs without text. |
denied | The extension does not hold the required permission. | A missing grant, for example files.create. |
unavailable | This service is currently unavailable. | No connected file service for the file dialogs. |
aborted | — | The signal fired, or the window closed with the dialog open. |
Lifecycle and permissions
Dialogs belong to the window that opened them: they close with it, and cancelling the signal dismisses them. While one is open the desktop marks your window busy, so quitting and disconnecting wait.
Keyboard focus returns to the element that had it before the dialog opened, so a dialog in the middle of a form does not lose the user's place.
These dialogs are the only way an app reaches the filesystem outside the paths it was given: there is no "browse anywhere" API, and no local filesystem access at all. What the user picks in the workspace is what your app receives.
Related guides
- Files API — reading and writing what the user chose.
- Window API — dirty and busy state around a dialog.
- Build your first app — the dialog permission in the default manifest.
- Extension permissions and trust — what users approve.