Managing different versions of programming languages and command-line tools across projects is a recurring pain. One project needs Node 18, another Node 22, a third pins Python 3.11 while your system ships 3.12. Traditionally we solved this with a zoo of single-purpose version managers like nvm, pyenv, rbenv, sdkman or asdf.
mise (pronounced "meez", short for mise-en-place) replaces all of them with a single, fast tool written in Rust. It manages tool versions, per-project environment variables and project tasks – roughly asdf + direnv + a task runner in one binary.
In the following tutorial I’m going to demonstrate how to install mise, manage tool versions per project, define environment variables and run project tasks.
Prerequisites
mise ships as a single binary and can be installed in several ways. The quickest is the install script:
curl https://mise.run | sh
Alternatively, use your package manager:
brew install mise
After installing, activate mise in your shell so it can manage your PATH. Add the appropriate line to your shell config:
echo 'eval "$(mise activate bash)"' >> ~/.bashrc
echo 'eval "$(mise activate zsh)"' >> ~/.zshrc
echo 'mise activate fish | source' >> ~/.config/fish/config.fish
Restart your shell and verify the installation:
mise --version
Output
Running the command above should produce a similar output:
2026.8.14 macos-arm64 (2026-08-25)
Managing Tool Versions
mise stores tool configuration in a mise.toml file at the root of your project. The cleanest way to add a tool is with mise use, which installs the tool and writes it to the config in one step.
mise use node@22
mise use python@3.12
mise use rust@1.86
This creates or updates a mise.toml in the current directory:
[tools]
node = "22"
python = "3.12"
rust = "1.86"
As soon as you cd into the directory, mise activates exactly these versions. Leaving the directory restores your previous environment.
We can inspect which tools are active in the current directory:
mise ls --current
Output
Running the command above should produce a similar output:
node 22.23.2 ~/project/mise.toml 22
python 3.12.9 ~/project/mise.toml 3.12
rust 1.86.0 ~/project/mise.toml 1.86
To install every tool declared in a project’s config in one go – for example after cloning a repository – simply run:
mise install
Pinning Exact Versions
The example above uses fuzzy versions like node = "22", which resolve to the latest matching release. For reproducible builds you often want an exact version pinned across the whole team:
[tools]
node = "22.14.0"
python = "3.12.9"
You can also set a global default that applies whenever no project config is found:
mise use --global node@lts
Managing Environment Variables
mise can load environment variables per project, replacing tools like direnv. Variables are declared in the [env] section of mise.toml and are only set while you are inside the project directory.
[tools]
node = "22"
[env]
NODE_ENV = "development"
DATABASE_URL = "postgres://localhost:5432/myapp"
_.file = ".env"
The special _.file directive loads additional variables from a dotenv file – handy for secrets you don’t want to commit. mise also supports composing variables and prepending to the PATH:
[env]
BIN_DIR = "{{config_root}}/bin"
_.path = ["{{config_root}}/bin", "{{config_root}}/node_modules/.bin"]
We can verify that the variables are set inside the project:
mise env
Output
Running the command above should produce a similar output:
export DATABASE_URL='postgres://localhost:5432/myapp'
export NODE_ENV=development
export PATH='/home/user/project/bin:/home/user/project/node_modules/.bin:...'
Running Project Tasks
Beyond tool and environment management, mise doubles as a lightweight task runner. Tasks are defined in the [tasks] section and executed with mise run.
[tools]
node = "22"
[tasks.build]
description = "Build the project"
run = "npm run build"
[tasks.test]
description = "Run the test suite"
run = "npm test"
[tasks.ci]
description = "Run build and tests"
depends = ["build", "test"]
Tasks run inside the fully configured environment, so the correct tool versions and environment variables are always in place. List the available tasks with:
mise tasks
Output
Running the command above should produce a similar output:
build Build the project
ci Run build and tests
test Run the test suite
Then run a task – dependencies are resolved automatically:
mise run ci
Output
Running the command above should produce a similar output:
[build] $ npm run build
[test] $ npm test
[build] ...
[build] Finished in 1.2s
[test] ...
[test] Finished in 3.4s
Finished in 4.6s
Migrating from asdf, nvm and Friends
Because mise is compatible with the asdf plugin ecosystem and understands existing version files, migration is usually painless. mise reads legacy files like .tool-versions, .nvmrc or .python-version out of the box, so an existing project often works without any changes.
To adopt mise gradually, you can enable reading of these idiomatic version files:
[settings]
idiomatic_version_file_enable_tools = ["node", "python"]
When you’re ready, convert an existing .tool-versions to a native mise.toml and remove the old per-tool managers from your shell config.