Open-source project
nodenv/nodenv avatar
nodenv/nodenv

nodenv: a shim-based Node version manager written in Bash

Manage your app's Node.js environment

2,415 stars171 forksShellMIT

At a glance

What is it?
It puts itself on your PATH, reads a .node-version file per project, and delegates almost everything to plugins. The trade is transparency and multi-version support against speed.
Who is it for?
nodenv is worth choosing when you need several Node.js versions on one machine and you want to be able to read exactly what happens on every invocation, because the whole mechanism is a shim on your PATH and a text file in your project. It is worth avoiding if you want fast version switching, since the shim adds a process on the path of every `node` call, or if you want automatic version switching as you change directories.
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 2 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 October 7, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Shims on the PATH and a version file per project

The mechanism is described plainly in the README and is worth understanding before installing anything. After nodenv injects itself into your PATH at installation time, any invocation of `node`, `npm`, `npx` or other Node.js related executable first activates nodenv. Then nodenv scans the current project directory for a file named `.node-version`. If found, that file determines the Node.js version to use within that directory. Finally nodenv looks up that version among those installed under `~/.nodenv/versions/`.

Four steps on every single invocation of `node`. That is the honest picture, and it is where the tool's trade-off lives. There is no daemon watching your working directory and no binary rewritten on `cd`; there is a shim that runs, reads a file, and execs the real binary. Transparent and easy to debug, and slower than alternatives that patch your shell prompt or symlink directly.

Choosing a version for a project writes the file:

sh
cd myproject
# choose Node.js version 24.1.0:
nodenv local 24.1.0

Because the file lives in the project directory, a second project can pin a different version and the switch happens as you move between directories, with no command to remember.

Everything real lives in a plugin

The README states that almost every aspect of nodenv's mechanism is customizable via plugins written in Bash. That is the single most important sentence for understanding the project, because it explains both its flexibility and its assembly-required reputation.

The clearest example is installation of Node.js itself. The `nodenv install` command does not ship with nodenv out of the box; it is provided by the node-build plugin. So the tool you use to manage Node versions cannot install Node versions until you install a separate plugin.

sh
# list latest stable versions:
nodenv install -l

# list all local versions:
nodenv install -L

# install a Node.js version:
nodenv install 24.1.0

If `nodenv install` is not found, the README gives the plugin install as a clone into the plugins directory under the nodenv root.

Before attempting any install the README also points you at the build environment documentation and tells you to check that your machine has the necessary tools and libraries. That is a real warning: compiling Node.js from source needs a toolchain, and a machine missing it will fail in a way that looks unrelated to a version manager.

The trade is worth stating plainly. A plugin architecture in Bash means you can read every step and modify it, and it means you assemble the tool you actually want rather than accepting a fixed feature set. It also means the install step for Node is a separate download that can drift from nodenv itself.

Installing it, platform by platform

The README recommends package managers where available and falls back to a Git checkout otherwise. On macOS or Linux with Homebrew, the recommended command is a single line.

sh
brew install nodenv

On Debian, Ubuntu and their derivatives there is a caution instead of a command: nodenv is presently not available in the Debian or Ubuntu package repositories, and the project asks you to install using Git or consider contributing a package. The same caution appears for Fedora. There is a commented-out block in the README showing the `dnf` command that would exist if the package were available, which is a small but honest signal about how thin the distro packaging situation is.

Arch Linux and its derivatives have an AUR package, which the README points at along with the Arch wiki page for installing AUR packages.

The Git checkout path installs into your home directory with no system-wide changes.

sh
git clone https://github.com/nodenv/nodenv.git ~/.nodenv

Then you run `nodenv init`, which sets up your shell to load nodenv, and restart the terminal so the changes take effect. The README also mentions nodenv-installer as a more automated path, while noting that if you would rather not execute a script downloaded from a web URL, the manual checkout is the alternative.

Shell completions differ per shell, which is a small sign of the design

The completion story is a good illustration of how much of nodenv is shell-specific integration, and it is documented with unusual care.

Bash and fish completions ship with the project and are loaded by the `nodenv init` mechanism. Zsh is the odd one out: the script ships with the project but has to be added to `FPATH` before Zsh can discover it, which means editing your `.zshrc`.

sh
# assuming that nodenv was installed to `~/.nodenv`
FPATH=~/.nodenv/completions:"$FPATH"

autoload -U compinit
compinit

That manual step is a genuine papercut for Zsh users, and it is worth knowing before you install rather than discovering it when tab completion silently does nothing. The `completions/` directory at the repository root is where these scripts live.

The fish setup is also handled differently. Where Bash and Zsh write a line into a shell profile, the fish instructions pipe the output of `nodenv init - fish` into `source` and append that to `~/.config/fish/config.fish`. The fish completion script was actually broken at some point: version 1.6.2, published on 2025-07-30, includes a restore fish completion change alongside a StepSecurity best-practices update.

