Skip to main content

Hooks

Hooks let extensions intercept command execution. Register before-hooks to modify arguments or cancel commands, and after-hooks to react to command results.

Before hooks

A before hook runs before the command handler. If the hook returns false, the command is cancelled and subsequent before hooks and the command itself are skipped.

editor.commands.registerHook(
{
commandId: "project.run",
type: "before",
priority: 50
},
async (args) => {
const activeFile = await editor.workspace.getActiveFile();

if (activeFile?.name.endsWith(".py")) {
// Handle Python files ourselves
await editor.terminal.open({
name: `Python: ${activeFile.name}`,
command: `python "${activeFile.name}"`,
});
return false; // Cancel the default "Run" behavior
}

return true; // Allow default behavior for other files
}
);

Return values for before hooks:

Return valueEffect
true (or any truthy value)Allow the command to proceed.
falseCancel the command. No further before hooks run, and the command handler is not called.
Promise rejection / thrown errorThe error is logged, and the command proceeds (fail-open). A broken hook does not block the command.

After hooks

An after hook runs after the command handler completes. It receives the command's return value as its argument. After hooks are fire-and-forget: their return values are ignored, and errors are logged but do not affect the caller.

editor.commands.registerHook(
{
commandId: "editor.save",
type: "after",
priority: 50
},
async (result) => {
// Log every save
console.log("File saved:", result);

// Auto-format after save
const file = await editor.workspace.getActiveFile();
if (file?.name.endsWith(".js")) {
await editor.commands.execute("editor.format");
}
}
);

Hook priority

Hooks are sorted by priority in ascending order -- lower numbers run first.

PriorityDescription
1-49High priority. Runs before most other hooks.
50-99Normal priority. Good default for most extensions.
100Default priority if not specified.
101+Low priority. Runs after other hooks.

When multiple extensions register hooks on the same command, priority determines execution order. If two hooks have the same priority, they run in registration order.

Hook timeout

Before hooks have a 30-second timeout. If an extension does not respond with true or false within that window, the hook defaults to true (allow), and the command proceeds. This prevents a stalled extension from blocking the editor indefinitely.

After hooks have no timeout constraint since they are fire-and-forget.

registerHook signature

const { hookId } = await editor.commands.registerHook(options, handler);
ParameterTypeDescription
options.commandIdstringThe command to hook. Must already be registered.
options.type'before' | 'after'When to run the hook.
options.prioritynumberExecution priority. Default: 100.
handlerfunctionFor before hooks: (args) => boolean | Promise<boolean> — return false to cancel. For after hooks: (result) => void — receives the command result; return value is ignored.

Returns: A Promise<{ hookId: string }> with the assigned hook ID (useful for debugging).