Macros for VS Code
A macro is a standalone JavaScript or TypeScript script executed within the context of a VS Code extension, with full access to the VS Code extensibility APIs. Macros let you automate tasks, customize your development workflow, and prototype extension behavior—without the overhead of building and maintaining a full extension thanks to their lightweight, standalone-file design.
Under the hood, macros run inside Node.js VM sandboxes. Each macro executes in its own isolated data context but all share the same process. Because of this model macros cannot be forcefully terminated; instead, cancellation tokens provide a cooperative mechanism for graceful shutdown.
TypeScript support is transparent, with transpilation happening on demand, providing a seamless experience.
Table of Contents
Getting Started
Any JavaScript or TypeScript document can be treated as a macro, even if not saved to disk—i.e. untitled documents. Once saved, certain tools—in particular UI ones such as CodeLens actions or custom IntelliSense—are enabled only if the file is either in a registered macro library or if its name matches the *.macro.* pattern. This restriction prevents confusion when working in workspaces containing JavaScript or TypeScript files.
Creating a Macro
Option 1: Use the Macros: New Macro command to create a new macro document.
- You will be prompted to choose from a list of templates to populate your macro.
- By default, this command uses the JavaScript templates but you can change this behavior via the Macros: Template Default Language setting.
Option 2: Use the New Macro action in the Temporary node in the Macro Explorer view.
Option 3: Create a new untitled document using your preferred method—for example, via the Create: New File... or Create: New Untitled Text File commands, or by double-clicking an empty area in the editor bar.
- Change the editor language to JavaScript or TypeScript.
- Use the Macros: Fill File with Template command, or the Apply Template CodeLens to add content from a template. The command respects the language the editor is set to.
Option 4: Ask the @macroschat participant to create a macro for you.
↑ Back to top
Writing Macro Code
The Macros: Fill File with Template command, or the Apply Template CodeLens on empty documents, can jumpstart your development by adding sample code to the currenr editor. To generate custom code, however, you can ask the @macroschat participant for help.
You can write your own code, of course—see Development for available APIs. When doing so, keep these basic rules in mind:
- Macros are standalone JavaScript or TypeScript files executed in a Node.js sandbox.
- Use CommonJS syntax (JavaScript) and avoid
export, top-level await or return statements.
- The last statement in your script should result in a
Promise when running async work, but result is otherwise ignored.
- Macros have access to globals like
vscode and macros, and they can import Node.js libraries, but there is no defined way to include arbitrary libraries.
- Macros cannot be forcefully stopped. They must either complete on their own or respect cancellation request received via the
__cancellationToken token.
Example: Async "Hello, World!" macro
// @macro:singleton
async function main() {
const yes = { title: 'Yes' };
const no = { title: 'No', isCloseAffordance: true };
let answer;
do {
answer = await vscode.window.showInformationMessage(
`Hello, World! Close this dialog?`, { modal: true }, yes, no);
} while (answer !== yes && !__cancellationToken.isCancellationRequested);
}
main()
↑ Back to top
Running a Macro
Option 1: From the Command Palette, use the Macros: Run Active Editor as Macro or Macros: Run Macro commands.
Option 2: On supported editors, i.e. those matching *.macro.* or saved in a macro library, use the equivalent Run Macro or Debug Macro buttons available in the editor title bar.
Option 3: Use the Run Macro button on macro nodes in the Macro Explorer view.
Option 4: If you asked the @macroschat participant to generate macro code, ask it to run it for you.
↑ Back to top
Stopping a Macro
ℹ️ A macro cannot be forcefully terminated. Instead, a cancellation request is issued via the unique __cancellationToken token provided to each instance. Macros must integrate this token into their asynchronous operations and logic—such as loops or long-running tasks.
Option 1: Use Request to Stop button on the macro node (all running instances) or the given run instance from the Macro Explorer view to stop a macro.
Option 2: Use the Macros: Show Running Macros command to select a specific instance to stop.
- This is also available from the status bar item shown when there are active macros instances.
If a macro does not respond to a cancellation request, it will continue running. In such cases, you can use the Developer: Restart Extension Host command to restart all extensions, or restart VS Code to stop the macro. While this is not ideal, it provides a way to recover from unresponsive or runaway macros. This approach does not implicitly terminate external processes started by the macro, however.
↑ Back to top
Running a Macro on Startup
⚠️ Workspace Trust: Startup macros are disabled in untrusted workspaces.
Startup macros allow you to customize your environment as soon as the environment launches. Because extensions restart when switching workspaces, these macros can be scoped to apply per workspace, enabling tailored setups across projects.
Startup macros allow you to create extension-like behavior, as they are initialized with VS Code. For clean-up, use the __disposables global which the macros execution engine disposes when the macro completes or when the extension is deactivated, for example when closing the current workspace.
Startup macros are defined via the macros.startupMacros setting in your workspace or user settings:
Option 1: edit the setting manually from the Settings Editor or the appropriate settings.json.
Option 2: directly from the Macro Explorer view by selecting Set as Startup Macro or Unset as Startup Macro from a macro node context menu.
The macros.startupMacros setting is additive across Global, Workspace, and Workspace Folder scopes, this is to say macros registered in all scopes will be run. The system deduplicates macros before execution so only one instance of any given startup macro file is run.
Startup macro paths may be defined using tokens like ${workspaceFolder} or ${userHome} for dynamic path resolution. This, for example, allows to define startup macros in user settings and have that match across different workspaces. Any paths that do not resolve to existing files are ignored. Check the Macros output channel for details logs, if needed.
↑ Back to top
Keybinding a Macro
To bind a macro to a keyboard shortcut, all you need to do is keybind the macros.run command, passing a single argument that is the path to the macro to run. This must be configured directly in your keybindings.json file as the Keyboard Shortcuts UI does not allow to define arguments. Check the VS Code documentation for details.
Use the Preferences: Open Keyboard Shortcuts (JSON) command to open the keybindings.json file.
Add a keybinding for the macros.run command:
- Add the path to the macro file as argument, with
${userhome} and ${workspaceFolder} tokens being supported.
Example: Macro keybinding definition
[
{
"key": "Shift+Alt+X",
"command": "macros.run",
"args": "${userhome}/macros/references.macro.js",
"when": "editorTextFocus"
}
]
↑ Back to top
Macro Libraries
A macro library is simply a folder registered in the macros.sourceDirectories setting. Any JavaScript or TypeScript file directly under these folders (no recursive discovery) is considered a macro. Additionally, the extension automatically adds files (e.g. jsconfig.json, global.d.ts) to support development.
Macro libraries are the core of managing your macros, and the Macro Explorer view itself, with a virtual Temporary library used to manage all untitled documents.
When a macro file is saved to disk, and it does not belong to a registered library, it will cause for some features to be automatically disabled for them.
↑ Back to top
Adding a Library
Option 1: Use the Add Library Folder action from Macro Explorer view title bar.
Option 2: Use the Macros: Add Library Folder command the Command Palette.
Option 3: Edit the macros.sourceDirectories setting. You can use the Macros: Source Directories entry in the Settings editor.
The macros.sourceDirectories setting supports using tokens like ${workspaceFolder} or ${userHome} for dynamic resolution, e.g. ${workspaceFolder}/.macros if you use that pattern to save macros in your workspaces. A missing folder won't lead to error, just to an empty library node. The latter behavior allows to easily create later the folder in the file-system by just adding a macro.
↑ Back to top
Removing a Library
Option 1: Use the Delete action from the context menu of the given library from Macro Explorer view.
This only deletes the relevant entry from the macros.sourceDirectories setting, it does not delete the folder.
Option 2: Edit the macros.sourceDirectories setting. You can use the Macros: Source Directories entry in the Settings editor.
↑ Back to top
User Interface
Macro Explorer View
The Macro Explorer view provides a central management hub for all macros and macro libraries actions.
Macro Library Folders: configure macro folders, browse their contents, and quickly add, delete, or move macro files around using drag-and-drop.
- "Temporary": this is a virtual library node that shows all untitled macro documents allowing to easily manage in-memory macros.
Macros: edit, run, or debug macros with one click.
Macro Run Instances: see active runs and stop existing ones from the view.
The Macros: Show Macro Explorer command can be used to bring it into view.
↑ Back to top
Macro REPL
The REPL is one of the more powerful out-of-box tools offered by the extension. It lets you evaluate JavaScript or TypeScript code interactively, in a context set up just like any macro, without resorting to disconnected experiences like the Developer Tools console.
Debug: Easily inspect the current context, evaluate any statement, or .load any macro into context.
Develop: Build full logic step by step, then .save your history to a macro file.
Experiment: Run any JavaScript or TypeScript snippet and see the result instantly.
You can start a new macros REPL using either of the following:
You can have as many REPL instances as you need, with each one being run in full isolation.
Some useful commands:
.ts, .js: Switch between TypeScript and JavaScript modes
.load, .save: Load a macro file or save your command history to macro editor
.help: View all available REPL commands
↑ Back to top
@macros Chat Participant
The @macros chat particpant supports macro development by being a domain expert on macro-code writing, avoiding the common mistakes generic LLMs would make when authoring macros such as adding export statements or top-level await statements (or just picking the wrong language).
The participant can save the generated code to an editor and run in upon request, streamlining exploratory workflows.
Example: Short chat with @macros to create and run a macro.
> @macros create a hello world macro
Save as hello-world.macro.ts and run it with the Macros extension.
// hello-world.macro.ts
export default async function main() {
// show greeting
...
> @macros run that macro
Created untitled:Untitled-4 and ran it as a macro
↑ Back to top
Commands
Debugging
See Debugging a Macro for additional information.
- Macros: Debug Active File as Macro: debug current editor as a macro (document will be saved before running).
- Macro: Debug Macro: select a macro file to debug. Provides access to configured
macros.sourceDirectories.
↑ Back to top
Development
Macros: Create REPL: create a REPL terminal to evaluate JavaScript or TypeScript code whose context matches the one used by running macros.
Macros: Setup Source Directory for Development: adds or updates optional files used to improve IntelliSense on macro files. This action is run automatically in the background when saving a .macro.js or .macro.ts file, provided that macros.sourceDirectories.verify is enabled.
↑ Back to top
Manage Macros
- Macros: Fill File with Template: initialize an existing file with example macro content.
- Macros: New Macro: creates a new file with example macro content.
- Macros: Show Running Macros: view and manage running macros.
↑ Back to top
Run Macros
- Macros: Run Active File as Macro: run current editor as a macro (document will be saved before running).
- Macros: Rerun Last Macro: execute the most recently run macro.
- Macros: Run Macro: select a macro to run. Provides access to macros in configured
macros.sourceDirectories directories.
↑ Back to top
IntelliSense
JavaScript and TypeScript macro files get IntelliSense support, as long as files have been saved to a macro library. The features can be made more accurate when the library is fully setup for development which means global.d.ts and jsconfig.json have been added to it. The extension verifies these files them when an editor for a file a library is opened for the first time in a given session, and updates them when new versions are available.
⚠️Automatic updating of global.d.ts and jsconfig.json means it is not recommended to customizing in any way.
⚠️ Untitled documents will not show proper IntelliSense until saved to disk as global.d.ts is needed to describe the global context.
Some custom features that the extension provides is autocomplete on vscode.commands.executeCommand showing all currently registered command IDs in your box, or on @macros options.
↑ Back to top
Development
Available Code References
The following references are available from the global context of your macro:
vscode: symbol that provides access to the VS Code APIs.
macros: symbol that provides access to this extension's API (see Macros API).
require: method that allows load Node.js libraries. Version is same as your installed VS Code's (see About option).
- Other:
atob, btoa, clearInterval, clearTimeout, crypto, fetch, global, require, setInterval, setTimeout.
↑ Back to top
macros API
log: Provides access to the Macros log output channel, allowing macros to write log entries as needed.
macro: Current macro.
uri: URI of the current macro instance. It can be undefined if running from an in-memory buffer.
window: Provides access to UI-related APIs.
Provides access to UI-related APIs for managing predefined macro views.
getTreeViewId(id: string): string | undefined: Claims an available treeview ID for the given macro run. Returns undefined if none are available.
getWebviewId(id: string): string | undefined: Claims an available webview ID for the given macro run. Returns undefined if none are available.
releaseTreeViewId(id: string): boolean: Releases a previously claimed treeview ID. Returns true if successful.
releaseWebviewId(id: string): boolean: Releases a previously claimed webview ID. Returns true if successful.
↑ Back to top
Special Variables
These tokens do not form part of contexts shared when @macro:persistent is used as they are different from session to session.
__cancellationToken: a CancellationToken used by the extension to notify about a stop request. See Stopping a Macro.
__disposables: an array for adding Disposable instances, which will be automatically disposed of when the macro completes.
__runId: Id of the current macro execution session.
__startup: Whether current macro execution session was triggered during startup.
↑ Back to top
Predefined Views and View Container
Views such as sidebars and panels cannot be created dynamically—they must be declared in the extension's package.json manifest. To work around this limitation, the extension predefines a Macros view container (macrosViews) that includes a fixed set of generic treeview and webview views.
Macro-backed tree view ("Tree View" template)
↑ Back to top
Available View IDs
The following views are statically registered and available for use:
macrosView.treeview1 through macrosView.treeview5 — for treeview-based UIs
macrosView.webview1 through macrosView.webview5 — for webview-based UIs
Avoid hardcoding view IDs unless necessary—there's no enforcement mechanism, so conflicts between macros may occur. Additionally, the predefined ID pool may expand in the future, meaning macros with hardcoded values could end up competing for a limited subset.
↑ Back to top
Dynamic View ID Claiming
While macros can hardcode and use these IDs directly, this approach becomes fragile as macro libraries grow—multiple macros may attempt to use the same view, causing conflicts.
To avoid this, macros can dynamically claim an available view ID using the following APIs:
macros.window.getTreeViewId(): string | undefined
macros.window.getWebviewId(): string | undefined
If no view ID is available, these methods return undefined so make sure to account for such case.
Once a macro is finished using a view, it can release the ID explicitly using the following APIs:
macros.window.releaseTreeViewId(id: string): boolean
macros.window.releaseWebviewId(id: string): boolean
Alternatively, all claimed IDs are automatically released when the macro completes. This includes REPL sessions, as each session is equivalent to a macro.
↑ Back to top
Enabling Views
Views are disabled by default. After claiming an ID, you must enable the corresponding view using a context key (notice the ID is suffixed with .show):
Example: Showing a view
const viewId = macros.window.getTreeViewId();
...
vscode.commands.executeCommand('setContext', `${viewId}.show`, true);
Be sure to reset the context when the macro finishes, there is no automatic tracking and any leftover context values will be effective until VS Code is restarted.
Example: Hiding a view
const viewId = macros.window.getWebviewId();
...
vscode.commands.executeCommand('setContext', `${viewId}.show`, false);
↑ Back to top
@macro Options
A @macro option defines runtime behaviors for your macro. It is added to macro file as a comment using this //@macro:«option»[,…«option»] syntax.
The following options are available:
persistent: All invocations of the macro use the same execution context so global variables persist across runs. Use the Reset Context CodeLens to reinitialize context.
retained: An instance of the macro will remain active until explicitly stopped, e.g., using the Macros: Show Running Macros command. This removes the need to await __cancellationToken.onCancellationRequested (or similar signal) to keep the macro's services and listeners running.
singleton: Only one instance of the macro may run at a time; additional invocations fail.
Example: Using the singleton option
// @macro:singleton
vscode.window.showInformationMessage("“There can only be one!");
↑ Back to top
Download Definition Files
Any URL-like string in a macro file pointing to a .d.ts file will automatically receive a code action, Download .d.ts, enabling you to download the file directly to the macro's parent folder. This simplifies adding type definitions to support IntelliSense in your macros.
GitHub URLs containing matching */blob/* are automatically converted to their raw equivalent. For example: https://github.com/Microsoft/vscode/blob/main/extensions/git/src/api/git.d.ts is automatically handled as https://github.com/Microsoft/vscode/raw/refs/heads/main/extensions/git/src/api/git.d.ts.
All URLs use a standard HTTP GET to download the file, which is not customizable at this time.
↑ Back to top
Debugging a Macro
Debugger
Using a debugger leverages the default debugging workflow for extensions. In this workflow, you start with a VS Code instance that is used to launch a second Extension Development Host instance. The debugger attaches to that host, and you run your debug scenario there, while the debugger itself remains in the original VS Code instance. The Debug Macro command automates the setup flow.
There are a couple of details to keep in mind:
You cannot open the same workspace at the same time in two different VS Code instances. This may require you to reopen the workspace for your scenario in the Extension Development Host instance.
The macro you start the Debug Macro command on is not run automatically in the new instance unless it is defined as a startup macro, because the execution / repro context is unknown.
Currently, there is no clear path to streamline this debugging flow. Ideally, the second instance would debug macros running in the first one, allowing you to debug macros without disturbing the current setup.
↑ Back to top
Logs
The macros.log API writes messages directly to the Macros output channel. Every log entry is prefixed with the run-id specific to the instance of your macro you are logging from.
- Use the Developer: Set Log Level... command to control the granurality of logs for the Macros extension. This a global setting for the extension and all macro instances.
- Use the Output: Show Output Channels... command to bring the Macros output channel into view, if needed.
↑ Back to top
REPL
The Macro REPL is a great way to verify your logic step-by-step, or to verify the current context as the extension sees it.
↑ Back to top