Environment Plugin Development
Environment plugins are a special type of mise plugin that provide environment variables and PATH modifications without managing tool versions. They're ideal for integrating external services, managing secrets, and standardizing environment configuration across teams.
Unlike tool plugins and backend plugins, environment plugins:
- ✅ Don't implement version management (
Available
,PreInstall
,PostInstall
hooks) - ✅ Only implement environment hooks (
MiseEnv
,MisePath
) - ✅ Are configured via
env._.<plugin-name>
syntax - ✅ Can accept configuration options as TOML values
- ✅ Execute on every environment activation
Quick Start
The fastest way to create an environment plugin is to use the mise-env-plugin-template:
# Clone the template
git clone https://github.com/jdx/mise-env-sample my-env-plugin
cd my-env-plugin
# Customize for your use case
# Edit metadata.lua, hooks/mise_env.lua, hooks/mise_path.lua
Plugin Structure
A minimal environment plugin has this structure:
my-env-plugin/
├── metadata.lua # Plugin metadata
└── hooks/
├── mise_env.lua # Returns environment variables (required)
└── mise_path.lua # Returns PATH entries (optional)
metadata.lua
The metadata.lua
file defines your plugin's basic information:
PLUGIN = {}
--- Plugin name (required)
PLUGIN.name = "my-env-plugin"
--- Plugin version (required)
PLUGIN.version = "1.0.0"
--- Plugin description (required)
PLUGIN.description = "Provides environment variables for my service"
--- Plugin homepage (optional)
PLUGIN.homepage = "https://github.com/username/my-env-plugin"
--- Plugin license (optional)
PLUGIN.license = "MIT"
--- Minimum mise/vfox version required (optional)
PLUGIN.minRuntimeVersion = "0.3.0"
hooks/mise_env.lua
The MiseEnv
hook returns environment variables to set:
function PLUGIN:MiseEnv(ctx)
-- Access configuration from mise.toml via ctx.options
local api_url = ctx.options.api_url or "https://api.example.com"
local debug = ctx.options.debug or false
-- Return array of environment variables
return {
{
key = "API_URL",
value = api_url
},
{
key = "DEBUG",
value = tostring(debug)
},
{
key = "SERVICE_TOKEN",
value = get_token_from_somewhere() -- Your custom logic
}
}
end
Return value: Array of tables, each with:
key
(string, required): Environment variable namevalue
(string, required): Environment variable value
hooks/mise_path.lua
The MisePath
hook returns directories to add to PATH (optional):
function PLUGIN:MisePath(ctx)
-- Return array of paths to prepend to PATH
local paths = {
"/opt/my-service/bin"
}
-- Optionally add user-configured path
if ctx.options.custom_bin_path then
table.insert(paths, ctx.options.custom_bin_path)
end
return paths
end
Return value: Array of strings (directory paths)
Context Object
Both hooks receive a ctx
parameter with:
ctx.options
: TOML table of user configuration frommise.toml
For environment plugins, ctx.options
is the primary way to accept user configuration.
Configuration in mise.toml
Users configure environment plugins using the env._
directive:
Simple activation with no options:
[env]
_.my-env-plugin = {}
With configuration options:
[env]
_.my-env-plugin = { api_url = "https://prod.api.example.com", debug = false, custom_bin_path = "/custom/path/bin" }
All fields in the TOML table are passed to your hooks as ctx.options
.
Complete Example: Secret Manager Plugin
Here's a complete example of a plugin that fetches secrets from an external service:
metadata.lua:
PLUGIN = {}
PLUGIN.name = "vault-secrets"
PLUGIN.version = "1.0.0"
PLUGIN.description = "Fetch secrets from HashiCorp Vault"
PLUGIN.minRuntimeVersion = "0.3.0"
hooks/mise_env.lua:
local http = require("http")
local json = require("json")
function PLUGIN:MiseEnv(ctx)
local vault_url = ctx.options.vault_url or error("vault_url required")
local secrets_path = ctx.options.secrets_path or error("secrets_path required")
local vault_token = os.getenv("VAULT_TOKEN") or error("VAULT_TOKEN not set")
-- Fetch secrets from Vault
local url = vault_url .. "/v1/" .. secrets_path
local response = http.get({
url = url,
headers = {
["X-Vault-Token"] = vault_token
}
})
if response.status_code ~= 200 then
error("Failed to fetch secrets: " .. response.status_code)
end
local data = json.decode(response.body)
local env_vars = {}
-- Convert Vault secrets to environment variables
for key, value in pairs(data.data.data) do
table.insert(env_vars, {
key = key,
value = value
})
end
return env_vars
end
Usage in mise.toml:
[env]
_.vault-secrets = { vault_url = "https://vault.example.com", secrets_path = "secret/data/myapp/production" }
Available Lua Modules
Environment plugins have access to mise's built-in Lua modules:
http
: Make HTTP requestsjson
: Encode/decode JSONfile
: Read/write filescmd
: Execute shell commandsstrings
: String manipulation utilitiesenv
: Access environment variables
See Plugin Lua Modules for complete documentation.
Best Practices
1. Provide Sensible Defaults
function PLUGIN:MiseEnv(ctx)
local api_url = ctx.options.api_url or "https://api.example.com"
local timeout = ctx.options.timeout or 30
-- ...
end
2. Validate Required Options
function PLUGIN:MiseEnv(ctx)
if not ctx.options.api_key then
error("api_key is required in mise.toml configuration")
end
-- ...
end
3. Handle Errors Gracefully
function PLUGIN:MiseEnv(ctx)
local response = http.get({url = ctx.options.api_url})
if response.status_code ~= 200 then
error("API request failed: " .. response.status_code .. " - " .. response.body)
end
-- ...
end
4. Cache Expensive Operations
For plugins that fetch data from external services, consider caching:
local cache_file = os.getenv("HOME") .. "/.cache/my-plugin/secrets.json"
function PLUGIN:MiseEnv(ctx)
-- Check if cache is fresh
if is_cache_valid(cache_file, 300) then -- 5 minute cache
return load_from_cache(cache_file)
end
-- Fetch fresh data
local secrets = fetch_secrets(ctx.options)
save_to_cache(cache_file, secrets)
return secrets
end
5. Support Multiple Environments
function PLUGIN:MiseEnv(ctx)
local env_name = ctx.options.environment or "development"
-- Load different config based on environment
local config = load_config(env_name)
return {
{key = "ENV", value = env_name},
{key = "API_URL", value = config.api_url},
-- ...
}
end
Testing Your Plugin
Local Testing
- Link your plugin for development:
mise plugin link my-env-plugin /path/to/my-env-plugin
- Configure it in
mise.toml
:
[env]
_.my-env-plugin = { test_option = "value" }
- Test the environment:
# See environment variables
mise env | grep MY_
# Run a command with the environment
mise exec -- env | grep MY_
# Debug with MISE_DEBUG
MISE_DEBUG=1 mise env
Common Issues
Plugin not found: Make sure you've installed/linked the plugin:
mise plugin ls
Hook not executing: Enable debug logging:
MISE_DEBUG=1 mise env
Options not passed: Verify TOML syntax in mise.toml
:
[env]
# Correct: TOML table
_.my-plugin = { key = "value" }
# Wrong: String value
_.my-plugin = "value" # This won't work
Publishing Your Plugin
Once your environment plugin is ready:
- Create a GitHub repository for your plugin
- Add a README with usage instructions
- Tag releases following semantic versioning
- (Optional) share the repository URL so others can install it directly with
mise plugin install
.
See Plugin Publishing for detailed instructions.
Examples
- mise-env-sample - Simple example showing basic usage
- The mise-plugins organization currently hosts tool plugins only—add your environment plugin there (or share it with the community) so others can learn from more examples
Migration from Tool Plugins
If you have an existing tool plugin that only sets environment variables, you can simplify it to an environment-only plugin:
Before (tool plugin with unused hooks):
my-plugin/
├── metadata.lua
└── hooks/
├── available.lua # Returns empty list
├── pre_install.lua # Not used
├── post_install.lua # Not used
└── env_keys.lua # Actually sets env vars
After (environment plugin):
my-plugin/
├── metadata.lua
└── hooks/
└── mise_env.lua # Clean and focused
Related Documentation
- Plugin Overview - Overview of all plugin types
- Tool Plugin Development - For plugins that manage tool versions
- Backend Plugin Development - For multi-tool backends
- Plugin Lua Modules - Available Lua APIs
- Plugin Publishing - Publishing your plugin
- Environment Variables - How mise manages environments