Docs

External commands (window.Electron.externalCommands)

Runs an allowlisted external OS executable — ffmpeg, git, a bundled CLI tool — and captures its stdout/stderr. Source: src/main/external-commands.ts, channel prefix externalCommands:*. Opt-in per alias. The renderer only ever names a config alias, never a real path or command string — see Processes configuration for the processes.externalCommands.<alias> shape and the externalCommands vs utilityProcesses mental model. With no aliases configured, every call rejects NOT_CONFIGURED, so this module is safe to leave enabled by default.

Commands run via spawn(..., { shell: false }) — a direct process, never through a shell string, so there's no && rm -rf-style shell-injection surface even from a hostile argument.

Methods

Method Returns Notes
run(alias: string, options?: ExternalCommandRunOptions) Promise<ExternalCommandResult> Runs the command and resolves once it exits. A failed spawn is reported via result.error, not a rejection.
start(alias: string, options?: ExternalCommandRunOptions) Promise<{ id: string; pid: number | null }> Starts the command and streams output/exit via events instead of awaiting completion.
kill(id: string) Promise<boolean> false when id is unknown.
onOutput(callback: (event: { id, stream: 'stdout' | 'stderr', text: string }) => void) () => void (unsubscribe) Only fires for commands started with start().
onExit(callback: (event: { id: string; result: ExternalCommandResult }) => void) () => void (unsubscribe)

ExternalCommandRunOptions

Field Type Notes
args string[] Passed as a real argv array, never a shell string. Checked against the config's allowedArgs allowlist when set. Max 256 args, 8192 chars each.
stdin string | number[] | ArrayBuffer | Uint8Array Optional stdin payload — use a byte array/Uint8Array for binary data.
stdinBase64 string Alternative binary stdin, base64-encoded. Ignored when stdin is also given.
timeoutMs number Overrides the config's timeoutMs for this call. 0 disables the timeout.

ExternalCommandResult

{ id, pid, exitCode, signal, stdout, stderr, stdoutTruncated, stderrTruncated, timedOut, error? }

A timed-out command receives SIGTERM, then SIGKILL after a 2-second grace period if it's still alive; timedOut is set to true on the result either way.

Example

const result = await window.Electron.externalCommands.run('ffmpeg', {
  args: ['-i', 'input.mp4', 'output.webm'],
});
if (result.exitCode !== 0) console.error(result.stderr);
// Streamed variant
const { id } = await window.Electron.externalCommands.start('ffmpeg', { args: [...] });
const unsubOut = window.Electron.externalCommands.onOutput((e) => {
  if (e.id === id) console.log(e.stream, e.text);
});
const unsubExit = window.Electron.externalCommands.onExit((e) => {
  if (e.id === id) console.log('exit code', e.result.exitCode);
});

.safe

Partial. run, start, kill are wrapped — window.Electron.safe.externalCommands.* resolves { ok: true, data } / { ok: false, error } instead of throwing. onOutput()/onExit() are the same functions on both surfaces (event subscriptions, never throw).

Last updated on July 18, 2026