A Bash project published through npm

The repository is a Shell project with a `package.json`, which sounds odd until you read what the manifest is for. The package is `@nodenv/nodenv`, version 1.6.2, licensed MIT, and the npm homepage points back at the repository README.

The `directories` mapping explains the layout: `libexec` is the implementation, `share/man` holds the man page, `nodenv.d` is the hooks directory, `completions` holds the completion scripts, and `test` holds the tests. The `bin` entry maps the `nodenv` command to `libexec/nodenv`.

Using npm here buys distribution rather than runtime. Homebrew, Arch and the npm registry are all channels, which is why the README carries badges for GitHub releases, Homebrew and npm versions side by side.

The test suite is `bats`, the Bash testing framework, run as `bats ${CI:+--tap} test`, with `bats-assert` and `bats-support` as dev dependencies. For a shell project that is a sensible choice, since testing shell logic requires a shell test framework and `bats` is the established one. The man pages are generated from AsciiDoc with `asciidoctor` during the `prepare` script, and a `version:sync` step rewrites a version string inside `libexec/nodenv---version` so the binary reports the right version after an npm version bump.

That version sync step is a small example of the maintenance tax of distributing a shell tool through a JavaScript registry: the version lives in three places and a Perl one-liner keeps them in step.

What nodenv gives up, and how it compares

The README is upfront that nodenv has downsides alongside its simplicity, and links to a comparison of version managers. The real comparison is with nvm, which shares the general goal but differs in mechanism.

nvm works by shell function rather than shim. When you run `node`, a function in your shell resolves the version and hands off. That is why switching directories with nvm can update the active version automatically, and why it tends to feel faster, since there is no extra executable to run on the path of every call. nodenv's choice of shims is what makes it work identically in subshells, in scripts, and in contexts where a shell function is not defined, which is the trade the project makes deliberately.

The other difference is the plugin split. nvm includes installation of Node versions in the tool itself. nodenv delegates that to node-build, which means a working setup is nodenv plus at least one plugin, assembled from more than one repository.

If you need automatic switching as you `cd` between directories, or you want the smallest possible latency on `node`, nodenv is the slower and less automatic choice. If you need predictable behaviour in CI, in Docker builds, or anywhere a shell function will not be loaded, the shim approach is the safer one. The MIT licence keeps the legal question simple, and the last push was on 2026-09-14, with the newest tagged release being v1.6.2 from 2025-07-30, so commits are landing ahead of releases.

Editorial conclusion

nodenv is worth choosing when you need several Node.js versions on one machine and you want to be able to read exactly what happens on every invocation, because the whole mechanism is a shim on your PATH and a text file in your project. It is worth avoiding if you want fast version switching, since the shim adds a process on the path of every `node` call, or if you want automatic version switching as you change directories. What the README settles is the install path per platform, including which distributions have no package at all, and the fact that `nodenv install` is not built in but comes from the node-build plugin. What it does not settle is the detail of what `nodenv init` writes into your shell files, which is explained in a section beyond what the front page carries. Start with `nodenv init`, then read the `nodenv.d/` hook directory and the `completions/` scripts, which are the two places the real behaviour lives.

Frequently asked questions

How do I install nodenv?

With Homebrew on macOS or Linux, run `brew install nodenv`, then run `nodenv init` and restart your terminal. On systems without a package, clone the repository to `~/.nodenv` with `git clone https://github.com/nodenv/nodenv.git ~/.nodenv` and run `~/.nodenv/bin/nodenv init`. The README notes nodenv is not currently in the Debian, Ubuntu or Fedora repositories.

Why does the nodenv install command not work out of the box?

Because it is not part of nodenv. The `nodenv install` command is provided by the separate node-build plugin, which you clone into the plugins directory under the nodenv root. Before installing any version the README also tells you to check that your machine has the build tools Node.js compilation needs, since node-build compiles from source.

How do I set a different Node version for each project?

Run `nodenv local` with a version in the project directory, which creates or updates a `.node-version` file there. The README's example uses `nodenv local 24.1.0` after changing into the project. For a machine-wide default, `nodenv global 24.1.0` is the equivalent command.

Why is tab completion not working in Zsh?

The Zsh completion script ships with the project but Zsh must find it through `FPATH`, so it does not load the way the Bash and fish scripts do. You need to add `FPATH=~/.nodenv/completions:"$FPATH"` to your `.zshrc`, then run `autoload -U compinit` and `compinit`. The fish and Bash scripts are loaded by the `nodenv init` mechanism instead.

Official sources

  1. Issues
  2. License: MIT
  3. nodenv/nodenv on GitHub
  4. README
  5. Releases
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.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/nodenv-nodenv.svg)](https://hysenlabs.com/projects/nodenv-nodenv)