Jesse A. Green / notebook

Live demo: the DocCompiler guide library as a static, read-only snapshot (October 2026). No backend, no accounts, nothing to submit.

NVM for Windows — Administrator & Developer Guide

Generated June 2026 from coreybutler/nvm-windows at 5b18223

AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it.

This guide documents the architecture, configuration, operations, security model, and internals of NVM for Windows (nvm) — the Node.js version manager for Windows. It is intended for system administrators deploying NVM4W across machines, support engineers diagnosing failures, and developers who want to understand or contribute to the codebase. End-user task instructions live in the companion User Guide.

NVM for Windows is a single, self-contained Go executable. It has no runtime dependency on Node.js, no service, no database, and no background daemon of its own. Its entire job is to manage a set of on-disk Node.js installations and a single Windows symlink that selects the active one.


1. Executive Summary

Attribute Detail
Purpose Install, switch, and manage multiple Node.js versions on Windows
Language Go (module nvm, go 1.18)
Distribution Single nvm.exe + companion author-nvm.exe, packaged with an Inno Setup installer
Runtime dependencies None (no Node.js required); relies on Windows APIs
State model Filesystem only — version folders + a single symlink + a plain-text settings.txt
Privilege requirement Administrator or Windows Developer Mode (to manage the symlink)
Network endpoints nodejs.org (Node + version index), github.com/npm/cli (npm), GitHub API (self-update), author.io (alerts)
Configuration settings.txt in NVM_HOME + two environment variables (NVM_HOME, NVM_SYMLINK)
Supported consoles Command Prompt, PowerShell (officially)

Operating principle: every Node.js version lives in its own directory under NVM_HOME (e.g. C:\nvm\v22.14.0). The path NVM_SYMLINK is added to the system PATH exactly once at install time. Switching versions re-points that symlink — so the change is global, instant across all shells, and persists across reboots, without ever re-editing PATH.


2. Architecture

2.1 Component overview

                          ┌─────────────────────────────────────────┐
   User terminal  ──────▶ │  nvm.exe  (package main, src/nvm.go)     │
   (Admin shell)          │  os.Args switch-dispatch CLI             │
                          └───────────────┬─────────────────────────┘
                                          │ calls internal packages
        ┌─────────────┬──────────────┬────┴───────┬─────────────┬───────────────┐
        ▼             ▼              ▼            ▼             ▼               ▼
   nvm/web        nvm/node       nvm/arch     nvm/file     nvm/utility     nvm/upgrade
   downloads,     version        PE-header    unzip,       debug logs,     self-update,
   mirrors,       discovery,     arch detect  settings I/O cross-vol move  notifications,
   proxy, SSL     install state                                            scheduled tasks
        │             │                                                         │
        ▼             ▼                                                         ▼
   nodejs.org    %NVM_HOME%\vX.Y.Z\                                       nvm/author ──▶ author-nvm.exe
   github npm    (per-version node.exe + npm)                            (companion: notifications)
        │
        ▼
   NVM_SYMLINK ──(on system PATH)──▶ re-targeted on `nvm use`

2.2 Package inventory

Package File(s) Responsibility
main src/nvm.go CLI entry point, command dispatch, install/use/uninstall orchestration, settings persistence, debug troubleshooter, elevation helpers
nvm/web src/web/web.go HTTP client, proxy + SSL config, mirror resolution, Node/npm downloads, redirects, connectivity ping, zip extraction with ZipSlip guard
nvm/node src/node/node.go Detect current version/arch, enumerate installed versions, fetch & classify the remote version index (LTS/current/stable/unstable), map Node→npm versions
nvm/arch src/arch/arch.go Architecture detection by scanning the PE header bytes of node.exe; validate/normalize 32/64/arm64
nvm/file src/file/file.go Unzip, ReadLines, Exists helpers
nvm/semver src/semver/semver.go Vendored semantic-version parsing/validation/comparison
nvm/upgrade src/upgrade/{check,notification,register,upgrade}.go Self-update pipeline, release/alert checks, toast notifications, Windows Task Scheduler registration
nvm/utility src/utility/{logging,rename}.go Debug logging (ANSI), cross-volume Rename (move-or-copy)
nvm/encoding src/encoding/encoding.go Charset detection and UTF-8 conversion for settings values
nvm/author src/author/bridge.go Bridge to the external author-nvm.exe companion (notifications, subscribe, upgrade hooks)

