nvm.fish: a Node version manager written in pure Fish
The Node.js version manager you'll adore, crafted just for Fish
At a glance
- What is it?
- nvm.fish installs and switches Node.js versions from Fish shell, with .nvmrc support and XDG-compliant storage. It is a rewrite, not a wrapper around nvm.sh, and that distinction decides who should use it.
- Who is it for?
- Adopt nvm.fish if your interactive shell is Fish and you want Node switching without sourcing a POSIX script; skip it if you need the original nvm.sh's command surface or you work in bash and zsh. Before relying on it, verify three things yourself: that `nvm install lts` resolves the release you expect, that your `nvm_data` path is where you want binaries to live, and that your project's `.nvmrc` is picked up when you run `nvm use` from a subdirectory.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 100 days ago.
- What is it written in?
- Mainly Shell, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 24, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A Fish-native Node manager, not a port of nvm.sh
The problem is narrow but real. Fish is not POSIX shell, so the original nvm.sh script cannot simply be sourced into it. nvm.fish is built from scratch for Fish instead of shimming the POSIX script, and the README says so explicitly: it is "not _that_ POSIX-compatible script." That single decision shapes everything else. The project is 100% pure Fish, which the README frames as making it simple to contribute to or tweak. It also means the command surface is nvm.fish's own, not a compatibility layer that mirrors nvm.sh flag for flag.
Who it is for: developers whose interactive shell is Fish and who juggle more than one Node runtime, whether across projects or across a single machine. The README's promise is that you can "juggle multiple active Node versions in a single local environment" without messing up your home directory or breaking system-wide scripts. That last clause matters. The tool keeps its own store of Node binaries rather than overwriting a system Node, and `nvm list` shows `system` alongside the managed versions when a system Node exists.
The repository layout matches the description: completions/, conf.d/, functions/ and tests/ at the top level. There is no compiled artifact and no package manifest to install from. It is shell functions, Fish configuration snippets, and tab completions, which is why Fisher is the install path.
How version resolution and activation actually work
Two mechanisms carry most of the behaviour, and both are visible in the README.
First, version resolution is alias-aware. `nvm install` accepts `latest`, `lts`, an LTS codename such as `carbon`, or a full or partial version number with an optional leading "v". The README notes that `nvm install carbon` installs 8.16.2, the latest release of the Carbon LTS line, which tells you codenames are resolved against the Node release list rather than treated as literal strings. Partial versions work too: `nvm use v14` activates an already-installed 14.x line, and in the README's own `nvm list` output the active entry is `v14.15.1 lts/fermium`, so the alias and the concrete version are tracked together.
Second, project pinning works by directory traversal. An `.nvmrc` or `.node-version` file in a project root holds a version number or alias. When you run `nvm install` or `nvm use` without an argument, the tool walks up the directory hierarchy until it finds one. The README states this plainly: it "works like a charm from anywhere in your project by traversing the directory hierarchy until an `.nvmrc` is found." That is the whole data flow. There is no daemon, no shim on PATH intercepting `node`, and no per-directory hook described in the README. Activation happens when you run the command, in the current environment.
That last point is a design boundary worth stating. The README says `nvm install` activates the specified version "only in the current environment." Persisting a default for new shells is a separate step, done by setting a universal variable.
Installing nvm.fish with Fisher and your first Node switch
Installation goes through Fisher, the Fish plugin manager. The README gives one command:
fisher install jorgebucaran/nvm.fishAfter that, the functions and completions are available in a new shell. The README claims no setup is needed, and the repository layout supports that reading: conf.d/ and functions/ are the directories Fish loads from automatically for a plugin installed this way.
Your first real use is installing and activating a runtime. To get the current LTS line:
nvm install ltsTo install a specific version instead, partial numbers are accepted:
nvm install v15.3.0Then confirm what you have. The `nvm list` output in the README shows the system Node, if present, plus each installed version, with the active one marked and LTS aliases printed alongside:
nvm listTo pin a project, write the version into a file at the project root and let the tool find it from any subdirectory:
node --version >.nvmrc
nvm installIf you want a version active in every new shell rather than just the current one, set the universal variable the README documents:
set --universal nvm_default_version v18.4.0The same pattern applies to packages you want present in every new Node install:
set --universal nvm_default_packages yarn npThat variable is the README's answer to a recurring question about defaults, and it is worth noting it is a universal Fish variable, not a config file inside the project.
Where nvm.fish stops being the right tool
The most consequential limitation is the one the README states in its first paragraph. This is not nvm.sh. If your team's scripts, CI steps, or documentation call nvm.sh subcommands and expect its exact semantics, nvm.fish will not be a drop-in replacement, and the README does not claim it is. It points readers who want the original nvm inside Fish at two other plugins instead.
The second limitation is scope of activation. `nvm install` changes the current environment only. There is no documented automatic hook that switches Node when you `cd` into a directory containing an `.nvmrc`; you run `nvm use` yourself. If you expect the shell to follow your project directory silently, this design will feel manual.
Third, storage is configurable but not self-managing. Binaries live under `$nvm_data`, which defaults to `$XDG_DATA_HOME/nvm` and, per the README, to `~/.local/share/nvm` when XDG_DATA_HOME is unset. Old versions you install stay there until you run `nvm uninstall`. There is no documented garbage collection, so a machine that has chased several LTS lines will accumulate runtimes.
Finally, the README does not document rollback of the universal variables it tells you to set. `set --universal nvm_default_version v18.4.0` persists across shells; the README gives no counterpart command for clearing it. That is a gap, not a bug, but it is the kind of gap you want to know about before you set it on a shared machine.
Maintenance is worth a plain statement rather than a mood. The last push to the default branch was on 2026-06-22, and the most recent tagged release listed is 2.2.17 from 2025-02-07. The repository is not archived.
nvm.fish versus fish-nvm and plugin-nvm
The README itself names the alternatives, which is more than most projects do: FabioAntunes/fish-nvm and derekstavis/plugin-nvm, both offered as ways to use the original nvm from Fish.
The difference is architectural, not cosmetic. nvm.fish reimplements version management in Fish. The other two wrap nvm.sh, the POSIX script, so that it runs under Fish. Wrapping means you inherit nvm.sh's behaviour and its compatibility surface: if a documented nvm.sh feature or flag matters to you, a wrapper is the safer bet, because nvm.fish does not promise that surface. Reimplementing means the code is Fish all the way down, which is what makes the tab completions and the Fish-native variable handling possible, at the cost of a smaller command set.
There is a practical consequence for teams. If your CI or your colleagues' bash shells already depend on nvm.sh semantics, standardising on nvm.fish adds a second, differently-behaving tool to the mix. If your whole interactive workflow is Fish and you never needed nvm.sh's full command set, the wrapper's extra layer buys you nothing.
Mirrors, data location and what the MIT licence leaves you to decide
Two environment-level knobs are documented. `$nvm_mirror` selects where Node binaries are downloaded from, defaulting to https://nodejs.org/dist. That is the setting to reach for behind a corporate proxy or on a network where nodejs.org is slow, and it is the only documented way to change the download source.
`$nvm_data` sets where nvm stores Node binaries and related data. The README shows an override to a dotfile-style path:
set --global nvm_data ~/.nvmNote the scope in that example: `--global`, versus `--universal` for the default-version and default-packages variables. Mixing these up is an easy mistake, and the README does not spell out the difference in behaviour between them.
The project is MIT licensed. In plain terms, that is a permissive licence: you can use, modify and redistribute it, including commercially, provided the licence and copyright notice are preserved. This is not legal advice; if you are vendoring the plugin into a product or a locked-down build image, read LICENSE.md and your own policy rather than this paragraph.
Upgrade cost is low by construction. There is no compiled step and no dependency tree to reconcile; the code is Fish functions plus completions. Upgrading means reinstalling through Fisher and, in practice, re-checking that your `nvm_default_version` and `nvm_default_packages` variables still behave as expected, since those are your state, not the plugin's.
Editorial conclusion
Adopt nvm.fish if your interactive shell is Fish and you want Node switching without sourcing a POSIX script; skip it if you need the original nvm.sh's command surface or you work in bash and zsh. Before relying on it, verify three things yourself: that `nvm install lts` resolves the release you expect, that your `nvm_data` path is where you want binaries to live, and that your project's `.nvmrc` is picked up when you run `nvm use` from a subdirectory. The README does not document how to undo `set --universal nvm_default_version`, so test that in a throwaway shell first.
Frequently asked questions
How do I install nvm.fish?
Install it with Fisher using `fisher install jorgebucaran/nvm.fish`. The README states no further setup is needed.
Is it safe to install nvm.fish?
The README does not make a safety claim. What it does state is that the tool does not mess up your home directory or break system-wide scripts, and that it stores data under `$nvm_data`, which defaults to `$XDG_DATA_HOME/nvm` or `~/.local/share/nvm`.
How do you install nvm?
For Fish, the README's installation step is `fisher install jorgebucaran/nvm.fish`, after which the nvm commands and tab completions are available. The README notes this is not the POSIX-compatible nvm.sh script.
What is nvm and NPM?
nvm.fish manages which Node.js runtime is active, installing versions such as `lts`, `carbon` or `v15.3.0` and switching between them. NPM is not covered by the README; the closest thing documented is `$nvm_default_packages`, which lists packages to install each time a new Node version is installed.
How do I install nvm.fish on Fish shell?
Run `fisher install jorgebucaran/nvm.fish`. Once installed, `nvm install lts` fetches the current LTS release and activates it in the current environment.
How do I install nvm in Fish?
The README gives a single command, `fisher install jorgebucaran/nvm.fish`, and says no setup is needed beyond that. The plugin's functions and completions load from the conf.d/ and functions/ directories in the repository.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/jorgebucaran-nvm-fish)