Environments & Secrets
This is the job you used to give direnv: set environment variables when you
enter a project, unset them when you leave. mise does it from the same
mise.toml that pins your tools — no second config file, no .envrc.
The [env] block
Section titled “The [env] block”[env]NODE_ENV = "development"PORT = "3000"DATABASE_URL = "postgres://localhost/app_dev"cd into the project and these are exported; cd out and they’re gone. Plain
strings cover most of what you need.
Three modifiers handle the edge cases:
[env]LOG_LEVEL = { default = "info" } # use this UNLESS already set in the shellAPI_TOKEN = { required = true } # error if unset — fail fast, not at runtimeCI = false # actively UNSET an inherited variabledefault— a fallback that an existing shell value overrides.required— mise errors when you enter the dir if the var isn’t set elsewhere.false— removes the variable (handy to scrub an inheritedCI/DEBUG).
_.path — extend PATH per project
Section titled “_.path — extend PATH per project”[env]_.path = "./bin" # relative → resolved from config_root# _.path = ["./bin", "./node_modules/.bin"]Prepends project-local binaries to PATH without touching your global setup.
Relative paths are anchored at the config file’s directory, not your cwd.
_.file — load dotenv / JSON / YAML / TOML
Section titled “_.file — load dotenv / JSON / YAML / TOML”[env]_.file = ".env" # dotenv by defaultThe format is inferred from the extension — .env, .json, .yaml, .toml
all work. Use the table form for options:
[env]_.file = { path = ".env.secret", redact = true } # hide values in `mise env`_.file = { path = ".env.tools", tools = true } # load AFTER tools installredact = true— values are masked inmise env/ logs.tools = true— defers loading until tools are installed, so the file can reference tool-provided binaries.
_.source — run a shell script and import its exports
Section titled “_.source — run a shell script and import its exports”[env]_.source = "./scripts/env.sh" # bash; its `export`s become mise envFor when an env value has to be computed in shell. This executes code, so it’s
trust-gated — mise won’t run it until you
mise trust.
Ordering and [[env]]
Section titled “Ordering and [[env]]”[env] entries within a block are unordered; when one value must reference
another, use the array-of-tables form so blocks apply in sequence:
[[env]]_.file = ".env"[[env]]DATABASE_URL = "{{env.DB_HOST}}/app" # DB_HOST from the .env loaded aboveTemplating with Tera
Section titled “Templating with Tera”mise renders config through Tera. The default
way to reference another variable is {{env.X}} — not $X:
[env]PROJECT = "{{config_root | basename}}" # the project dir nameCACHE_DIR = "{{config_root}}/.cache" # absolute path to this configGIT_SHA = "{{ exec(command='git rev-parse --short HEAD') }}"EDITOR = "{{ get_env(name='EDITOR', default='vim') }}"Useful building blocks: vars env, cwd, config_root, mise_bin,
mise_env; functions exec(command=…), get_env(name=…, default=…),
arch(), os(), num_cpus(); filters like basename, dirname, kebabcase.
Config environments (profiles)
Section titled “Config environments (profiles)”One project, several environments — dev, ci, prod. Set MISE_ENV (or
-E/--env) and mise loads the matching files on top of the base:
MISE_ENV=production mise run deploy # also loads mise.production.tomlmise -E ci run test[env]NODE_ENV = "production"LOG_LEVEL = "warn"DATABASE_URL = { required = true } # no localhost default in prodMISE_ENV accepts a comma list (MISE_ENV=ci,prod) — later wins. For a given
environment the files merge highest-to-lowest, key by key:
| Precedence | File | Commit? | Purpose |
|---|---|---|---|
| highest | mise.<env>.local.toml | gitignore | your machine, this env |
mise.local.toml | gitignore | your machine, all envs (per-dev overrides) | |
mise.<env>.toml | commit | shared, this env | |
| lowest | mise.toml | commit | shared base |
flowchart TD base["mise.toml<br/>(committed base)"] --> env["mise.production.toml<br/>(committed, env-specific)"] env --> local["mise.local.toml<br/>(gitignored, your machine)"] local --> envlocal["mise.production.local.toml<br/>(gitignored, your machine + env)"] envlocal --> result["effective [env] + [tools]<br/>(later overrides earlier, key by key)"]
Python virtualenv auto-activation
Section titled “Python virtualenv auto-activation”mise can create and activate a project venv as part of entering the directory —
no source .venv/bin/activate, ever:
[tools]python = "3.13"
[env]_.python.venv = { path = ".venv", create = true } # create if missing, then activatecd in and the venv is active; pip/python resolve inside it. Combined with
the [tools] pin, a new dev gets the right Python and an isolated venv from a
single mise install.
Secrets
Section titled “Secrets”mise can load encrypted secrets so the ciphertext is safe to commit and the plaintext only exists in memory. Both built-in methods are experimental:
[settings]experimental = trueThe decryption key — your age private key — stays out of the repo (default
~/.config/mise/age.txt). Commit only ciphertext.
sops + age (encrypted file)
Section titled “sops + age (encrypted file)”-
Install the tools and generate a key:
Terminal window mise use -g sops ageage-keygen -o ~/.config/mise/age.txt # prints the public key -
Encrypt a secrets file in place with your public key:
Terminal window sops encrypt -i --age age1xxxxxxxx... .env.json -
Load it — mise auto-decrypts via the age key on entry:
mise.toml [env]_.file = { path = ".env.json", redact = true }
Key lookup order: MISE_SOPS_AGE_KEY → *_FILE → SOPS_AGE_KEY* →
~/.config/mise/age.txt.
Inline age (single value)
Section titled “Inline age (single value)”For one-off secrets, encrypt a value straight into mise.toml:
mise set --age-encrypt --prompt DB_PASSWORD[env]DB_PASSWORD = { age = { value = "-----BEGIN AGE…", format = "raw" } }View the decrypted environment with mise env (or mise env --redacted --values
to confirm masking works).
Redaction
Section titled “Redaction”Keep secret values out of logs and mise env output with a top-level glob list,
or per-file redact:
[settings]redactions = ["*_TOKEN", "*_SECRET", "DB_PASSWORD"]Teams: fnox
Section titled “Teams: fnox”Per-developer age keys work for small teams. For shared/remote secret backends
(1Password, AWS Secrets Manager, KMS), jdx’s separate fnox
manager is the 2026 answer — it integrates with mise without putting any private
key on disk.
You can now pin tools and shape the environment around them. Next, turn the chores into real tasks: The Task Runner.