2.3 Command dispatch model

There is no CLI framework (no Cobra, no flag package for subcommands). main() reads os.Args and routes on args[1] with a switch. The implication for operators and contributors: the authoritative list of commands is the switch block in src/nvm.go:main() plus the hard-coded usage text in help(). Positional arguments are interpreted as: args[1] = command, args[2] = version/detail, args[3] = architecture (32/64/arm64) when applicable.

The init() function additionally registers a custom nvm:// URL-protocol handler. When nvm.exe is launched with a nvm://launch?action=... argument (from a toast-notification button), it rewrites os.Args into the equivalent command (install, use, upgrade) before main() runs.

Note

Any argument equal to verbose (dashes stripped) anywhere on the command line enables debug logging via utility.EnableDebugLogs(). This is parsed in init(), independent of the command.

This is the core design decision (chosen over the .bat-shim and PATH-rewriting approaches used by older Windows version managers):

  1. At install time, NVM_SYMLINK (e.g. C:\nvm\node) is added to the system PATH.
  2. nvm use X validates the target, removes the existing symlink (rmdir via elevation), and creates a new directory symlink (mklink /D) pointing at %NVM_HOME%\vX.
  3. Because the symlink — not the version folders — is on PATH, the active node/npm change everywhere at once and survive reboots.

Architecture selection is handled by renaming executables inside the active version folder: node32.exe/node64.exe are swapped to/from node.exe so the on-PATH node matches the requested bitness.


3. System Requirements

Requirement Detail
OS Windows (10/11 and Server editions). Uses kernel32.dll/user32.dll and golang.org/x/sys/windows
Privileges Administrator, or Windows Developer Mode enabled (AllowDevelopmentWithoutDevLicense registry value > 0)
Console Command Prompt or PowerShell (officially supported)
Network Outbound HTTPS to nodejs.org, github.com, api.github.com, and author.io (or configured mirrors/proxy)
Disk Each Node.js version is a full install (tens to hundreds of MB); plan capacity per version retained
Go toolchain (build only) Go ≥ 1.18 to compile from source

Important

NVM_HOME and NVM_SYMLINK must be different paths, and NVM_SYMLINK must point at a location that does not already exist as a physical directory. The debug command enforces both invariants and warns if violated.


4. Configuration Reference

4.1 Environment variables

These are read directly from the process environment (os.Getenv). The installer sets the first two.

Variable Source Purpose
NVM_HOME src/nvm.go Root directory for installed versions and settings.txt. settings.txt is resolved as %NVM_HOME%\settings.txt.
NVM_SYMLINK src/nvm.go The symlink path placed on system PATH; its target is swapped on nvm use.
PROCESSOR_ARCHITECTURE src/nvm.go Seeds the default architecture when none is configured.
PATH src/nvm.go Inspected by nvm debug to detect conflicting Node installs and confirm the symlink is present.
APPDATA src/upgrade/notification.go Location of notification state: %APPDATA%\.nvm\.updates.json (hidden).

4.2 settings.txt

The persistent configuration file, stored at %NVM_HOME%\settings.txt. It is a plain-text, colon-delimited key/value file (one pair per line) — not YAML or JSON. It is written by saveSettings() and parsed by setup(). Values are passed through os.ExpandEnv, so environment-variable references like %APPDATA% are expanded on read.

Example (examples/settings.txt):

root: C:\Users\Corey\AppData\Roaming\nvm
path: C:\Program Files\nodejs
arch: 64
proxy: none
Key Written by Meaning
root nvm root, install/use Storage root for version folders (mirrors env.root).
path installer / setup() The original Node.js path captured at install (originalpath).
arch nvm arch <32|64> Default architecture (32/64/arm64).
proxy nvm proxy <url> Download proxy URL, or none.
node_mirror nvm node_mirror <url> Override for the Node download base URL.
npm_mirror nvm npm_mirror <url> Override for the npm download base URL.
originalversion installer / setup() Version of the pre-existing Node.js captured at install.

