VersionER
Central project version HUD for VS Code — detect, display, and sync versions across your project files.
One version lives in your head. It lives in five files. VersionER makes them agree.

What it does
VersionER detects your project type, finds every file that carries a version number, and shows the authoritative one in a sidebar HUD and the status bar. Bump it once and every file updates together — or get warned when they've drifted apart.
- Detects automatically. No configuration for the common cases.
- Edits surgically. Files are patched with targeted replacements, never parsed and re-serialized, so comments, key order, and formatting survive a bump.
- Flags drift. When a lockfile or a plist disagrees with the source of truth, the status bar turns amber and the offending file is marked.
- Confirms before multi-file writes. Touching two or more files always asks first.

Supported projects
| Type |
Primary file |
Also kept in sync |
| Node |
package.json |
package-lock.json, npm-shrinkwrap.json |
| Rust |
Cargo.toml ([package]) |
— |
| Python |
pyproject.toml ([project] or [tool.poetry]) |
setup.cfg, an unambiguous __version__ |
| Maven |
pom.xml (project version, not <parent>) |
— |
| Gradle |
build.gradle.kts / build.gradle |
gradle.properties |
| .NET |
Directory.Build.props or *.csproj |
additional *.csproj |
| PHP |
composer.json |
— |
| Flutter / Dart |
pubspec.yaml |
Android build.gradle, iOS Info.plist |
| Go |
VERSION |
— (shows latest git tag as a hint) |
| Generic |
VERSION or version.txt |
— |
When several markers are present the most specific one wins, so a Flutter project with a package.json for tooling is still treated as Flutter.
Commands
| Command |
Description |
VersionER: Show HUD |
Reveal the HUD |
VersionER: Display Options… |
Change location, layout, theme and more from a quick pick |
VersionER: Set Version… |
Enter an exact version |
VersionER: Bump Patch |
1.2.3 → 1.2.4 |
VersionER: Bump Minor |
1.2.3 → 1.3.0 |
VersionER: Bump Major |
1.2.3 → 2.0.0 |
VersionER: Refresh |
Re-read all version files |
VersionER: Add Synced File… |
Pick a file to keep at the project version — see below |
VersionER: Remove Synced File… |
Take an entry back out of .versioner.json |
Where the HUD lives
The HUD takes one of six positions in the window, and by default it opens automatically when the window does — without taking keyboard focus off your editor.
versioner.hud.location |
Where that is |
terminalRight (default) |
Beside the terminal inside the panel, on its right |
terminalLeft |
Beside the terminal inside the panel, on its left |
bottom |
Its own tab in the panel, spanning the whole bottom bar |
left |
A slice of the left side bar, under the file tree |
right |
Alone in the right-hand secondary side bar |
rightShared |
A slice of the right-hand secondary side bar, under the chat panel |

