Per-project PHP version manager for Windows. Sister project to phpvm (Linux/macOS).
phpvm discovers every PHP install on your machine (Scoop, XAMPP, Laragon, WAMP, or hand-extracted zips) and lets you swap the active version instantly without editing PATH.
- Lists every PHP install on disk and marks the active one.
- Switches active PHP in under a second via a
.cmdshim. NoPATHchurn. - Auto-switches on
cdbased on.php-versionorcomposer.jsonrequire.php.
One-liner (PowerShell, no admin required):
irm https://raw.githubusercontent.com/rijverse/phpvm-win/main/install.ps1 | iexOr clone and run manually:
git clone https://github.com/rijverse/phpvm-win
cd phpvm-win
.\install.ps1The installer:
- Creates
%USERPROFILE%\.phpvm\{bin,shim}. - Prepends both dirs to user
PATH(idempotent). - Broadcasts
WM_SETTINGCHANGEso new shells pick upPATHwithout logout. - Optionally enables the auto-switch hook and sets an initial active version.
phpvm # arrow-key picker (TUI)
phpvm --list # list installed PHP versions
phpvm --current # show effective version + the three layers
phpvm global 8.2 # set the global default (highest matching minor)
phpvm shell 8.2 # pin PHP for THIS terminal only (needs the hook)
phpvm local 8.2 # write .php-version in current dir
phpvm --doctor # diagnose install--set / --set-project still work as aliases for global / local.
Drop a .php-version in any project root:
8.2
...or rely on composer.json:
{
"require": { "php": "^8.2" }
}Enable the hook once:
phpvm --enable-hookOn every cd, the hook walks up looking for .php-version or composer.json and
sets $env:PHPVM_AUTO_VERSION for the current terminal only. This is the project
layer (see Per-shell switching); it never changes the
global default or other terminals. A phpvm shell pin always wins over it.
IDEs launch php.exe directly and do not follow the shim. Point them at a
concrete path with phpvm which <ver> or phpvm --list --json. See
docs/IDE.md for PhpStorm and VS Code.
| Source | Detection |
|---|---|
| Scoop | scoop list + scoop prefix |
| XAMPP | C:\xampp\php |
| Laragon | C:\laragon\bin\php\php-* |
| WAMP | C:\wamp64\bin\php\php* |
| Manual | C:\php*, C:\tools\php*, %LOCALAPPDATA%\Programs\php* |
| Custom | Set $env:PHPVM_SEARCH_PATHS (semicolon-separated globs) |
| Flag | Behavior |
|---|---|
| (none) | Launch TUI |
--list, -l |
List versions, mark active |
--list --paths |
List versions with absolute php.exe paths |
--list --json |
List as JSON ([{version, path, active}]) |
which <ver> |
Print the php.exe path for a version |
install <ver> [--print] [--force] |
Download + install NTS x64 PHP (minor or latest) |
--current, -c |
Effective version + shell/project/global layers |
global <ver> |
Set the global default (alias: --set, -s) |
local <ver> |
Pin this dir via .php-version (alias: --set-project, -p) |
shell <ver> |
Pin PHP for this terminal only (needs the hook) |
shell --unset |
Remove this terminal's pin |
--auto [--quiet] [--print] [dir], -a |
Resolve project PHP (used by the hook) |
--enable-hook |
Install auto-switch hook + phpvm wrapper |
--disable-hook |
Remove prompt hook |
--window, --tray |
Launch the system-tray GUI |
--doctor |
Diagnose install |
--self-update [URL] [REF] |
Pull from git, re-run installer |
--version, -v |
Print version |
--help, -h |
Help |
Reload PATH in the current shell:
$env:Path = [Environment]::GetEnvironmentVariable('Path', 'User') + ';' + [Environment]::GetEnvironmentVariable('Path', 'Machine')Or open a fresh terminal.
Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSignedAdd %USERPROFILE%\.phpvm\shim to your AV exclusions.
Run phpvm --doctor. Common causes: shell PATH stale (open new terminal), PHPRC env var hijacking the binary, or shim file locked by a running php.exe (kill it).
If a version is not on disk, phpvm can fetch it:
phpvm install 8.3 # download + verify + extract PHP 8.3 (highest patch)
phpvm install latest # the newest minor on windows.php.net
phpvm install 8.3 --print # show the URL + target dir, download nothing
phpvm install 8.3 --force # reinstall over an existing copyIt downloads the NTS x64 build from windows.php.net, verifies the published
SHA256, extracts to %USERPROFILE%\.phpvm\php\<minor>, and registers a
per-version junction so phpvm global 8.3 / phpvm shell 8.3 can use it. Pass a
minor (8.3), not a patch (8.3.14); the latest patch of that minor is
installed. If Scoop is present, scoop install php is suggested as an
alternative but never required.
PHP is resolved at call time from three layers, highest priority first:
- shell -
$env:PHPVM_SHELL_VERSION, set byphpvm shell <ver>, lives only in the current terminal. - project -
$env:PHPVM_AUTO_VERSION, set by thecd-hook from.php-version/composer.json. - global - the persisted default set by
phpvm global <ver>.
A .cmd resolver shim reads these and dispatches to a per-version directory
junction (%USERPROFILE%\.phpvm\versions\<minor>), so a cd in one terminal
never changes PHP in another. Junctions need no admin rights.
Because a child process cannot set its parent's environment, phpvm shell is
handled by a phpvm wrapper function that --enable-hook installs into your
profile. Without the hook, phpvm shell prints how to enable it.
phpvm shell 8.1 # this terminal -> 8.1 (others unaffected)
phpvm shell # show this terminal's pin
phpvm shell --unset # drop the pin; fall back to project/global
phpvm --current # see which layer is winningphpvm --windowLaunches a tray icon (WinForms NotifyIcon, no extra dependencies). Right-click
to see installed versions with the global default checked and an [xdebug]
hint; click a version to switch the global default. The tray runs until you pick
Exit. To start it hidden in the background:
Start-Process powershell -WindowStyle Hidden -ArgumentList '-STA','-File',"$env:USERPROFILE\.phpvm\bin\phpvm.ps1",'--window'The GUI needs an STA thread; the phpvm (cmd) launcher already is one. Under
PowerShell 7 (pwsh, which defaults to MTA), the command prints how to relaunch
in STA.
| Linux/macOS | Windows | |
|---|---|---|
| Mechanism | update-alternatives symlink + per-shell php shim |
resolver .cmd shim + per-version junctions in %USERPROFILE%\.phpvm |
| Per-shell switching | Yes (phpvm shell, three layers) |
Yes (phpvm shell, three layers) |
| Auto-switch hook | chpwd_functions (zsh) / PROMPT_COMMAND (bash) |
prompt function override + phpvm wrapper |
| Discovery | Homebrew + dir scan | Scoop + dir scan |
| Project detection | .php-version / composer.json |
.php-version / composer.json |
install <ver> |
Yes (upstream repo) | Yes (windows.php.net, NTS x64) |
| GUI / tray | Yes (GTK) | Yes (WinForms tray) |
phpvm --uninstall # or run uninstall.ps1 directlyRemoves the shim, strips PATH, removes the hook. Does not touch any PHP installation.
MIT - see LICENSE.