---
outline: [1, 3]
---

# Contributing

## Contribution Expectations

mise has a specific scope and design taste. Before opening a PR, unless it is
something obvious, start a
[discussion](https://github.com/jdx/mise/discussions) or mention what you plan
to do in [Discord](https://discord.gg/UBa7pJUN7Z). The important part is to
settle the direction before much implementation or review happens. PRs are
often either rejected or need to change significantly after submission, so make
sure the idea fits before you invest too much time.

Before I review a PR, CI must be passing, the PR title must follow
[Conventional Commits](#conventional-commits), and all automated AI review
comments must be addressed. If any of those are still open, assume I will wait
to look at the PR.

If I am on the fence about a contribution, I will probably reject it for that
reason alone. If I did not do this, mise would suffer from feature bloat. I
may also reject a PR if the quality is poor enough that I do not have confidence
the contributor can get it across the finish line. I do not have time to coach
contributors.

I get hundreds of PRs per week across my projects, so I do not have time to
respond to every PR with detailed context. A rejection may be brief.

## Pull Request Checklist

1. **Discuss first**: Use GitHub Discussions or Discord for non-obvious changes
2. **Use a conventional title**: PR titles are validated automatically
   - For new tools in registry: Use `registry: add tool-name (backend:full/name)`
3. **Run local checks**: Run `mise run render` and `mise run lint-fix` before
   opening a PR when relevant
4. **Test thoroughly**: Ensure the relevant unit and E2E tests pass
5. **Update documentation**: Add or update docs for user-facing changes
6. **Keep dependencies healthy**: New dependencies are validated with cargo-deny

### Development Tips

1. **Disable mise during development**: If you use mise in your shell, disable
   it when running tests to avoid conflicts
2. **Test specific features**: Use `cargo test test_name` for targeted testing
3. **Update snapshots**: Use `mise run snapshots` when changing test outputs
4. **Rate limiting**: Set `MISE_GITHUB_TOKEN` to avoid GitHub API rate limits
   during development

## Packaging and Self-Update Instructions

When mise is installed via a package manager, `mise self-update` should not replace the binary the package manager owns; users should update through the package manager instead. This is opt-in: a package that does none of the following keeps self-update fully enabled. Packagers have three ways to turn it off, and any of them makes `mise doctor` report `self_update_available: no`.

The paths below are relative to the install prefix, which mise derives from its own binary: the path is canonicalized (symlinks resolved) and then taken two levels up, so `/usr/bin/mise` gives `/usr`.

### Disable at build time

Build without the `self_update` Cargo feature, as the Arch Linux package does:

```bash
cargo build --release --no-default-features --features native-tls
```

The subcommand still exists, so scripts that call it get a clear error rather than "unknown command", but it always fails with `mise's self-update feature has been disabled at build time, cannot update`.

### Disable with a marker file

Install an empty `.disable-self-update` file at any one of:

- `lib/.disable-self-update` (used by Homebrew)
- `lib/mise/.disable-self-update` (used by the AUR `mise-bin` package)
- `lib64/mise/.disable-self-update`

### Ship update instructions

Installing a TOML file with platform-specific instructions also disables self-update, and mise prints its message when `mise self-update` is run and when a newer release is detected. Install it at any one of:

- `lib/mise-self-update-instructions.toml`
- `lib/mise/mise-self-update-instructions.toml`
- `lib64/mise/mise-self-update-instructions.toml`

Example contents:

```toml
# Debian/Ubuntu (APT)
message = "To update mise from the APT repository, run:\n\n  sudo apt update && sudo apt install --only-upgrade mise\n"
```

```toml
# Fedora/CentOS Stream (DNF)
message = "To update mise from COPR, run:\n\n  sudo dnf upgrade mise\n"
```

Setting `MISE_SELF_UPDATE_INSTRUCTIONS` to a file path overrides the search.

### Overriding the outcome

`MISE_SELF_UPDATE_AVAILABLE=false` disables self-update without installing anything, and `MISE_SELF_UPDATE_AVAILABLE=true` re-enables it even when a marker or instructions file is present. Both are useful for testing a package build. Neither has any effect on a binary built without the `self_update` feature, where self-update is always unavailable.

`mise self-update --force` also bypasses the availability check, so a user who passes it updates the binary in place even when a marker file, an instructions file, or `MISE_SELF_UPDATE_AVAILABLE=false` is in effect. Treat the runtime mechanisms as "do not update by default" rather than a hard block. A build without the `self_update` feature is the only variant `--force` cannot get past.

## Testing

mise has a comprehensive test suite with multiple types of tests to ensure
reliability and functionality across different platforms and scenarios.

### Unit Tests

Unit tests are fast, focused tests for individual components and functions:

```bash
# Run all unit tests
cargo test --all-features

# Run specific unit tests
cargo test <test_name>
```

**Unit test structure:**

- Located in `src/` directory alongside source code
- Use Rust's built-in test framework
- Test individual functions and modules
- Fast execution (used for quick feedback during development)

### E2E Tests

End-to-end tests validate the complete functionality of mise in realistic
scenarios:

```bash
# Run all E2E tests
mise run test:e2e

# Run specific E2E test
./e2e/run_test test_name

# Run E2E tests matching pattern
./e2e/run_test task  # runs tests matching *task*

# Run all tests including slow ones
TEST_ALL=1 mise run test:e2e
```

**E2E test structure:**

- Located in `e2e/` directory
- Organized by functionality:
  - `e2e/cli/` - Command-line interface tests
  - `e2e/core/` - Core functionality tests
  - `e2e/env/` - Environment variable tests
  - `e2e/tasks/` - Task runner tests
  - `e2e/config/` - Configuration tests
  - `e2e/tools/` - Tool management tests
  - `e2e/shell/` - Shell integration tests
  - `e2e/backend/` - Backend tests
  - `e2e/plugins/` - Plugin tests

**E2E test categories:**

- **Fast tests** (`test_*`): Run in normal test suites
- **Slow tests** (`test_*_slow`): Only run when `TEST_ALL=1` is set
- **Isolated environment**: Each test runs in a clean, isolated environment

### Coverage Tests

Coverage tests measure how much of the codebase is covered by tests:

```bash
# Run coverage tests
mise run test:coverage

# Coverage tests run in parallel tranches for CI
TEST_TRANCHE=0 TEST_TRANCHE_COUNT=8 mise run test:coverage
```

### Windows E2E Tests

Windows has its own test suite written in PowerShell:

```powershell
# Run all Windows E2E tests
pwsh e2e-win\run.ps1

# Run specific Windows tests
pwsh e2e-win\run.ps1 task  # run tests matching *task*
```

### Plugin Tests

Test plugin functionality across different backends:

```bash
# Test specific plugin
mise test-tool ripgrep

# Test all plugins in registry
mise test-tool --all

# Test all plugins in config files
mise test-tool --all-config

# Test with parallel jobs
mise test-tool --all --jobs 4
```

### Test Environment Setup

Tests run in isolated environments to avoid conflicts:

```bash
# Disable mise during development testing
export MISE_DISABLE_TOOLS=1

# Run tests with specific environment
MISE_TRUSTED_CONFIG_PATHS=$PWD cargo test
```

### Test Assertions

The E2E tests use a custom assertion framework (`e2e/assert.sh`):

```bash
# Basic assertions
assert "command" "expected_output"
assert_contains "command" "substring"
assert_fail "command" "expected_error"

# JSON assertions
assert_json "command" '{"key": "value"}'
assert_json_partial_array "command" "fields" '[{...}]'

# File/directory assertions
assert_directory_exists "/path/to/dir"
assert_directory_not_exists "/path/to/dir"
assert_empty "command"
```

### Running Specific Test Categories

```bash
# Run all tests (unit + e2e)
mise run test

# Run only unit tests
mise run test:unit

# Run only e2e tests
mise run test:e2e

# Run tests with shuffle (for detecting order dependencies)
mise run test:shuffle

# Run nightly tests (with bleeding edge Rust)
rustup default nightly && mise run test
```

### Running Individual Tests

#### Running Single Unit Tests

```bash
# Run a specific unit test by name
cargo test test_name

# Run tests matching a pattern
cargo test pattern

# Run tests in a specific module
cargo test module_name

# Run a single test with output
cargo test test_name -- --nocapture
```

#### Running Single E2E Tests

```bash
# Run a specific E2E test by name
./e2e/run_test test_name

# Run E2E tests matching a pattern
mise run test:e2e pattern

# Examples:
./e2e/run_test test_use                    # Run specific test
./e2e/run_test test_config_set            # Run config-related test
mise run test:e2e task                     # Run all tests matching "task"
```

#### Testing Individual Plugins

```bash
# Test a specific plugin
mise test-tool ripgrep

# Test a plugin with verbose output
mise test-tool ripgrep --raw

# Test multiple plugins
mise test-tool ripgrep jq terraform
```

### Performance Testing

```bash
# Run performance benchmarks
mise run test:perf

# Build performance test workspace
mise run test:build-perf-workspace
```

### Snapshot Testing

Used for testing output consistency:

```bash
# Update test snapshots when output changes
mise run snapshots

# Use cargo-insta for snapshot testing
cargo insta test --accept --unreferenced delete
```

## Development Setup

### Prerequisites

- [Rust](https://www.rust-lang.org/) (latest stable, we don't use mise to
  manage rust)
- mise

### Getting Started

```bash
# Clone the repository
git clone https://github.com/jdx/mise.git
cd mise

# Install dependencies
mise install

# Build the project
mise run build
```

### Development Shim

Create a development shim to easily run mise during development:

```bash
# Create ~/.local/bin/@mise
#!/bin/sh
exec cargo run -q --all-features --manifest-path ~/src/mise/Cargo.toml -- "$@"
```

Then use `@mise` to run the development version:

```bash
@mise --help
eval "$(@mise activate zsh)"
```

## Project Structure

```text
mise/
├── src/           # Main Rust source code
├── e2e/           # End-to-end tests
├── docs/          # Documentation
├── tasks.toml     # Development tasks
├── mise.toml      # Project configuration
├── Cargo.toml     # Rust project configuration
└── xtasks/        # Additional build scripts
```

## Available Development Tasks

Use `mise tasks` to see all available development tasks:

### Common Tasks

- `mise run build` - Build the project
- `mise run test` - Run all tests (unit + E2E)
- `mise run test:unit` - Run unit tests only
- `mise run test:e2e` - Run E2E tests only
- `mise run lint` - Run linting
- `mise run lint:fix` - Run linting with fixes
- `mise run format` - Format code
- `mise run clean` - Clean build artifacts
- `mise run snapshots` - Update test snapshots
- `mise run render` - Generate documentation and completions

### Documentation Tasks

- `mise run docs` - Start documentation development server
- `mise run docs:build` - Build documentation
- `mise run render:help` - Generate help documentation
- `mise run render:completions` - Generate shell completions

### Release Tasks

- `mise run release-plz` - Create a release
- `mise run ci` - Run CI tasks (format, build, test)

## Setup

Shouldn't require anything special I'm aware of, but `mise run build` is a good
sanity check to run and make sure it's all working.

## Pre-commit Hooks & Code Quality

mise uses [hk](https://hk.jdx.dev) as its git hook manager for
linting and code quality checks. hk is a modern alternative to lefthook written
by the same author as mise.

### hk Configuration

The project uses `hk.pkl` (written in the Pkl configuration language) to define
linting rules:

```bash
# Run all linting checks
hk check --all

# Run linting with fixes
hk fix --all

# Run specific linter
hk check --step shellcheck
```

### Available Linters in hk

- **prettier**: Code formatting for multiple languages
- **clippy**: Rust linting with `cargo clippy`
- **shellcheck**: Shell script linting
- **shfmt**: Shell script formatting
- **pkl**: Pkl configuration file validation

### Using hk in Development

```bash
# Run linting (used in CI and pre-commit)
mise run lint  # This runs hk check --all

# Run linting with fixes
hk fix --all

# Check specific file types
hk check --step prettier
hk check --step shellcheck
```

### Setting Up Pre-commit Hooks

```bash
# Set up git hooks to run hk on pre-commit
hk install --mise
```

### Running Checks Manually

```bash
# Run all checks
hk check --all

# Run checks with fixes
hk fix --all

# Run checks on specific files
hk check --files="src/**/*.rs"
```

## Running the CLI

I use the following shim in `~/.local/bin/@mise`:

```sh
#!/bin/sh
exec cargo run -q --all-features --manifest-path ~/src/mise/Cargo.toml -- "$@"
```

::: info
Don't forget to change the manifest path to the correct path for your setup.
:::

Then if that is in PATH just use `@mise` to run mise by compiling it on the fly.

```sh
@mise --help
eval "$(@mise activate zsh)"
@mise activate fish | source
```

## Releasing

Releases are cut automatically by the `release-plz` GitHub Actions workflow
(`mise run release-plz` in CI). Do not run that task locally.

## Linting

- Lint codebase: `mise run lint`
- Lint and fix codebase: `mise run lint:fix`

## Generating readme and shell completion files

```sh
mise run render
```

## Dependency Management

mise uses several tools to validate dependencies and code quality:

- **cargo-deny**: Validates licenses, security advisories, and dependency
  duplicates
- **cargo-msrv**: Verifies minimum supported Rust version compatibility
- **cargo-machete**: Detects unused dependencies in Cargo.toml

These checks run automatically in CI and can be run locally:

```bash
# Run checks (tools are automatically available via mise.toml)
cargo deny check
cargo msrv verify
cargo machete --with-metadata
```

## Conventional Commits

mise uses [Conventional Commits](https://www.conventionalcommits.org/) for
consistent commit messages and automated changelog generation. All commits
should follow this format:

```text
<type>[optional scope]: <description>

[optional body]

[optional footer(s)]
```

### Commit Types

- **feat**: New features (🚀 Features)
- **fix**: Bug fixes (🐛 Bug Fixes)
- **refactor**: Code refactoring (🚜 Refactor)
- **docs**: Documentation changes (📚 Documentation)
- **style**: Code style changes (🎨 Styling)
- **perf**: Performance improvements (⚡ Performance)
- **test**: Testing changes (🧪 Testing)
- **chore**: Maintenance tasks, dependency updates
- **revert**: Reverting previous changes (◀️ Revert)

### Examples

```bash
feat(cli): add new command for listing plugins
fix(parser): handle edge case in version parsing
refactor(config): simplify configuration loading logic
docs(readme): update installation instructions
test(e2e): add tests for new plugin functionality
chore(deps): update dependencies to latest versions
```

### Scopes

Common scopes used in mise:

- `cli` - Command line interface changes
- `config` - Configuration system changes
- `parser` - Parsing logic changes
- `deps` - Dependency updates
- `security` - Security-related changes

### Breaking Changes

#### Breaking Change Policy

Breaking changes are rarely accepted into mise and are only performed in
exceptional situations where there is no better alternative. When a breaking
change is necessary, the process includes:

1. **CLI warnings**: Users receive deprecation warnings in the CLI
2. **Migration period**: Several months are provided for users to migrate
3. **Documentation**: Clear migration guides are provided
4. **Community notice**: Announcements in Discord and GitHub discussions

For breaking changes, add `!` after the type or include `BREAKING CHANGE:` in
the footer:

```bash
feat(api)!: remove deprecated configuration options
# OR
feat(api): remove deprecated configuration options

BREAKING CHANGE: The old configuration format is no longer supported
```

## CI/CD & Pull Request Automation

mise uses several automated workflows to maintain code quality and streamline
development:

### Formatting and Linting

- Run `mise run render` and `mise run lint-fix` before opening a PR
- Generated docs, completions, and snapshots should be committed with the
  change that requires them
- PRs with formatting or lint failures should be fixed by the contributor

### PR Title Validation

- **semantic-pr-lint**: Validates PR titles follow conventional commit format
- PR titles must match: `<type>[optional scope]: <description>`
- Example: `feat(cli): add new command for listing plugins`

### Continuous Integration

- **Cross-platform testing**: Ubuntu, macOS, and Windows
- **Unit tests**: Fast component-level tests
- **E2E tests**: Full integration testing with multiple test tranches
- **Dependency validation**: `cargo deny`, `cargo msrv`, `cargo machete`

### Release Automation

- **release-plz**: Automated release management based on conventional commits
- Automatically creates release PRs and publishes releases
- Runs daily via scheduled workflow
- Handles version bumping and changelog generation

## Adding a new setting

To add a new setting, add it to
[`settings.toml`](https://github.com/jdx/mise/blob/main/settings.toml) in the
root of the project and run `mise run render` to update the codebase.

## Adding Tools

Adding tools to mise involves adding entries to the
[registry/](https://github.com/jdx/mise/blob/main/registry/) file. This
allows users to install tools using short names like `mise use ripgrep` instead
of the full backend specification.

### Quick Start

1. **Choose the right backend** for your tool:

   - **[aqua](dev-tools/backends/aqua.md)** - Preferred for GitHub releases with security
     features
   - **[github](dev-tools/backends/github.md)** - Simple GitHub releases following
     standard conventions
   - **Language package managers** - `npm`, `pipx`, `cargo`, `gem`, etc. for
     ecosystem-specific tools
   - **[Core tools](core-tools.md)** - Built-in support for major languages
     (not user-contributed)

2. **Add to registry/**:

   ```toml
   version_order = "semver"
   description = "Brief description of the tool"
   backends = ["aqua:owner/repo", "github:owner/repo"]
   test = { cmd = "your-tool --version", expected = "{{version}}" }
   ```

3. **Test the tool** works properly with `mise test-tool your-tool`

### Guidelines and Requirements

When adding a new tool, the following requirements apply:

- **A test is required in `registry/`** - Must include a `test` field to
  verify installation. This is automatically enforced by the
  [`validate-new-tools` job](https://github.com/jdx/mise/blob/main/.github/workflows/registry.yml)
  in the registry workflow.
- **Tools may be rejected if they are not notable** - The tool should be
  reasonably popular and well-maintained. Notability is decided by maintainer
  review (not CI). There are no specific guidelines for this and
  a lot of factors are taken into account. @jdx won't explain why a given tool wasn't
  accepted. Include a brief popularity summary (stars, downloads, recent release date) in
  the PR description so the policy can be applied without re-doing the research.

#### Backend acceptance tiers

Which backend you choose for a registry entry matters as much as which tool you
add. Backends fall into the following tiers:

**Tier 1 — preferred, routinely accepted:** [`aqua`](/dev-tools/backends/aqua.html),
[`github`](/dev-tools/backends/github.html), and [`gitlab`](/dev-tools/backends/gitlab.html).

- Prefer `aqua` when the tool is in the [aqua registry](https://github.com/aquaproj/aqua-registry) —
  it has better UX, SLSA verification, and per-version logic.
- Use `github` when the tool isn't in aqua but ships GitHub releases.
- Use `gitlab` for tools released through GitLab.

**Tier 2 — high bar, but lower than tier 3:** [`conda`](/dev-tools/backends/conda.html).

Potentially accepted for tools that can't reasonably be supported via aqua/github.
The bar is lower than tier 3 because **mise's conda backend does not require a
separately-installed package manager** — packages are downloaded and extracted
directly from anaconda.org, with no `conda`/`mamba`/`micromamba` needed on the
user's PATH. The tool still needs to be popular and well-maintained.

**Tier 3 — very high bar, rarely accepted:** `npm`, `pipx`, `gem`, `cargo`, `go`, `dotnet`.

These all depend on a separately-installed runtime or toolchain being present on
the user's PATH (`node`, `python`, `ruby`, `cargo`, `go`, `dotnet`), which is
fragile — `npm`/`pipx`/`gem` in particular silently bind tools to whichever
`node`/`python`/`ruby` happened to be on PATH at install time, which breaks when
versions change or the runtime isn't installed. Accepted only when no aqua/github
option exists and the tool is widely used. Discuss with @jdx before submitting.

**Not accepted:** `asdf`, `vfox`, `ubi`.

- **New `asdf` plugins** — supply-chain security. Use [aqua](/dev-tools/backends/aqua.html) or [github](/dev-tools/backends/github.html) instead.
- **New `vfox` plugins** — same reason. Use aqua/github instead.
- **`ubi`** is deprecated and is not accepted for new registry entries.

Users can still install via any backend themselves with explicit syntax
(`mise use vfox:owner/repo`, `mise use cargo:name`, etc.) — they just don't get
a registry shorthand for it.

### Registry Format

The `registry/` file uses this format:

```toml
# Tool name "your-tool" (becomes the short name for `mise use`)
version_order = "semver"
description = "Tool description"
backends = [
    "aqua:owner/repo",           # Preferred backend first
    "github:owner/repo",         # Fallback backend
]
test = { cmd = "your-tool --version", expected = "{{version}}" }
aliases = ["alt-name"] # Optional alternative names
os = ["linux", "macos"] # Optional OS restrictions
```

Every registry entry must explicitly set `version_order` to `semver` or
`source`. Use `semver` only when the tool's stable releases consistently use
strict `MAJOR.MINOR.PATCH` semantic versions. Use `source` for date versions,
two-component versions, channels, refs, tool-specific formats, mixed histories,
or whenever the convention is uncertain. Semantic ordering currently affects
the Aqua, GitHub, GitLab, Forgejo, and HTTP backends; the field still documents
the policy for tools whose current backend owns version ordering itself.

#### Idiomatic version files

Registry tools can opt into [idiomatic version files](/configuration.html#idiomatic-version-files)
with `idiomatic_files`. A filename string uses mise's default plain-text parser:

```toml
backends = ["aqua:owner/repo"]
idiomatic_files = [".your-tool-version"]
```

For structured or tool-specific files, use a table with the same parsing options supported by the
[HTTP backend's version listing](/dev-tools/backends/http.html#version-listing):

```toml
idiomatic_files = [
  { path = "your-tool.json", version_json_path = ".toolchain.version" },
  { path = "your-tool.conf", version_regex = 'version\s*=\s*"([^"]+)"' },
]
```

The supported parser fields are:

- `version_regex`: extract every regex match, using the first capture group when present.
- `version_json_path`: extract values using mise's jq-like JSON path syntax.
- `version_expr`: extract or post-process versions using an
  [expr-lang](https://expr-lang.org/) expression. The original contents are available as `body`,
  and versions produced by `version_regex` or `version_json_path` are available as `versions`.

These parsers are evaluated in-process and cannot run shell commands. Plain string entries remain
compatible with existing registry entries and backend-native parsers.

Only extract a value that determines compatibility with the tool binary. Good candidates include
an exact version, a minimum/required version, or a configuration-format major that is intentionally
coupled to the CLI major. Do not extract unrelated project versions, dependency versions, lockfile
schema revisions, or generic `version` fields that do not constrain the tool itself.

Include all filenames that the tool officially searches, including documented nested paths such as
`.config/tool.yml`. When suffixes overlap, mise uses the most specific matching path.

Idiomatic files are disabled by default. Users enable them for a registry shorthand with:

```sh
mise settings add idiomatic_version_file_enable_tools your-tool
```

### Backend Priority

List backends in order of preference. Users will get the first available
backend, but can override with explicit syntax like `mise use aqua:owner/repo`.
Only include `npm` as a fallback for a tool that already has a non-npm primary
backend when the npm package works with lifecycle scripts disabled.

### Tool Testing

All tools must include a test to verify proper installation:

```toml
test = { cmd = "command-to-run", expected = "expected-output-pattern" }
```

The test command should be reliable and the output pattern should use
<code v-pre>{{version}}</code> to match any version number.

If `test.cmd` needs extra mise-managed tools on PATH, declare them with
`test.tools`. This is used only by `mise test-tool`; it does not affect normal
tool installation.

```toml
test = { cmd = "gradle -V", expected = "Gradle", tools = ["java"] }
```

### Registry Examples

Recent tool additions:

- **DuckDB**: Simple github backend ([#4248](https://github.com/jdx/mise/pull/4248))

  ```toml
  [tools.duckdb]
  backends = ["github:duckdb/duckdb"]
  test = ["duckdb --version", "{{version}}"]
  ```

- **Biome**: Multiple backends ([#4283](https://github.com/jdx/mise/pull/4283))

  ```toml
  [tools.biome]
  backends = ["aqua:biomejs/biome", "github:biomejs/biome"]
  test = ["biome --version", "Version: {{version}}"]
  ```

## Adding Backends

:::warning Backend vs Tool Confusion
**Most contributors want to add tools, not backends.** Before reading this
section, make sure you actually need a new backend. Tools are individual
software packages (like `node` or `ripgrep`), while backends are installation
mechanisms (like `aqua` or `github`). If you want to add a specific tool to mise,
see [Adding Tools](#adding-tools) instead.
:::

:::warning Core Backend Acceptance Policy
**New backends are unlikely to be accepted into mise core.** They require
a lot of maintenance so it's generally better to use the [backend plugin system](backend-plugin-development.md) to add backends without core changes. A new backend would only be accepted for a major package manager
or tool that would greatly enhance mise's capabilities.

If you need a custom backend:

1. **Discuss with jdx first** in [Discord](https://discord.gg/UBa7pJUN7Z) or by
   creating a [discussion](https://github.com/jdx/mise/discussions)
2. **Consider if existing backends** (github, aqua, npm, pipx, etc.) can meet your
   needs
3. **Create a plugin** - use the [plugin system](tool-plugin-development.md) to create plugins for private/custom tools without core changes. Start with the [mise-tool-plugin-template](https://github.com/jdx/mise-tool-plugin-template) for a quick setup

Most tool installation needs can be met by existing backends, especially
[github](dev-tools/backends/github.md) for GitHub releases and
[aqua](dev-tools/backends/aqua.md) for comprehensive package management.
:::

Backends are mise's abstraction for different tool installation methods. Each
backend implements the `Backend` trait to provide consistent functionality
across different installation systems.

### Backend Types

- **Core Backends** (`src/backend/core/`) - Built-in language runtimes like
  Node.js, Python, Ruby
- **Package Manager Backends** (`src/backend/`) - npm, pipx, cargo, gem, go
  modules
- **Universal Installers** (`src/backend/`) - github, aqua for GitHub releases and
  package management
- **Plugin Backends** (`src/backend/`) - plugins can provide custom backends or individual tools

### Implementation Steps

1. **Create the backend module** in `src/backend/` (e.g., `my_backend.rs`)

2. **Implement the Backend trait**:

   ```rust
   use crate::backend::{Backend, BackendType};
   use crate::install_context::InstallContext;

   #[derive(Debug)]
   pub struct MyBackend {
       // backend-specific fields
   }

   impl Backend for MyBackend {
       fn get_type(&self) -> BackendType { BackendType::MyBackend }

       async fn list_remote_versions(&self) -> Result<Vec<String>> {
           // Implementation for listing available versions
       }

       async fn install_version(&self, ctx: &InstallContext,
                                 tv: &ToolVersion) -> Result<()> {
           // Implementation for installing a specific version
       }

       async fn uninstall_version(&self, tv: &ToolVersion) -> Result<()> {
           // Implementation for uninstalling a version
       }

       // ... other required methods
   }
   ```

3. **Register the backend** in `src/backend/mod.rs`:

   - Add your backend to the imports
   - Add it to the backend registry/factory function
   - Add the `BackendType` enum variant

4. **Add CLI argument parsing** in `src/cli/args/backend_arg.rs` if needed

5. **Update the registry** in `registry/` if it should be available as a
   shorthand

### Testing Requirements

- **Integration tests** in `e2e/backend/test_my_backend`
- **Test both installation and usage** of tools from your backend
- **Windows testing** if the backend supports Windows

### Documentation

- **Update backend documentation** in `docs/dev-tools/backends/`
- **Add usage examples** showing how to install tools with your backend
- **Update the registry documentation** if adding new shorthand tools

### Implementation Examples

Look at existing backends for patterns:

- `src/backend/github.rs` - Simple GitHub release installer
- `src/backend/npm.rs` - Package manager integration
- `src/backend/core/node.rs` - Full language runtime implementation

For detailed architecture information, see
[Backend Architecture](dev-tools/backend_architecture.md).

## Testing packaging

This is only necessary to test if actually changing the packaging setup.

### Ubuntu (apt)

This is for arm64, but you can change the arch to amd64 if you want.

```sh
docker run -ti --rm ubuntu
apt update -y
apt install -y curl
install -dm 755 /etc/apt/keyrings
curl -fSso /etc/apt/keyrings/mise-archive-keyring.pub https://mise.jdx.dev/gpg-key.pub
echo "deb [signed-by=/etc/apt/keyrings/mise-archive-keyring.pub arch=arm64] \
https://mise.jdx.dev/deb stable main" >/etc/apt/sources.list.d/mise.list
apt update -y
apt install -y mise
mise -V
```

### Fedora (dnf)

```sh
docker run -ti --rm fedora
dnf copr enable -y jdxcode/mise && dnf install -y mise && mise -v
```

### RHEL (dnf)

```sh
docker run -ti --rm registry.access.redhat.com/ubi9/ubi:latest
dnf copr enable -y jdxcode/mise && dnf install -y mise && mise -v
```