Caution

Do not hand-edit settings.txt to point root/symlink at a real Node.js directory. validSymlink() deliberately refuses to delete a path that is a physical file/directory rather than a symlink — an anti-footgun added after users corrupted installs by manual edits. Use the nvm commands instead.

4.3 Download endpoints and defaults

Defined in src/web/web.go:

Setting Default Override
Node base address https://nodejs.org/dist/ nvm node_mirror <url>
npm base address https://github.com/npm/cli/archive/ nvm npm_mirror <url>
Version index <node_base>/index.json follows the node mirror
Proxy none nvm proxy <url>
SSL verification on --insecure flag on install (per-invocation only)

Mirror URLs are normalized: a missing scheme gets http:// prepended, and a trailing / is enforced.


5. Operations

5.1 Installation and provisioning

Deploy via the Inno Setup installer (nvm-setup.exe) built from nvm.iss. The installer:

  • Writes nvm.exe and author-nvm.exe to the install directory.
  • Sets NVM_HOME and NVM_SYMLINK environment variables.
  • Adds NVM_SYMLINK to the system PATH.
  • Optionally absorbs an existing Node.js installation (copying its files and global modules into the NVM root, since Windows forbids cross-vendor uninstalls).

For fleet deployment, standardize NVM_HOME/NVM_SYMLINK paths and pre-remove any baseline Node.js image to avoid PATH conflicts (the #1 support issue).

5.2 Health check — nvm debug

nvm debug (checkLocalEnvironment()) is the primary operational diagnostic. It reports and validates:

  • Administrator vs. elevated vs. standard rights, and Windows Developer Mode status.
  • PATH conflicts — another node.exe ahead of the symlink.
  • Console type (warns if not Command Prompt/PowerShell).
  • Windows build number (via RtlGetNtVersionNumbers).
  • NVM4W version, author-bridge version, resolved binary path, settings path.
  • NVM_HOME / NVM_SYMLINK values, and that they differ and are not nested.
  • Whether the symlink exists and actually is a symlink pointing at a directory.
  • IPv6 status (flagged as a possible download-slowness cause).
  • Reachability of the Node version index (accounting for mirror/proxy).
  • Installed versions missing node.exe (invalid) or npm.cmd (Node-only).
  • Presence of settings.txt.
  • Availability of an NVM4W self-update.

A clean machine prints No problems detected.; otherwise a PROBLEMS DETECTED block lists each issue with a remediation hint. Use this as the first step in any support ticket.

5.3 Logs

  • Fatal errors are appended to error.log next to nvm.exe (writeToErrorLog), and some upgrade/scheduling paths write error.log in the working directory.
  • Verbose/debug logging is opt-in: add verbose to any command. Output is ANSI-colored (utility.DebugLogf) and includes the resolved command, architecture, download URLs, and file moves.

Tip

When collecting diagnostics for a bug report, re-run the failing command with verbose appended and attach both the console output and any error.log next to nvm.exe.

5.4 Self-update lifecycle

nvm upgrade (upgrade.Run → run) performs:

  1. Query the GitHub releases API (api.github.com/repos/coreybutler/nvm-windows/releases/latest) and the alerts feed (author.io/nvm4w/feed/alerts).
  2. Compare the running version against the latest; abort early if already current.
  3. Download nvm-noinstall.zip plus its .checksum.txt; verify with an MD5 comparison (abort on mismatch).
  4. Extract to a temp dir; download any extra assets (e.g. update.exe).
  5. Zip the current install as a backup and copy it to a hidden .update\nvm4w-backup.zip.
  6. Copy new files over the install directory; stage the new nvm.exe under .update\.
  7. Write and launch updater.bat, which waits for the current PID to exit, swaps in the new nvm.exe, schedules a RemoveNVM4WBackup cleanup task (~7 days out), and relaunches via the nvm://launch?action=upgrade_notify protocol.

Warning

Negative knowledge — rollback is not wired up in this build. The user-facing help text advertises "Manual rollback available for 7 days after upgrade," and the backup is created and retained for 7 days. However, the rollback argument handler inside upgrade.run() is entirely commented out in this commit. To revert, reinstall the prior release from the GitHub releases page; do not rely on an nvm upgrade rollback-style command — it does not exist here.

5.5 Notifications and scheduled checks

nvm subscribe <topic> (topics: lts, current, nvm4w, author) registers Windows scheduled tasks via schtasks (src/upgrade/register.go), each running nvm checkForUpdates <topic> hourly at a staggered minute:

Topic Scheduled task name Cadence
lts NVM for Windows Node.js LTS Update Check hourly @ :30
current NVM for Windows Node.js Current Update Check hourly @ :25
nvm4w NVM for Windows Update Check hourly @ :15
author NVM for Windows Author Update Check hourly @ :45

Notification de-duplication state lives in %APPDATA%\.nvm\.updates.json (hidden). Toast notifications and the actual notification delivery route through the external author-nvm.exe via nvm/author.Bridge. nvm unsubscribe <topic> removes the corresponding scheduled task.

5.6 Backup and recovery

  • Node versions are independent directories under NVM_HOME; backing up that tree backs up all installed runtimes.
  • Configuration is a single settings.txt.
  • NVM4W binary backups are produced automatically on upgrade (.update\nvm4w-backup.zip, 7-day retention).
  • To recover a corrupted version: nvm reinstall <version>. To recover NVM4W itself: re-run the installer for the desired release.

6. Security Analysis

Area Finding
Privilege model Requires Administrator or Developer Mode to manage the symlink. Elevation is performed via elevatedRun → elevate.cmd/elevate.vbs helpers, with a fallback that retries through UAC.
TLS HTTPS by default with certificate verification on. --insecure sets InsecureSkipVerify for a single install invocation and prints an explicit warning; it does not persist.
Update integrity Self-update validates the download against a published checksum — but with MD5, which is cryptographically weak. It guards against corruption, not a determined tamper. There is no signature verification of the update payload itself (the distributed installer is Authenticode-signed for releases other than v1.1.8).
Archive extraction web.unzip includes a ZipSlip guard (illegal file path on traversal outside the destination). The simpler file.Unzip helper only rejects names containing .. — a weaker check; prefer the web path for untrusted archives.
Symlink safety validSymlink() refuses to delete a target that is a physical file/directory rather than a symlink, preventing accidental destruction of real Node.js installs from a bad settings.txt.
Network trust Downloads come from nodejs.org and the npm GitHub archive over TLS; mirrors and proxies are user-configurable and trusted as configured. The alerts feed is fetched from author.io.
Privilege of scheduled tasks subscribe creates schtasks entries that invoke nvm checkForUpdates; these run under the registering user's context.

Caution

Treat --insecure as a break-glass option for TLS-intercepting corporate proxies only. Because update integrity relies on MD5, do not depend on the checksum step as a tamper-evidence control on untrusted networks — verify you are pulling releases from the official GitHub project, and prefer the Authenticode-signed installer for distribution.


7. Code Quality & Patterns

  • Concurrency: install, use, and upgrade run their work in goroutines coordinated by a Status channel and a sync.WaitGroup, while a foreground goroutine renders progress (either console text or a zenity progress dialog when --show-progress-ui is set). Signal handling (SIGINT/SIGTERM) triggers rollback of partial installs.
  • Architecture detection is done by byte-scanning the PE header of node.exe (arch.Bit: arm64 = 5045000064AA, x64 = 504500006486, x86 = 504500004C), rather than trusting filenames.
  • Cross-volume moves: utility.Rename falls back from os.Rename to a recursive copy-then-delete when source and destination are on different drives — important because temp downloads and the NVM root may live on different volumes. Large-extract races are handled with exponential backoff (1/2/4/8/16s) when moving npm into place.
  • Vendored semver: the project vendors a semver package (src/semver) in addition to importing blang/semver, used in the upgrade comparison paths.
  • Error surface: 92 distinct error strings were catalogued across the codebase (richest in src/semver and src/nvm.go), giving precise, user-actionable messages — these double as the canonical troubleshooting catalog.

Conventions for contributors

  • Add new commands to the switch in src/nvm.go:main() and to the help() usage text — they are maintained by hand and must stay in sync.
  • Positional argument slots are fixed (args[2] = version, args[3] = arch); preserve this contract.
  • Keep new user-facing failures as explicit error strings with remediation guidance, consistent with the existing catalog.

8. Build & Contributing

The build is driven by src/manifest.json (name, version, build entrypoint nvm.go, output ../bin, minify, release/dev profiles) and an Inno Setup script (nvm.iss). A Deno helper, build.js, injects the manifest version into nvm.iss and invokes assets\buildtools\iscc.exe to compile the installer.

From source (per the README):

# Install Go from https://golang.org
git clone https://github.com/coreybutler/nvm-windows
cd nvm-windows\src
go get github.com/blang/semver
go get github.com/olekukonko/tablewriter
build.bat
# resulting setup program lands in the dist directory

Note

The build deliberately uses Go (and a Deno helper for the installer) rather than Node.js — the author's stated rationale is that a Node version manager should not depend on the tool it installs.

Key dependencies

Dependency Role
github.com/blang/semver Semantic version parsing/comparison
github.com/olekukonko/tablewriter Renders the nvm list available table
github.com/ncruces/zenity Native progress dialogs for protocol-launched installs/upgrades
github.com/go-toast/toast Windows toast notifications
github.com/dustin/go-humanize Human-friendly relative release dates in notifications
github.com/saintfish/chardet Charset detection for settings encoding
github.com/coreybutler/go-fsutil, go-where Filesystem move helpers and executable lookup
golang.org/x/sys/windows Windows registry, console mode, token/privilege APIs

9. Potential Improvements

These are observations drawn from the source as it stands at this commit.

  • Wire up rollback. The 7-day backup is created and advertised in help text, but the rollback handler is commented out. Either implement the restore path or correct the help text to avoid setting a false expectation.
  • Strengthen update integrity. Replace the MD5 update checksum with SHA-256, and consider verifying a signature over the payload rather than relying on the installer's Authenticode signature alone.
  • Unify zip extraction. Two unzip implementations coexist (web.unzip with a full ZipSlip guard, file.Unzip with only a .. substring check). Consolidate on the hardened version.
  • Formalize the command surface. The hand-maintained switch/help() pairing is easy to desynchronize. A small command-registry table (name, aliases, handler, help) would remove the duplication and make --version/alias handling uniform.
  • Externalize settings.txt parsing edge cases. The colon-delimited parser splits on the first : and rejoins the remainder, which is correct for Windows paths but is custom logic worth unit-testing against values containing : and expanded env vars.
  • arm64/64 availability heuristics (IsNode64bitAvailable, IsNodeArm64bitAvailable) carry TODO: fixme and optimistically assume availability above a version threshold. A definitive check against the version index would prevent misleading "only available in 32-bit" errors.

10. Quick Reference

Files and locations

Path Purpose
%NVM_HOME%\settings.txt Persistent configuration
%NVM_HOME%\vX.Y.Z\ A single installed Node.js version
%NVM_SYMLINK% Active-version symlink (on system PATH)
<install dir>\nvm.exe Main executable
<install dir>\author-nvm.exe Notification/subscribe companion
<install dir>\error.log Fatal error log
<install dir>\.update\nvm4w-backup.zip Pre-upgrade backup (hidden, 7-day retention)
%APPDATA%\.nvm\.updates.json Notification de-dup state (hidden)

Network endpoints

Endpoint Used for
https://nodejs.org/dist/ (+ index.json) Node downloads and the version index
https://github.com/npm/cli/archive/ npm downloads
https://api.github.com/repos/coreybutler/nvm-windows/releases/latest Self-update check
https://author.io/nvm4w/feed/alerts Upgrade alerts/warnings

Operational commands

Command Operator use
nvm debug First-line diagnostic for any issue
nvm root [path] Inspect/relocate the version store
nvm upgrade Update NVM4W itself (no rollback command — reinstall to revert)
nvm proxy / node_mirror / npm_mirror Configure for restricted networks
nvm subscribe / unsubscribe <topic> Manage background update-check scheduled tasks
nvm install <v> all Provision both 32- and 64-bit of a version

Architecture availability rules

Architecture Available from
32-bit All supported versions
64-bit Node 8+ (older majors are 32-bit only)
arm64 Node 19.9+