Top 10 Features in Mise You're Not Using
1. Task sources and outputs
Any task can declare input/output globs. If all outputs are newer than all sources, the task is skipped entirely:
[tasks.build]
run = "cargo build"
sources = ["Cargo.toml", "src/**/*.rs"]
outputs = ["target/debug/myapp"]
Don’t want to enumerate outputs? Set outputs = { auto = true } (which is actually the default when sources is defined) — mise tracks a hash internally and skips the task if sources haven’t changed.
2. The github, http, and s3 backends
The github backend installs tools directly from GitHub releases. It infers the right asset for your platform automatically — just point it at a repo and it usually works:
[tools]
"github:BurntSushi/ripgrep" = "latest"
For more complex setups (non-standard naming, multiple binaries, etc.), the aqua registry is a better choice for open-source tools — it has hand-written manifests for hundreds of tools with full platform mapping. The github backend is best for quick one-offs and private/internal tools.
The http backend goes further — install any binary from any URL with templated platform variables:
[tools."http:my-internal-tool"]
version = "2.1.0"
url = 'https://artifacts.internal.co/tool-v{{version}}-{{os(macos="darwin")}}-{{arch(x64="amd64")}}.tar.gz'
The s3 backend [experimental] does the same for S3 or S3-compatible storage (MinIO, etc.):
[tools."s3:my-tool"]
version = "1.0.0"
url = "s3://my-bucket/tools/my-tool-v{{ version }}.tar.gz"
endpoint = "http://minio.internal:9000"
Great for internal artifact servers, private buckets, or anything with a download link.
3. Tool stubs
Inspired by Meta’s dotslash, tool stubs are self-contained scripts that auto-download and execute tools on first run. Check them into your repo and anyone can run them — they just need mise:
#!/usr/bin/env -S mise tool-stub
version = "20.0.0"
tool = "node"
bin = "node"
Generate them with mise generate tool-stub. Add --lock to embed platform-specific URLs and checksums for fully reproducible installs. Add --bootstrap to create a wrapper that installs mise itself if missing. You can also use the http backend style for custom binaries — no registry entry needed.
4. Typed task arguments with usage
Tasks can have real CLI arguments with validation, choices, defaults, and auto-generated help:
[tasks.deploy]
usage = '''
arg "<environment>" help="Target env" { choices "dev" "staging" "prod" }
flag "-v --verbose" help="Verbose output"
flag "--region <region>" default="us-east-1"
'''
run = 'deploy.sh ${usage_environment} --region ${usage_region}'
mise run deploy --help prints a proper help page. Tab completion works out of the box — including completing argument values from choices or dynamic complete handlers. Adding autocompletion for custom values is as simple as:
complete "environment" run="mise plugins ls"
5. Lockfiles — avoid GITHUB_TOKEN issues with mise lock
Pin exact versions, checksums, and download URLs for every tool:
mise lock
That’s it. Run it and it generates mise.lock with checksums and URLs for all common platforms. This is a big deal for teams — without a lockfile, every mise install hits the GitHub API to resolve versions and download URLs. GitHub’s API rate limits are aggressive for unauthenticated requests, and getting every developer on a team to configure a GITHUB_TOKEN is an unnecessary hassle. With a lockfile, installs are fully offline-capable: no API calls, no rate limits, no tokens needed.
Use --locked in CI to fail if the lockfile is missing or outdated.
6. Supply chain security
Most tools in the mise registry now install through the aqua backend, which natively verifies integrity — no external tools required:
- Checksums (SHA256, SHA512, Blake3) — always on
- Cosign signature verification
- SLSA provenance verification
- GitHub Attestations — workflow-level verification
- Minisign — public key signatures
Only 39 out of 919 tools in the registry default to a plugin-based backend (23 asdf, 16 vfox) — around 4%. The rest install through aqua, github, or other backends with built-in verification. Those remaining plugin-backed tools have all been forked to the mise-plugins org under mise maintainer control. When you run mise use ripgrep, you’re getting a verified binary from the vendor’s GitHub release — not running arbitrary bash from a third-party plugin.
7. Environment profiles with MISE_ENV
Set MISE_ENV=staging and mise layers mise.staging.toml on top of mise.toml:
mise.toml # shared defaults
mise.dev.toml # dev overrides
mise.staging.toml # staging overrides
mise.local.toml # personal overrides, never committed
The catch: MISE_ENV determines which config files get loaded, so it can’t be set inside mise.toml itself. That’s what .miserc.toml is for — a lightweight config loaded before everything else:
# .miserc.toml
env = ["development"]
This way your project defaults to the development profile without requiring every developer to set an environment variable.
8. Shell aliases
Project-scoped aliases that activate/deactivate as you cd:
[shell_alias]
ll = "ls -la"
dev = "npm run dev"
dc = "docker compose -f docker-compose.dev.yml"
src = "cd {{config_root}}/src"
Works in bash, zsh, and fish. Leave the directory and they’re gone.
9. mise prepare [experimental]
Detects lockfiles and installs project dependencies only when they’re stale. If the content of package-lock.json has changed since the last run, it triggers npm install. Built-in providers for npm, yarn, pnpm, bun, go, pip, poetry, uv, bundler, and composer. Custom providers for anything else:
[prepare.codegen]
sources = ["schema/*.graphql"]
outputs = ["src/generated/"]
run = "npm run codegen"
depends = ["npm"]
Set auto = true and it runs automatically before mise run and mise exec. Pass --dry-run to see why each provider is stale (e.g., “package.json changed”, “outputs missing”).
Watch this space — this is an area I plan to improve further to give users a cargo/uv-like experience across all languages.
10. Monorepo tasks [experimental]
Run tasks across packages with wildcard patterns:
mise //services/api:build # specific package
mise //...:test # all packages
mise '//services/...:build' # all under services/
Configuration inherits from parent directories — define node = "20" and common env vars in the root mise.toml, and every package gets them automatically. Individual packages can override just what they need (e.g., a legacy app pinning node = "14") while inheriting everything else. Enable with experimental_monorepo_root = true in your root mise.toml.
Bonus: The mise ecosystem
fnox
Secret management for development and production. Encrypt secrets in git with age or cloud KMS, or fetch them from AWS Secrets Manager, 1Password, Vault, and more. Shell integration auto-loads secrets on cd:
# fnox.toml
[providers]
age = { type = "age", recipients = ["age1ql3z..."] }
[secrets]
DATABASE_URL = { provider = "age", value = "YWdlLWVuY3..." } # encrypted, safe to commit
API_KEY = { default = "dev-key-12345" } # plain default for local dev
[profiles.production.providers]
aws = { type = "aws-sm", region = "us-east-1", prefix = "myapp/" }
hk
A git hook manager built for speed. The key innovation is read/write file locking: check commands (e.g., eslint, prettier --check) take read locks and run fully in parallel, while fix commands (e.g., prettier --write) take write locks to prevent race conditions. It runs checks first by default, only falling back to fixes when needed. No other git hook manager does this — tools like husky, lefthook, and pre-commit either run steps serially or do naive parallelism that can corrupt files when multiple fixers touch the same file:
amends "package://github.com/jdx/hk/releases/download/v1.36.0/hk@1.36.0#/Config.pkl"
import "package://github.com/jdx/hk/releases/download/v1.36.0/hk@1.36.0#/Builtins.pkl"
hooks {
["fix"] {
fix = true
steps {
["prettier"] = (Builtins.prettier) {}
["eslint"] = (Builtins.eslint) {}
["tsc"] { check = "tsc --noEmit" }
}
}
["check"] {
steps {
["prettier"] = (Builtins.prettier) {}
["eslint"] = (Builtins.eslint) {}
["tsc"] { check = "tsc --noEmit" }
}
}
}
pitchfork
Daemon manager for dev services. Auto-starts processes when you cd into a project, auto-stops when you leave. Ready checks, restart on failure, dependency ordering, and cron jobs:
# pitchfork.toml
[daemons.postgres]
run = "docker run --rm -p 5432:5432 -e POSTGRES_PASSWORD=dev postgres:16"
auto = ["start", "stop"]
ready_delay = 5
[daemons.api]
run = "npm run dev:api"
auto = ["start", "stop"]
ready_output = "listening on"
depends = ["postgres"]