TL;DR:

  • mise replaces nvm, pyenv, rbenv, sdkman, and asdf with one tool that manages Node, Python, Ruby, Go, Rust, Java, and 200+ more
  • It’s 10–100x faster than asdf, written in Rust, with automatic environment activation per directory
  • The .mise.toml config file makes per-project tool versions reproducible across your whole team

Most developers accumulate a small zoo of version managers over time: nvm for Node, pyenv for Python, rbenv for Ruby, maybe sdkman for Java or rustup for Rust. Each one has its own install method, its own config file, its own syntax, and its own idiosyncratic activation hook in your shell config.

mise (pronounced “meese”) replaces all of them. One tool, one config format, one shell hook. And because it’s written in Rust rather than bash, it’s dramatically faster than the asdf-based alternatives it was designed to supersede.

What mise Does

mise manages runtime versions — it downloads and switches between different versions of any language runtime. When you cd into a project directory, mise reads the .mise.toml (or .tool-versions) config file and automatically activates the right versions of Node, Python, or whatever your project needs. No manual nvm use before every project.

Beyond version management, mise has grown into a task runner: you can define project tasks in .mise.toml alongside your tool versions, replacing simple Makefiles or package.json scripts with a unified interface.

Installation

curl https://mise.run | sh

Add activation to your shell (bash, zsh, fish all supported):

# ~/.zshrc or ~/.bashrc
eval "$(mise activate zsh)"
# or for bash:
eval "$(mise activate bash)"

That’s the entire install. No caveats, no PATH manipulation, no legacy shim directories to deal with.

Basic Usage

Install a runtime:

mise install node@22
mise install python@3.12
mise install go@1.22

Set the version globally (your default):

mise use --global node@22
mise use --global python@3.12

Set versions per-project:

cd ~/projects/my-api
mise use node@20
mise use python@3.11

This creates or updates a .mise.toml in the current directory:

[tools]
node = "20"
python = "3.11"

Commit .mise.toml to your repo. Anyone else who uses mise will get the same versions when they enter the project directory — no documentation required, no “it works on my machine.”

The .mise.toml Format

The config file is straightforward TOML:

[tools]
node = "22.3.0"
python = "3.12.4"
go = "1.22.3"

[env]
NODE_ENV = "development"
DATABASE_URL = "postgresql://localhost/myapp_dev"

[tasks.dev]
run = "node server.js"
description = "Start development server"

[tasks.test]
run = "pytest"
description = "Run test suite"

[tasks.build]
run = "npm run build"
description = "Build for production"

The [env] section is one of mise’s most useful features: environment variables defined here are automatically set when you’re in the project directory and unset when you leave it. No more .env files loaded by a patchwork of shell scripts and dotenv packages — your dev environment variables travel with your tool versions.

Tasks: The Built-in Task Runner

mise run dev        # starts development server
mise run test       # runs tests
mise run build      # builds
mise tasks          # lists all available tasks

This isn’t a replacement for complex build systems, but for the 80% of projects where your task runner is a handful of commands in a Makefile or package.json scripts section, mise tasks are cleaner and language-agnostic.

Migrating from asdf

If you’re already using asdf, migration is seamless. mise reads .tool-versions files natively — no conversion step needed. Install mise, activate it, and your existing asdf config files continue to work as-is. Over time you can convert to .mise.toml at your own pace.

If you’re coming from nvm:

# Check what versions nvm was managing
nvm list

# Install the same versions in mise
mise install node@20.18.0
mise install node@22.3.0

# Set your default
mise use --global node@22.3.0

# Remove nvm activation from your shell config
# (the nvm lines in your .zshrc or .bashrc)

Why Faster Matters

asdf’s architecture routes every command through a shim — a small bash script that intercepts calls to node, python, etc. and forwards them to the right version. Shims add latency to every command invocation. In a shell where tab completion calls the binary, or a test runner that spawns thousands of processes, that latency adds up.

mise uses a different approach: it modifies PATH directly when you enter a directory, so binary calls go straight to the right version without a shim in between. Command invocation is essentially as fast as if mise didn’t exist.

On a cold shell startup, mise activation typically adds 1–3ms. The equivalent asdf setup typically adds 40–80ms. On a warm path, the difference matters even more for tools like pre-commit hooks or CI pipelines that run many commands.

The One Gotcha

mise isn’t a complete replacement for tools that manage more than just versions. If you use pyenv-virtualenv for Python virtual environment management, you’ll want to pair mise with standard venv or the newer uv for environment creation. mise handles the Python version; you handle the virtual environment separately.

For most projects this is cleaner anyway — the separation between “which Python version” and “which packages” maps naturally to mise + uv or mise + pip.

Should You Switch?

If you’re managing more than one language runtime and you’re not already on mise or asdf, yes. The tooling quality is high, the migration path is gentle, and the .mise.toml team config file alone is worth the switch — it replaces a section of onboarding documentation with a file that just works.

If you’re happily using asdf, the case is weaker but still there: faster startup, a cleaner config format, and the task runner integration. Migration is low-friction. Most developers who try mise don’t go back.