Make running custom scripts easier!
Maintained fork. This is a maintained fork of jwaterfall/script-buttons, which as far as I can tell is no longer being updated. New features and fixes ship here.
When a package.json file is detected in the current workspace folder a button is created on the status bar for each script. When this button is clicked it runs the script in a terminal. Only 1 instance of each script can run at a given time.
Scripts can also be loaded in from a script-buttons.json or script-buttons.jsonc file (placed at the workspace root or inside the .vscode folder). Npm scripts will be white whereas non-npm scripts will be grey.
When no scripts can be found a warning message will be displayed.
Tip: If you have since added a package.json/script-buttons.json file or have modified existing scripts clicking the refresh button will attempt to find scripts again and update the buttons. Buttons also refresh automatically when you change a
scriptButtons.*setting.
Script Buttons can be configured from two places:
- VSCode settings (
scriptButtons.*keys in user or workspacesettings.json) — defines defaults that apply across projects. - A
script-buttons.json(orscript-buttons.jsonc) file at the workspace root or inside.vscode/— overrides settings on a per-project basis. JSONC is also accepted (comments + trailing commas).
When both are present, the project file wins per-field. The scripts arrays from both sources are concatenated; if two entries share the same label, the project file entry is used.
| Setting | Type | Default | Description |
|---|---|---|---|
scriptButtons.sources |
"package" | "config" | "both" |
"both" |
Which sources to pull scripts from. |
scriptButtons.filter.mode |
"whitelist" | "blacklist" |
"blacklist" |
How the filter list is applied to package.json scripts. |
scriptButtons.filter.contents |
string[] |
[] |
Script names to include or exclude (per mode). Only affects package.json scripts. |
scriptButtons.scripts |
{ label, icon?, script }[] |
[] |
Custom buttons. label is the button text; optional icon is a codicon shown before the label (bare name like "rocket" or full syntax like "$(rocket)" / "$(sync~spin)"); script is either a shell command string or an array of step objects forming a DAG (see Multi-step buttons). |
scriptButtons.showNpmInstall |
boolean |
true |
Show the special NPM Install button when a package.json is detected. |
// script-buttons.json (or .vscode/script-buttons.json)
{
"sources": "config",
"scripts": [
{ "label": "Dev Server", "script": "npm run dev" },
{ "label": "Lint", "script": "npm run lint" },
],
}The original flat-dict shape for script-buttons.json is still supported — no migration required:
{
"Dev Server": "npm run dev",
"Lint": "npm run lint",
}The shape is auto-detected: if any of sources, filter, scripts, or showNpmInstall keys are present, the file is read as a config object; otherwise it is treated as a legacy name → command dict.
Config files may use the .jsonc extension and freely include // line and /* block */ comments plus trailing commas. The parser also tolerates comments and trailing commas inside script-buttons.json itself, so you can switch on JSONC features without renaming. Search order is script-buttons.json → script-buttons.jsonc → .vscode/script-buttons.json → .vscode/script-buttons.jsonc; the first one that parses cleanly wins.
The extension ships a JSON Schema for script-buttons.json / script-buttons.jsonc and registers it via contributes.jsonValidation, so VSCode applies autocomplete and validation to any script-buttons.json or script-buttons.jsonc (workspace root or .vscode/) automatically — no $schema field required.
If you want validation in editors other than VSCode, the schema is also published at:
https://raw.githubusercontent.com/Smoke3785/script-buttons-extended/main/schemas/script-buttons.schema.json
You can reference it from your file with a $schema key:
{
"$schema": "https://raw.githubusercontent.com/Smoke3785/script-buttons-extended/main/schemas/script-buttons.schema.json",
"scripts": [{ "label": "Dev Server", "script": "npm run dev" }],
}A button's script can be an array of steps instead of a single shell command. Each step is either a shell command or a vscode command (anything you can run from the command palette), and steps can declare ordering constraints on other steps. Steps with no constraints run concurrently.
Three constraint flavors are available, matching different "what does ready mean?" questions:
| Constraint | A step waits until each named dependency… | Use it for |
|---|---|---|
reliesOn |
…has finished successfully (exit code 0 / command resolved). | Pipelines where downstream needs the dep's output or success. |
executeAfter |
…has started executing (process spawned / command invoked). | Long-running predecessors (e.g. a dev server) — don't wait for exit. |
reliesOnPort |
…has opened a TCP port that becomes reachable. | Following a server once it is actually listening, not just spawned. |
reliesOn and executeAfter accept a step id or an array of ids. reliesOnPort accepts an array of { id, port, host?, timeoutMs? } entries. All three can be combined on the same step — every constraint must be satisfied before it runs.
| Field | Type | Required | Description |
|---|---|---|---|
id |
string |
only if referenced by another step | Unique identifier within this script. |
type |
"shell" | "vscode" |
yes | shell runs command in a task terminal; vscode calls vscode.commands.executeCommand(command, ...args). |
command |
string |
yes | Shell command line, or a VSCode command id (e.g. workbench.action.files.saveAll). |
args |
unknown[] |
no | Arguments spread into executeCommand. Ignored for shell steps. |
background |
boolean |
no (default false) |
If true, the shell terminal is allocated silently — not revealed or focused. Output is still available via the terminal dropdown. Ignored for vscode steps. |
reliesOn |
string | string[] |
no | Step id(s) that must finish successfully before this step runs. |
executeAfter |
string | string[] |
no | Step id(s) that must have started executing before this step runs. Does not wait for the dep to finish — useful for long-running predecessors. |
reliesOnPort |
{ id, port, host?, timeoutMs? }[] |
no | Wait until each named dependency opens the given TCP port (default host localhost). If the dep finishes (or is skipped) before the port becomes reachable, this step is skipped. Optional timeoutMs bounds the wait. |
{
"scriptButtons.scripts": [
{
"label": "Build & Test",
"script": [
{ "id": "clean", "type": "shell", "command": "rm -rf dist" },
{ "id": "build", "type": "shell", "command": "npm run build", "reliesOn": "clean" },
{ "id": "test", "type": "shell", "command": "npm test", "reliesOn": ["build"] },
{ "type": "vscode", "command": "workbench.action.files.saveAll" },
],
},
],
}In this example clean and the saveAll vscode command kick off immediately and in parallel; build starts when clean finishes; test starts when build finishes.
{
"scriptButtons.scripts": [
{
"label": "Dev",
"icon": "rocket",
"script": [
{ "id": "server", "type": "shell", "command": "npm run dev", "background": true },
{
"type": "vscode",
"command": "simpleBrowser.show",
"args": ["http://localhost:3000"],
"reliesOnPort": [{ "id": "server", "port": 3000 }],
},
],
},
],
}reliesOn: "server" would never resolve here — the dev server doesn't exit. executeAfter: "server" would fire as soon as the process spawns, before it's bound the port. reliesOnPort waits for the actual port to accept connections.
- Shell steps run as VSCode tasks (
vscode.tasks.executeTaskwith aShellExecution), so each step gets its own task terminal and a real exit code. A non-zero exit is treated as a failure. The "started" signal forexecuteAfterfires when the task process actually spawns (onDidStartTaskProcess), not just when the task is queued. - VSCode steps succeed if
executeCommandresolves and fail if it throws. Their "started" signal fires immediately before the command is invoked. - Failure cascades.
reliesOn: if the dep failed or was skipped, this step is skipped.executeAfter: if the dep was skipped (never started), this step is skipped. A failed-but-started dep does not block — it did execute.reliesOnPort: if the dep finishes (any state) before the port becomes reachable, or if it was skipped, this step is skipped. An optional per-entrytimeoutMsalso triggers a skip. Independent branches keep running.
- Port probing. TCP
connecttohost:port(defaultlocalhost), polled every 250 ms. This confirms the port is accepting connections — it does not validate HTTP readiness or any application-level handshake. - Output channel. Orchestration logs (which step is running, port waits, success/failure/skip, and a final summary) are written to the Script Buttons output channel. If anything failed or was skipped, a warning toast offers a "Show Output" button.
- Validation at registration. Cycles (across all of
reliesOn,executeAfter, andreliesOnPort), unknown ids, self-references, duplicateids, out-of-range ports, and missingtype/commandare detected when the button is registered. Invalid entries are skipped (logged to the output channel) without affecting other buttons.
The legacy single-string form is unchanged:
{ "label": "Dev", "script": "npm run dev" }It still runs in a regular terminal with the original "dispose-and-recreate" behavior on repeated clicks.
There are currently no known issues.
script-buttons.jsonis now parsed as JSONC: line/block comments and trailing commas are allowed. A newscript-buttons.jsoncfilename is also recognised at the workspace root and inside.vscode/. Schema validation applies to both extensions. No migration needed.
- Custom buttons now support an optional
iconfield (inscriptButtons.scriptsandscript-buttons.json). Accepts a bare codicon name like"rocket"or full syntax like"$(rocket)"/"$(sync~spin)". The icon renders before the label; package.json scripts and built-in buttons are unchanged.
- Buttons can now run multiple steps with dependencies. The
scriptfield on a custom button accepts an array of{ id?, type, command, args?, reliesOn? }steps.type: "shell"runs a shell command as a VSCode task;type: "vscode"callsvscode.commands.executeCommand. Steps with noreliesOnrun concurrently; failures skip transitive dependents. - New Script Buttons output channel logs orchestration events and shows a final summary per click.
- Added validation at registration time: cycles, unknown
reliesOnids, duplicateids, and missing fields cause the button to be skipped (logged) instead of crashing init. - Published a JSON Schema for
script-buttons.jsonand registered it viacontributes.jsonValidation, so editors get autocomplete and validation automatically. - Legacy single-string scripts are unchanged.
- Forked and resumed maintenance.
- Added VSCode settings (
scriptButtons.*) forsources,filter, customscripts, andshowNpmInstall. script-buttons.jsonnow supports a richer object shape (sources,filter,scripts,showNpmInstall) in addition to the legacy flat-dict form.- Buttons refresh automatically when
scriptButtons.*settings change. - The
NPM Installbutton can now be hidden viascriptButtons.showNpmInstall: false.
Added an NPM install button if a package.json file is detected.
Added the ability to define scripts without a package.json file, this is done using a script-buttons.json file.
Initial release.



