Install
# Linux / macOS
curl https://mise.run | sh
# Or via Homebrew
brew install mise
# Or with cargo
cargo install mise
# Verify
mise --version
Then activate the shell hook. mise needs to know when you cd into a directory with a different config so it can swap the active runtimes:
# bash
echo 'eval "$(mise activate bash)"' >> ~/.bashrc
# zsh
echo 'eval "$(mise activate zsh)"' >> ~/.zshrc
# fish
echo 'mise activate fish | source' >> ~/.config/fish/config.fish
Re-open the terminal; which node now resolves to ~/.local/share/mise/shims/node.
Per-project runtime versions
cd ~/code/my-project
mise use node@22
mise use python@3.13
mise use ruby@3.4
This writes (or updates) a mise.toml in the current directory:
# mise.toml
[tools]
node = "22"
python = "3.13"
ruby = "3.4"
Subsequent cd into this directory will activate exactly those versions. Subsequent cd out switches back to the global defaults. mise.local.toml can override the committed file for developer-specific tweaks — gitignored by default.
Install requested versions
mise install # install everything declared in mise.toml
mise ls # show what's installed and what's active
mise outdated # show updates available
mise upgrade # bump all to the latest matching the constraint
Versions live under ~/.local/share/mise/installs/ — one tree per tool per version, shared across projects.
Compatibility with existing files
mise reads (and writes) several legacy formats so migrating from another tool is one command:
.nvmrc,.node-versionfor Node.python-versionfor Python (compatible with pyenv).ruby-versionfor Ruby (compatible with rbenv, chruby).tool-versionsfor everything (compatible with asdf)
Existing projects keep their .python-version and mise honours it. New projects use mise.toml for the multi-tool case.
Plugins and the asdf compatibility layer
mise ships built-in support for the most common runtimes via its core plugins (Rust-backed, fast). Beyond that, mise can use the entire asdf plugin ecosystem — hundreds of community plugins for anything obscure (Elixir, Erlang, Crystal, Lua, Terraform, kubectl, helm, etc.):
mise plugins ls-remote # list all available
mise plugins install terraform
mise use terraform@1.10.0
For pre-built binaries via aqua, ubi, or vfox backends, mise handles those too — declared in mise.toml with a backend prefix:
[tools]
"ubi:cli/cli" = "latest" # GitHub CLI from github.com/cli/cli
"aqua:cli/cli" = "latest"
"npm:typescript" = "5.3"
"pipx:black" = "latest"
Environment variables per project
This is the direnv-replacement feature:
# mise.toml
[env]
DATABASE_URL = "postgres://localhost/dev"
RAILS_ENV = "development"
_.path = ["./bin", "./scripts"] # prepend to PATH
[env.development] # only when MISE_ENV=development
DEBUG = "1"
cd into the directory and the environment variables are set; cd out and they're unset. mise env prints the effective env. mise env --shell bash emits shell-quoted exports for scripting.
The _.file = ".env" directive in [env] reads a dotenv file with sane variable expansion — the missing piece of plain source .env.
Tasks: one tool, one place
mise can also be a Makefile/npm-script replacement:
# mise.toml
[tasks.build]
run = "cargo build --release"
[tasks.test]
run = "cargo test"
depends = ["build"]
[tasks.lint]
run = [
"cargo fmt --check",
"cargo clippy -- -D warnings",
]
[tasks.ci]
depends = ["lint", "test"]
mise run build
mise run ci
mise tasks ls
Compared to make, the syntax is YAML-ish instead of tab-sensitive; compared to npm run, it works for any language. Compared to just, it shares mise's per-project env so tasks see the right runtime versions automatically.
Trust model
By default mise refuses to activate a config file unless it's explicitly trusted:
cd untrusted-repo
# mise: ~/.../mise.toml is not trusted. Use `mise trust` to trust this file.
mise trust # trust the current project
mise trust --untrust # revoke trust
This prevents a freshly-cloned repo from running arbitrary commands via a [hooks] section the moment you cd into it. The trust state lives in ~/.local/share/mise/trusted_configs.txt.
In CI
# GitHub Actions
- uses: jdx/mise-action@v2
with:
install: true
cache: true
Or a manual install:
curl https://mise.run | sh
echo "$HOME/.local/bin" >> $GITHUB_PATH
mise install
mise run ci
The cache mode hashes mise.toml and the relevant lockfiles to skip the install step when nothing has changed — substantially faster runs.
When to stay with the per-language tool
- If the team is happy with their current per-language tool (nvm, pyenv) and works in only one language, the migration friction isn't worth it.
- For very specific PATH ordering needs or shimming quirks, the language-native tool's escape hatches may be more accessible.
- For builds where the runtime is a system package on every host (NixOS, distro Python on a CI runner), version management isn't the bottleneck.
Outside those cases, mise is the easiest "I just want the right version of everything, fast" pick available in 2026. It's the second tool worth installing on a new dev machine (after a shell setup; before any language).