The two terminal* positions, left and rightShared work by moving the HUD into a container something else already occupies — the terminal's, the Explorer's, or whichever container is already sitting in the secondary side bar. A container lays its views out side by side, so the HUD takes a share of the space rather than a tab or a whole side bar of its own; that is exactly what dragging the view there by hand does, and vscode.moveViews is that drag in command form. bottom and right use containers this extension contributes.
Which side of the terminal the HUD lands on comes down to which of the two arrives last: a container shows its views in the order they were added, and moveViews only appends. Two views already sharing a container cannot be reordered — a move whose source and destination are the same container does nothing — so the two sides are two different meetings. For terminalRight the HUD goes to the terminal's container. For terminalLeft the terminal comes to VersionER's panel container instead, which means the panel tab reads VersionER rather than Terminal until you switch away; switching away sends the terminal home.
The same appending rule is what puts rightShared under the chat rather than over it. Nothing enumerates the containers in a part, so VersionER finds the one to join by reading installed extensions' manifests: a container reaches the secondary side bar only by being contributed to it. If nothing has, there is nothing to share with and the choice falls back to right; a container you dragged there yourself is not contributed to it either, so reach those with Move view….
The side bar slices open about 400px tall
left and rightShared ask for the share of the side bar that comes out near 400 pixels on a 1080p window — room for the version, the three bump buttons, the Set field and a short file list, and no more. VS Code applies a view's requested size the first time it appears in a container, which is exactly when the position is picked, so switching to either sets the height and any dragging you do afterwards is yours to keep.
Pixels are not on offer: initialSize in the manifest is a weight, and a view's opening height is weight / (sum of the weights of every visible view in the container) of the container's height, with views that name no weight counting as 20. Next to the Explorer's three — Folders, Outline, Timeline, 60 together — VersionER's 40 asks for 40 %, which is ~400px of a ~1000px-tall side bar and scales with the window from there. Raise or lower initialSize on versioner.hud and versioner.hudSecondary to move it.
right and rightShared need a VS Code build that accepts extension containers in the secondary side bar; confirmed on 1.130 and later. On older builds the choice has nothing to reveal, so use left or Whole panel below instead.
Options, in the HUD
The OPT button in the HUD's title bar turns the view into an options screen. Location is picked off a small picture of the VS Code window: each of the six positions is a hotspot drawn where it actually is, the chosen one inverted, and pointing at any of them names it underneath. The remaining settings are rows of segmented switches. DONE or Escape goes back. VersionER: Display Options… offers the same settings as a quick pick, and both routes write the same settings, so nothing can drift out of step.
Moving the panel
Two of those switches place the panel itself:
- Whole panel —
bottom, left, or right. Moves the panel to that edge of the window.
- Panel width —
full (the whole window width), editor (aligned with the editor area, VS Code's default), left, or right. Only applies while the panel is at the bottom, so the row dims when it isn't.
Both run VS Code's own panel commands, which means they move the whole panel — terminal, Problems, Output and all — not just the HUD. That is not a shortcoming of the switch: VS Code exposes panel placement only as a whole-panel command, and has none that sends a single view to an edge. If you want the HUD alone down an edge, use HUD location — left or right — instead. The window picture always draws the panel along the bottom, and says so in a note when the panel is docked elsewhere.
VS Code keeps view placement with the rest of its window layout rather than in a setting, so the two can part ways: drag the HUD somewhere else and location will no longer describe where it is. Move view… on the options screen is the way back, and reaches any container — it opens VS Code's own list of destinations for the HUD.
Layout
There are two regions, never three: the version readout, and a narrow side column holding one pane at a time — the bump controls or the synced list, whichever tab at its head is chosen. They ask for the same strip and only one of them is ever being read, so they take turns in it, and the readout keeps everything else. The column is sized to the three bump buttons rather than to a share of the view, and takes a little more width while the list is the pane showing, since file paths need more room than three buttons do.
The two regions arrange themselves to the space the view is given, so the same settings work in a tall side bar and a short panel:
| Arrangement |
Chosen when |
Shape |
| Row |
Short — a panel, wide or shared with the terminal |
Readout beside the side column |
| Stack |
Tall — a side bar |
Readout above it |
The version readout is always centred, and scales to the largest size that still fits on one line. When vertical room runs short the HUD drops its secondary chrome — bump previews, the git-tag hint, then Reload, which the palette carries as VersionER: Refresh — rather than overflowing.
Force one arrangement with versioner.hud.layout (auto, row, stack). Turning off versioner.hud.showFiles takes the tabs away with the list and leaves the controls in sole possession of the column.
Settings
| Setting |
Default |
Description |
versioner.hud.location |
terminalRight |
terminalRight, terminalLeft, bottom, left, right, or rightShared — see the table above |
versioner.hud.panelSide |
bottom |
bottom, left, or right — moves the whole panel, terminal included |
versioner.hud.panelAlign |
center |
justify, center, left, right — bottom panel only |
versioner.hud.openOnStartup |
true |
Reveal the HUD on window open, without stealing focus |
versioner.hud.layout |
auto |
auto, row, or stack |
versioner.hud.theme |
auto |
auto, dark, or light |
versioner.hud.showFiles |
true |
Offer the synced-files list as the side column's second tab |
versioner.hud.scanlines |
true |
Retro CRT scanline overlay |
versioner.hud.caretBlink |
true |
Blink the caret beside the version, or leave it steady |
Version handling
Versions are parsed as SemVer, with two conveniences:
- A leading
v is preserved. v1.2.3 bumps to v1.2.4.
- Pre-release identifiers are cleared on bump.
1.2.3-rc.1 → 1.2.4.
- Flutter-style build metadata is treated as a counter.
1.0.0+5 patch-bumps to 1.0.1+6; a minor or major bump resets it to +1.
For Android and iOS targets the marketing version and build number are split automatically — versionName / CFBundleShortVersionString get the core version, versionCode / CFBundleVersion get the build number.
Any file that carries the version — a docs config, a badge, a constants file — can be kept in step with the rest. The list lives in .versioner.json at your workspace root, and there are three ways onto it that all end in the same file:
- The options screen. The Synced extras section at the bottom lists every entry with a remove button, and Add file… opens a file picker.
- The palette.
VersionER: Add Synced File… and VersionER: Remove Synced File….
- By hand. Edit
.versioner.json directly — a JSON schema ships with the extension, so you get completion and validation while editing it.
Picking a file is usually all it takes: VersionER reads it looking for the current version. A file that contains nothing but the version is added as-is; a file that carries it somewhere inside gets its sync pattern generated from the line it sits on, and if the version appears on several lines you choose which one. Only when the current version is nowhere in the file do you have to write the pattern yourself.
{
"files": [
{ "path": "VERSION_BADGE.txt" },
{
"path": "docs/conf.py",
"pattern": "(release\\s*=\\s*\")([^\"]+)(\")"
}
]
}
Without a pattern, the file is treated as containing nothing but the version. With one, the regex needs exactly three capture groups — prefix, version, suffix — and only the middle group is rewritten.
Prompt for AI
If an AI assistant works in this project — Copilot, Cursor, Claude, or otherwise — point it here so version bumps go through VersionER instead of drifting out of sync. Paste this into its instructions file (.github/copilot-instructions.md, .cursorrules, CLAUDE.md, or however your tool takes project rules):
This project uses the VersionER VS Code extension as the single source of
truth for its version number. Follow these rules whenever a task touches
versioning:
1. Never invent a version number. Read the current one from the primary
version file for this project's type first (see "Supported projects"
in README.md) — or from VersionER's HUD / status bar if you have
editor access.
2. Never hand-edit a version string in just one file. This project's
version lives in several files at once (see "Supported projects", and
`.versioner.json` if the project has one); editing only the primary
file leaves the rest drifted, which is exactly what VersionER exists
to prevent.
3. Prefer VersionER's own commands over manual edits if you can drive
the editor's command palette:
VersionER: Bump Patch 1.2.3 -> 1.2.4
VersionER: Bump Minor 1.2.3 -> 1.3.0
VersionER: Bump Major 1.2.3 -> 2.0.0
VersionER: Set Version… enter an exact version
Each updates every synced file together in one confirmed edit.
4. If you can only edit files directly, update the primary file AND
every file listed under "Also kept in sync" for this project type —
plus any extra files in `.versioner.json` — to the exact same
version, using VersionER's own rules: SemVer; a leading `v` is
preserved; pre-release identifiers are cleared on bump; Flutter-style
build metadata (`+N`) is treated as a counter and reset to `+1` on a
minor or major bump; Android/iOS targets split the marketing version
from the build number automatically.
5. After editing, say so — VersionER's HUD and status bar will flag
"DRIFT" if a file was missed, and `VersionER: Refresh` re-reads
everything from disk to confirm.
Development
| Script |
Description |
npm run build |
Bundle the extension to dist/ with esbuild |
npm run watch |
Rebuild on change |
npm test |
Unit tests (vitest) |
npm run check |
Typecheck without emitting |
npm run smoke |
Run detection and bumping against fixtures/ |
npm run preview |
Render the HUD to .preview/ for checking in a browser |
npm run package |
Build a .vsix |
npm run preview renders the real HUD markup against sample snapshots — synced, drifting, and no-project — at the sizes VS Code actually gives a view, then writes .preview/index.html as a contact sheet. It stubs the vscode module rather than launching an extension host, so layout and theme changes can be checked in a second. Press F5 to run the extension for real.
Notes
The HUD is styled in monochrome — black, white and gray, blocky borders, monospace type, and an optional CRT scanline overlay. It follows your VS Code light/dark theme unless you pin it with versioner.hud.theme.
Fonts are bundled with the extension, so the HUD renders identically offline and the webview contacts no external hosts.