docs/dev-tools/shims.md
There are several ways for the mise context (dev tools, environment variables) to be loaded into your shell:
mise activate (also called "mise PATH activation") where mise updates your PATH and other environment variables every time your prompt is displayed.mise activate --shims which uses shims to load dev tools.mise x|exec or mise r|run for ad-hoc commands or tasks (see "neither shims nor PATH").This page will help you understand the differences between these methods and how to use them. In particular, it will help you decide if you should use shims or mise activate in your shell.
mise activation methods {#overview}Mise's "PATH" activation method updates environment variables every time the prompt is displayed. In particular, it updates the PATH environment variable, which is used by your shell to search for the programs it can run.
::: info
This is the method used when you add the echo 'eval "$(mise activate bash)"' >> ~/.bashrc line to your shell rc file (in this case, for bash).
:::
For example, by default, your PATH variable might look like this:
echo $PATH
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
If using mise activate, mise will automatically add the required tools to PATH.
PATH="$HOME/.local/share/mise/installs/python/3.15.0/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"
In this example, the python bin directory was added at the beginning of the PATH, making it available in the current shell session.
When a fuzzy version like python = "3.15" or node = "26" is active, this path may use the requested-version symlink, such as ~/.local/share/mise/installs/python/3.15/bin, instead of the fully resolved patch version.
While the PATH design of mise works great in most cases, there are some situations where shims are preferable. This is the case when you are not using an interactive shell (for example, when using mise in an IDE or a script).
::: warning
mise activate --shims does not support all the features of mise activate.
See shims vs path for more information. :::
When using shims, mise places small executables (shims) in a directory that is included in your PATH. You can think of shims as symlinks to the mise binary that intercept commands and load the appropriate context.
ls -l ~/.local/share/mise/shims/node
# [...] ~/.local/share/mise/shims/node -> ~/.local/bin/mise
By default, the shim directory is located at ~/.local/share/mise/shims (on Windows: %LOCALAPPDATA%\mise\shims). When installing a tool (for example, node), mise will add some entries for every binary provided by this tool in the shims directory (for example, ~/.local/share/mise/shims/node).
mise use -g node@20
npm install -g [email protected]
~/.local/share/mise/shims/node -v
# v20.0.0
~/.local/share/mise/shims/prettier -v
# 3.1.0
To avoid calling ~/.local/share/mise/shims/node, you can add the shims directory to your PATH.
export PATH="$HOME/.local/share/mise/shims:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"
This will effectively make all dev tools available in your current shell session as well as non-interactive environments.
Set lazy = true on a tool when it should be installed the first time one of
its commands is invoked instead of by a bare mise install:
[tools]
node = { version = "24", lazy = true }
For registry shorthands, mise creates bootstrap shims from the registry's
bins metadata. Explicit backends and tools that are not in the registry must
declare their command names with lazy_bins:
[tools]
"github:example/acme" = { version = "1.2.3", lazy = true, lazy_bins = ["acme", "acmectl"] }
Run mise reshim after editing a lazy declaration directly. Commands such as
mise use that update tool configuration rebuild the farm automatically.
Invoking a lazy shim installs only its configured provider and then executes
it. This is independent of not_found_auto_install; an explicit project tool
selection is never bypassed by a lower-precedence lazy declaration.
A bare mise install skips missing lazy tools. Pass --include-lazy to install
all configured tools including lazy declarations, or name one explicitly, such
as mise install node, to install only that lazy tool immediately. Once
installed, normal mise activate places the real tool path ahead of the shim
farms, so later calls have no shim dispatch overhead. mise activate --shims
remains project-aware and dispatches every call through mise by design.
::: tip
mise activate --shims is a shorthand for adding the shims directory to PATH.
:::
The recommended way to add shims to PATH is to call mise activate --shims in one of your shell initialization file. For example, you can do the following:
::: code-group
# note that bash will read from ~/.profile or ~/.bash_profile if the latter exists
# ergo, you may want to check to see which is defined on your system and only append to the existing file
echo 'eval "$(mise activate bash --shims)"' >> ~/.bash_profile # this sets up non-interactive sessions
echo 'eval "$(mise activate bash)"' >> ~/.bashrc # this sets up interactive sessions
echo 'eval "$(mise activate zsh --shims)"' >> ~/.zprofile # this sets up non-interactive sessions
echo 'eval "$(mise activate zsh)"' >> ~/.zshrc # this sets up interactive sessions
echo 'mise activate fish --shims | source' >> ~/.config/fish/config.fish
echo 'mise activate fish | source' >> ~/.config/fish/config.fish
:::
In this example, we use mise activate --shims in the non-interactive shell configuration file (like .bash_profile or .zprofile) and mise activate in the interactive shell configuration file (like .bashrc or .zshrc)
::: info
It's fine to call mise activate --shims in your shell profile file and then
later call mise activate in an interactive session. PATH
activation keeps the user and existing system shim farms behind real tool paths
when the effective toolset contains a lazy declaration or
not_found_auto_install is enabled. Without either behavior, full activation
removes the shim farms as before. This makes lazy bootstrap commands available
without adding dispatch overhead after installation. not_found_auto_install
still controls general missing-tool installation, but does not disable an
explicit lazy = true declaration.
:::
::: info
When a shim cannot resolve a mise-managed tool (for example, a version pinned in mise.toml that hasn't
been installed and not_found_auto_install is
disabled), it falls back to the first same-named executable found elsewhere on PATH rather than erroring.
This is convenient for tools you also want available outside of mise, but for a tool the OS also ships
(python3 on Debian/Ubuntu, for example) it means the shim can silently run a completely different,
unrelated binary instead of failing loudly.
Set not_found_system_fallback to false,
alongside not_found_auto_install = false, if you'd rather an unresolvable shim fail outright.
:::
shims if you prefer, though this comes with some limitations.mise activate --shims is to use export PATH="$HOME/.local/share/mise/shims:$PATH". This can be helpful if mise is not yet available at that point in time.To force mise to update the content of the shims directory, you can manually call mise reshim.
Use mise reshim --system for the system shim farm. If shims_dir and
system_shims_dir resolve to the same physical path, either command reconciles
one combined farm containing both scopes.
Note that mise already runs a reshim anytime a tool is installed/updated/removed, so you don't need to use it for those scenarios. It is also done by default when using most tools such as npm.
mise reshim only creates/removes the shims. Some users sometimes use it as a
"fix it" button, but it is only necessary if ~/.local/share/mise/shims doesn't contain something it should.
Do not add additional executable in the mise directory, mise will delete them with the next reshim.
Use [wrappers] when a command should always pass through another program while
keeping its ordinary name. For example, this routes every cargo invocation
through Mr Boxington:
[tools]
mr-boxington = "1.2.0"
[wrappers.cargo]
command = "mbx"
env = { MBX_CARGO_SHIM_MODE = "1" }
Run mise reshim after adding or removing a wrapper. The wrapper is available
with both mise activate and mise activate --shims, and takes precedence over
an executable with the same name. When it delegates, mise removes its dispatch
directories from PATH, so mbx resolves Cargo from mise-managed Rust when
configured and otherwise falls through to rustup or the system installation.
A short form is available when no arguments or environment variables are needed:
[wrappers]
terraform = "tofu"
The detailed form can insert arguments before those supplied by the user:
[wrappers.python]
command = "uv"
args = ["run", "python"]
The following features are affected when shims are used instead of PATH activation:
which command points to the shim, obscuring the real executableIn general, using PATH (mise activate) instead of shims for interactive situations is recommended.
The way activate works is every time the prompt is displayed, mise-en-place will determine what PATH and other
env vars should be and export them. This is why it doesn't work well for non-interactive situations like scripts. The prompt never gets displayed so you have to manually call mise hook-env to get mise to update
the env vars. (though there are exceptions, see hook on cd)
A downside of shims is that the environment variables are only loaded when a shim is called. This means if you
set an environment variable in mise.toml, it will only be used when a shim is called.
The following example only works under mise activate:
$ mise set NODE_ENV=production
$ echo $NODE_ENV
production
But this will work in either:
$ mise set NODE_ENV=production
$ node -p process.env.NODE_ENV
production
Also, mise x|exec and mise r|run can be used to get the environment even if you don't need any mise tools:
$ mise set NODE_ENV=production
$ mise x -- bash -c "echo \$NODE_ENV"
production
$ mise r some_task_that_uses_NODE_ENV
production
::: tip In general, tasks are a good way to ensure that the mise environment is always loaded. :::
The hooks cd, enter, and leave only trigger with mise activate. The separate watch_files configuration also requires mise activate. However preinstall and postinstall still work with shims because they don't require shell integration.
whichwhich is a command that a lot of users find great value in. Using shims effectively "break" which and cause it to show the location of the shim. A workaround is to use mise which, which will show the actual location. Some users prefer the "cleanliness" of running which node and getting back a real path with a version number inside of it. e.g:
$ which node
~/.mise/installs/node/20/bin/node
Truthfully, you're probably not going to notice a difference in performance when using shims vs. using mise activate.
mise activate, you'll pay a few ms cost
every time the prompt is displayed. Regardless of whether you're actively using a mise tool, you'll
pay that penalty every time you run any command. It does have some short-circuiting logic to make it faster
if there are no changes, but it doesn't help much unless you have a very complex setup.If you are calling a shim from within a bash script like this:
for i in {1..500}; do
node script.js
done
You'll pay the mise penalty every time you call it within the loop. However, if you did the same thing but call a subprocess from within a shim (say, node creating a node subprocess), you will not pay a new penalty. This is because when a shim is called, mise sets up the environment with PATH for all tools and those PATH entries will be before the shim directory.
In other words, which is better in terms of performance just depends on how you're calling mise. Really
though most users will not notice a few ms lag on their terminal caused by mise activate.
See Troubleshooting: Slow shell prompts for how to diagnose performance issues.
The only difference between these would be that using hook-env you will need to call
it again if you change directories but with shims that won't be necessary. If you use both, mise activate
takes care of the shim farms for you: they are kept behind the tool paths as a fallback. Disabling
not_found_auto_install disables general missing-tool
installation, but explicit lazy = true declarations remain available through their shims.
There are many ways to load the mise environment that don't require either, chiefly:
mise x|exec, mise r|run or mise en.
These will both load all the tools and env vars before executing something. This might be ideal because you don't need to modify your shell rc file at all and the environment is always loaded explicitly. Some might find this is a "clean" way of working.
The obvious downside is that anytime one wants to use mise they need to prefix it with mise exec|run. Though, you can easily alias them to mx|mr.
cd {#hook-on-cd}For some shells (bash, zsh, fish, xonsh), mise hooks into the cd command, while in others, it only runs when the prompt is displayed. This relies on chpwd in zsh, PROMPT_COMMAND in bash, fish_prompt in fish, and on_chdir in xonsh.
The upside is that it doesn't run as frequently but since mise is written in Rust the cost for executing mise is negligible (a few ms).
::: details Running several commands in a single line
If you run a set of commands in a single line like the following:
cd ~
cd ~/src/proj1 && node -v && cd ~/src/proj2 && node -v
If using mise activate, in shell without hook on cd, this will use the tools from ~, not from ~/src/proj1 or ~/src/proj2 even after the directory changed.
This is because, in these shells mise runs just before your prompt gets displayed whereas in others, it hooks on cd. Note that shims will always work with the inline example above.
:::
rc files like .zshrc are unusual. It's a script but also runs only for interactive sessions. If you need
to access tools provided by mise inside of an rc file you have 2 options:
::: code-group
eval "$(mise activate zsh)"
eval "$(mise hook-env -s zsh)"
node some_script.js
eval "$(mise activate zsh --shims)" # should be first
eval "$(mise activate zsh)"
node some_script.js
:::