Manage Node, Python and Go versions per project with mise
Pin Node, Python and Go versions per project with mise: install, shell activation, mise use, mise.toml vs .tool-versions, and moving off asdf or nvm.
Updated
in short
Install mise, add mise activate to your shell rc file, then run mise use node@22 python@3.12 go@1.23 in a project. mise writes the versions to mise.toml and switches them when you cd into the directory.
mise installs language runtimes and other dev tools, records which versions a project needs in a config file, and puts the right ones on PATH per directory. One tool covers what nvm, pyenv and a Go version manager do separately, and it reads asdf's .tool-versions files.
The same mise.toml can also hold environment variables and project tasks. This guide focuses on runtime versions and touches on the rest briefly.
Tools used
Before you start
- macOS, Linux or Windows; this guide uses bash, zsh or fish
curlfor the install script, or Homebrew, apt, dnf or another package manager- Remove or plan to remove nvm, asdf or pyenv lines from your shell rc file (step 8)
Steps
Step 1: Install mise
The official script installs a single binary to
~/.local/bin/mise. Homebrew and most Linux package managers also ship mise; the project notes that the Homebrew build is separate from its release binaries.shcurl https://mise.run | sh ~/.local/bin/mise --versionshbrew install miseStep 2: Activate mise in your shell
Activation adds a hook that updates
PATHand environment variables at each prompt, so the versions follow you between directories. Put it in the rc file your interactive shell reads, not~/.bash_profileor~/.zprofile. Use the full path~/.local/bin/miseif that directory is not onPATHyet.Open a new shell and run
mise doctor; it should reportactivated: yes. With Homebrew and fish, mise activates itself and you can skip this step.zsh# ~/.zshrc eval "$(mise activate zsh)"bash# ~/.bashrc eval "$(mise activate bash)"fish# ~/.config/fish/config.fish mise activate fish | sourceshmise doctorStep 3: Pin versions for a project with
mise useFrom the project root,
mise useinstalls each tool and writes the request tomise.toml. A partial version such as22selects the newest installed or available 22.x release; it is not an exact pin. Add--pinto write the full resolved version instead.For Go 1.20 and older, where
1.20is itself a release, usego@prefix:1.20to get the latest 1.20.x.shcd ~/code/my-app mise use node@22 python@3.12 go@1.23toml# mise.toml (written by the command above) [tools] go = "1.23" node = "22" python = "3.12"shmise ls --current node --versionStep 4: Install a project's tools after cloning
Commit
mise.toml. On another machine, or for a teammate,mise installinstalls everything the config declares without changing it. Review config files from other people first: tasks, hooks and some[env]directives can run code, andmise trustmarks a reviewed file as trusted.shgit clone https://github.com/example/my-app && cd my-app mise install mise exec -- node --versionStep 5: Choose between
mise.tomland.tool-versionsmise.tomlis the native format and the one to use unless teammates still run asdf. It supports[env],[tasks]and tool options..tool-versionsis asdf's format; mise reads it and can write it with--path, using the asdf names (nodejs,golang). If both files exist in a directory,mise.tomlwins for the tools it declares.mise can also read per-language files such as
.nvmrc,.node-versionand.python-version, but that is off by default. Enable it per tool.sh# keep a shared .tool-versions for asdf users mise use --path .tool-versions --pin node@22sh# read .nvmrc / .node-version and .python-version mise settings add idiomatic_version_file_enable_tools node mise settings add idiomatic_version_file_enable_tools pythonStep 6: Set global defaults
mise use -gwrites to~/.config/mise/config.toml. Those versions apply anywhere a project config does not override them, for example in your home directory or in repos without amise.toml.shmise use -g node@lts python@3.12 go@latestStep 7: Add environment variables and tasks
The
[env]table sets variables while you are in the project;_.fileloads a.envfile, and for Python_.python.venvcreates and activates a virtualenv.[tasks]defines commands you run withmise run, which get the project's tools and environment.toml# mise.toml [env] NODE_ENV = "development" _.file = ".env" _.python.venv = { path = ".venv", create = true } [tasks.test] description = "Run the test suite" run = "npm test"shmise run test mise tasks lsStep 8: Migrate from asdf or nvm
From asdf: mise reads existing
.tool-versionsfiles, so runmise installin each project and check it withmise exec -- node --version. mise does not reuse asdf's install directory. Once that works, delete the asdf lines and its shims directory fromPATHin your rc file.From nvm: enable
.nvmrcsupport (step 5), set a default withmise use -g node@lts, and remove theNVM_DIRexport and the line that sourcesnvm.sh. Globally installed npm packages do not carry over; reinstall the ones you need.Open a new shell and confirm with
mise doctorandtype -a node.
Troubleshooting
- Versions do not change when you
cd:mise activateis in a profile file, not your rc file. Move it to~/.bashrcor~/.zshrc. - The wrong
noderuns: comparemise ls --current node,mise which nodeandtype -a node. Leftover nvm or asdf lines usually come first onPATH. - A script cannot find a tool right after
mise install: installing does not change the running shell. Usemise exec -- <command>. - A project config seems ignored: it may be untrusted.
mise doctorlists untrusted files;mise trustmarks a reviewed one as trusted. .nvmrcor.python-versionis ignored: idiomatic version files are off by default. Enable them withmise settings add idiomatic_version_file_enable_tools <tool>.mise use go@1.20installs 1.20.0 instead of the latest patch: usego@prefix:1.20for Go 1.20 and older.
asdf commands in mise
asdf install nodejs 22.11.0becomesmise install node@22.11.0.asdf set nodejs 22.11.0(asdf 0.16+) becomesmise use node@22.11.0, which also installs it.asdf list all nodejsbecomesmise ls-remote node.asdf currentbecomesmise ls --current.asdf which nodebecomesmise which node;asdf reshimbecomesmise reshim.mise setwrites environment variables, not tool versions; usemise usefor versions.
Tips
- Put personal overrides in
mise.local.tomland add it to.git/info/excludeso it stays out of commits. mise use --dry-run node@22shows which file would change before it writes anything.mise exec -- <command>andmise runwork without activation, which suits CI and cron.
Also worth a look
FAQ
- What is the difference between `mise use` and `mise install`?
- `mise use` installs a tool and writes the version to a config file. `mise install` only installs what the config already declares, or a version you name, without editing any file.
- Do I have to activate mise?
- No. `mise exec -- <command>` and `mise run <task>` load the project's tools and environment for one command. Activation is for interactive shells, so plain `node` or `python` picks the right version.
- Can teammates on asdf share the project?
- Yes, if you keep versions in `.tool-versions` and avoid mise-only syntax in it. mise reads the same file, and `mise use --path .tool-versions` writes to it.