docs/dev-tools/backends/http.md
You may install tools directly from HTTP URLs using the http backend. This backend downloads files from any HTTP/HTTPS URL and is ideal for tools that distribute pre-built binaries or archives through direct download links.
The code for this is inside of the mise repository at ./src/backend/http.rs.
The following installs a tool from a direct HTTP URL:
mise use -g http:my-tool[url=https://example.com/releases/my-tool-v1.0.0.tar.gz]@1.0.0
The version will be set in ~/.config/mise/config.toml with the following format:
[tools]
"http:my-tool" = { version = "1.0.0", url = "https://example.com/releases/my-tool-v1.0.0.tar.gz" }
http:my-tool[url=https://example.com/releases/my-tool-v1.0.0.tar.gz]@1.0.0The following tool-options are available for the http backend—these
go in [tools] in mise.toml.
url (Required)Specifies the HTTP URL to download the tool from. The URL supports templating with variables like version, os(), and arch():
[tools]
"http:my-tool" = { version = "1.0.0", url = "https://example.com/releases/my-tool-v{{version}}.tar.gz" }
You can also use static URLs without templating:
[tools]
"http:my-tool" = { version = "1.0.0", url = "https://example.com/releases/my-tool-v1.0.0.tar.gz" }
The following template functions are available in URLs (use double curly braces, e.g., version becomes <code v-pre>{{version}}</code>):
version - The tool versionos() - Operating system: macos, linux, or windowsarch() - Architecture: x64 or arm64os_family() - OS family: unix or windowsThe os() and arch() functions support remapping for tools that use different naming conventions:
[tools]
# HashiCorp tools use "darwin" instead of "macos" and "amd64" instead of "x64"
"http:sentinel" = {
version = "latest",
url = 'https://releases.hashicorp.com/sentinel/{{version}}/sentinel_{{version}}_{{os(macos="darwin")}}_{{arch(x64="amd64")}}.zip',
}
This produces URLs like:
sentinel_0.26.3_darwin_arm64.zipsentinel_0.26.3_darwin_amd64.zipsentinel_0.26.3_linux_amd64.zipFor tools that need different downloads per platform, use the table format:
[tools."http:my-tool"]
version = "1.0.0"
[tools."http:my-tool".platforms]
macos-x64 = { url = "https://example.com/releases/my-tool-v1.0.0-macos-x64.tar.gz" }
macos-arm64 = { url = "https://example.com/releases/my-tool-v1.0.0-macos-arm64.tar.gz" }
linux-x64 = { url = "https://example.com/releases/my-tool-v1.0.0-linux-x64.tar.gz" }
::: tip
You can use either macos or darwin, and x64 or amd64 for platform keys. macos and x64 are preferred in documentation and examples, but all variants are accepted.
OS/architecture values use mise's conventions: linux, macos, windows for operating systems and x64, arm64 for architectures. For platform-specific URLs, use the appropriate platform key (e.g., macos-x64, linux-arm64) and specify the full URL for each platform.
If you mess up and use something like darwin-aarch64 mise will try to figure out what
you meant and do the right thing anyhow.
:::
checksumVerify the downloaded file with a checksum:
[tools."http:my-tool"]
version = "1.0.0"
url = "https://example.com/releases/my-tool-v1.0.0.tar.gz"
checksum = "sha256:a1b2c3d4e5f6789..."
Instead of specifying the checksum here, you can use mise.lock to manage checksums.
[tools."http:my-tool"]
version = "1.0.0"
[tools."http:my-tool".platforms]
macos-x64 = {
url = "https://example.com/releases/my-tool-v1.0.0-macos-x64.tar.gz",
checksum = "sha256:a1b2c3d4e5f6789...",
}
macos-arm64 = {
url = "https://example.com/releases/my-tool-v1.0.0-macos-arm64.tar.gz",
checksum = "sha256:b2c3d4e5f6789...",
}
linux-x64 = {
url = "https://example.com/releases/my-tool-v1.0.0-linux-x64.tar.gz",
checksum = "sha256:c3d4e5f6789...",
}
checksum_urlURL of a published checksum source. When set, mise lock
resolves checksums for every target platform — including platforms other than
the one you are running on — without downloading the artifacts. This lets a
single machine produce a complete, cross-platform lockfile.
checksum_url is a template (supports <code v-pre>{{ version }}</code>, <code v-pre>{{ os() }}</code>, <code v-pre>{{ arch() }}</code>
and is platform-specific via platforms.<key>.checksum_url). It may point at any
of:
<artifact>.sha256), which may contain
just the hash or <hash> <filename>;<hash> <filename> for many platforms (the row
is matched by the artifact's filename);checksum_expr below.For individual and SHASUMS checksum files, the algorithm is detected from the
file's name (*.sha512, SHA512SUMS, *.md5, *.b3, defaulting to sha256).
# Individual checksum file (one per artifact)
[tools."http:my-tool"]
version = "1.0.0"
url = "https://example.com/releases/my-tool-{{ version }}-{{ os() }}-{{ arch() }}.tar.gz"
checksum_url = "https://example.com/releases/my-tool-{{ version }}-{{ os() }}-{{ arch() }}.tar.gz.sha256"
# SHASUMS (one file lists every platform)
[tools."http:other-tool"]
version = "1.0.0"
url = 'https://example.com/{{ version }}/other_{{ version }}_{{ os(macos="darwin") }}_{{ arch(x64="amd64") }}.zip'
checksum_url = 'https://example.com/{{ version }}/other_{{ version }}_SHASUMS'
checksum_exprWhen the checksum lives in a manifest (rather than a plain checksum file), use
checksum_expr to extract it. The manifest body fetched from checksum_url is
evaluated with expr-lang. The following variables are
available: body (the raw manifest), version, os, arch, url (the
resolved artifact URL for the target), and filename.
The expression must evaluate to a qualified algo:hash string (e.g.
sha256:<hash>, sha512:<hash>). Build the prefix in the expression: prepend a
literal when the algorithm is fixed ("sha256:" + entry.hash), or read it from
the manifest when it varies (entry.algo + ":" + entry.hash).
[tools."http:my-tool"]
version = "1.10.0"
checksum_url = "https://example.com/versions.json"
# Match the file whose url equals the resolved artifact url, return sha256:<hash>
checksum_expr = '"sha256:" + filter(fromJSON(body)[version + ""].files, { #.url == url })[0].sha256'
[tools."http:my-tool".platforms]
linux-x64 = { url = "https://example.com/my-tool-{{ version }}-linux-x86_64.tar.gz" }
macos-arm64 = { url = "https://example.com/my-tool-{{ version }}-macos-arm64.tar.gz" }
::: tip expr-lang gotchas
The predicate placeholder must be written as { #... } with a space after
{, because {# is the Tera comment delimiter. To index a map by a runtime
value, force evaluation with [version + ""] — a bare [version] is treated as
the literal key "version".
:::
sizeVerify the downloaded file size:
[tools."http:my-tool"]
version = "1.0.0"
url = "https://example.com/releases/my-tool-v1.0.0.tar.gz"
size = "12345678"
You can specify different sizes for different platforms:
[tools."http:my-tool"]
version = "1.0.0"
[tools."http:my-tool".platforms]
macos-x64 = {
url = "https://example.com/releases/my-tool-v1.0.0-macos-x64.tar.gz",
size = "12345678",
}
macos-arm64 = {
url = "https://example.com/releases/my-tool-v1.0.0-macos-arm64.tar.gz",
size = "9876543",
}
linux-x64 = {
url = "https://example.com/releases/my-tool-v1.0.0-linux-x64.tar.gz",
size = "11111111",
}
strip_componentsNumber of directory components to strip when extracting archives:
[tools."http:my-tool"]
version = "1.0.0"
url = "https://example.com/releases/my-tool-v1.0.0.tar.gz"
strip_components = 1
::: info
If strip_components is not explicitly set, mise will automatically detect when to apply strip_components = 1. This happens when the extracted archive contains exactly one directory at the root level and no files. This is common with tools like ripgrep that package their binaries in a versioned directory (e.g., ripgrep-14.1.0-x86_64-unknown-linux-musl/rg). The auto-detection ensures the binary is placed directly in the install path where mise expects it.
:::
binRename the downloaded binary to a specific name. This is useful when downloading single binaries that have platform-specific names:
[tools."http:docker-compose"]
version = "2.29.1"
url = "https://github.com/docker/compose/releases/download/v{{ version }}/docker-compose-linux-x86_64"
bin = "docker-compose" # Rename from docker-compose-linux-x86_64 to docker-compose
::: info
When downloading single binaries (not archives), mise automatically removes OS/arch suffixes from the filename. For example, docker-compose-linux-x86_64 becomes docker-compose automatically. Use the bin option only when you need a specific custom name.
:::
rename_exeRename the executable inside an extracted archive to a specific name. This is useful when archives contain binaries with platform-specific names or when installing kubectl plugins that need specific naming:
[tools."http:openunison-cli"]
version = "1.0.0"
url = "https://nexus.tremolo.io/repository/openunison-cli/openunison-cli-v{{version}}-linux.zip"
rename_exe = "kubectl-openunison-cli" # Rename extracted binary for kubectl plugin
This works by searching for the first executable in the extracted directory (or bin_path if specified) and renaming it to the specified name.
To rename multiple binaries from one archive, use the table form — each key is a source name (an exact file name or a glob) and each value is the new name:
[tools."http:mytool"]
version = "1.0.0"
url = "https://example.com/mytool-v{{version}}-linux.zip"
rename_exe = { "mytool-*" = "mytool", "myhelper-*" = "myhelper" }
::: tip
Use bin for renaming single binary downloads, and rename_exe for renaming executables inside archives.
:::
formatExplicitly specify the archive format when the URL lacks a file extension or has an incorrect extension:
[tools."http:my-tool"]
version = "1.0.0"
url = "https://example.com/releases/my-tool-v1.0.0"
format = "tar.xz" # Explicitly specify the format
::: info
If format is not specified, mise automatically detects the format from the final URL after HTTP redirects, falling back to the configured URL. This allows an extensionless download endpoint to redirect to an archive such as .tar.gz. An explicit format always takes precedence, so use it when neither URL has a useful extension or when you need to override the detected format.
:::
You can specify different formats for different platforms:
[tools."http:my-tool"]
version = "1.0.0"
[tools."http:my-tool".platforms]
macos-x64 = {
url = "https://example.com/releases/my-tool-v1.0.0-macos-x64",
format = "tar.xz",
}
linux-x64 = {
url = "https://example.com/releases/my-tool-v1.0.0-linux-x64",
format = "tar.gz",
}
windows-x64 = {
url = "https://example.com/releases/my-tool-v1.0.0-windows-x64",
format = "zip",
}
version_list_urlFetch available versions from a remote URL. This enables mise ls-remote to list available versions for HTTP-based tools:
[tools."http:my-tool"]
version = "1.0.0"
url = "https://example.com/releases/my-tool-v{{version}}.tar.gz"
version_list_url = "https://example.com/releases/versions.txt"
The version list URL can return data in multiple formats:
2.0.53)["1.0.0", "1.1.0", "2.0.0"][{"version": "1.0.0"}, {"tag_name": "v2.0.0"}]{"versions": ["1.0.0", "2.0.0"]}Version prefixes like v are automatically stripped.
version_regexExtract versions from the version list URL response using a regular expression:
[tools."http:my-tool"]
version = "1.0.0"
url = "https://example.com/releases/my-tool-v{{version}}.tar.gz"
version_list_url = "https://example.com/releases/"
version_regex = 'my-tool-v(\d+\.\d+\.\d+)\.tar\.gz'
The first capturing group is used as the version. If no capturing group is present, the entire match is used.
version_json_pathExtract versions from JSON responses using a jq-like path expression:
[tools."http:my-tool"]
version = "1.0.0"
url = "https://example.com/releases/my-tool-v{{version}}.tar.gz"
version_list_url = "https://api.example.com/releases"
version_json_path = ".[].tag_name"
Supported path expressions:
. - root value.[] - iterate over array elements.[].field - extract field from each array element.field - extract field from object.field[] - iterate over array in field.field.subfield - nested field access.data.versions[] - complex nested paths.[?field=value] - filter array elements where field equals valueExamples:
# GitHub releases API format
version_json_path = ".[].tag_name"
# Nested versions array
version_json_path = ".data.versions[]"
# Release info objects
version_json_path = ".releases[].info.version"
# Filter for stable releases only (e.g., Flutter)
version_json_path = ".releases[?channel=stable].version"
The filter syntax [?field=value] allows filtering JSON arrays before extraction. This is useful for APIs that return multiple release channels (stable, beta, dev) and you only want specific ones.
version_exprExtract versions using an expr-lang expression. This provides the most flexibility for complex version extraction logic:
[tools."http:my-tool"]
version = "latest"
url = "https://example.com/releases/my-tool-v{{ version }}.tar.gz"
version_list_url = "https://example.com/versions.txt"
version_expr = 'split(body, "\n")'
The expression receives the HTTP response body as the body variable and should return an array of version strings.
Example expressions:
# Split newline-separated versions
version_expr = 'split(body, "\n")'
# Split and filter empty lines
version_expr = 'filter(split(body, "\n"), # != "")'
# Parse JSON and extract object keys (useful for HashiCorp-style JSON)
# e.g., {"versions": {"1.0.0": {}, "2.0.0": {}}}
version_expr = 'keys(fromJSON(body).versions)'
The expr-lang library provides built-in functions including:
fromJSON(string): Parse a JSON string into a valuetoJSON(value): Convert a value to a JSON stringkeys(map): Get the keys of an object/map as an arrayvalues(map): Get the values of an object/map as an arraylen(value): Get the length of a string, array, or map::: tip
version_expr takes precedence over version_regex and version_json_path if multiple are specified. Use it when the other options aren't flexible enough for your use case.
:::
bin_pathSpecify the directory containing binaries within the extracted archive, or where to place the downloaded file. This supports templating with <code v-pre>{{version}}</code>:
[tools."http:my-tool"]
version = "1.0.0"
url = "https://example.com/releases/my-tool-v1.0.0.tar.gz"
bin_path = "my-tool-{{version}}/bin" # expands to my-tool-1.0.0/bin
Binary path lookup order:
bin_path is specified, use that directorybin_path is not set, look for a bin/ directory in the install pathbin/ directory exists, search subdirectories for bin/ directoriesbin/ directories are found, use the root of the extracted directoryThe HTTP backend implements an intelligent caching system to optimize disk usage and installation speed:
For normal user installations, downloaded and extracted files are cached in
$MISE_DATA_DIR/http-tarballs/ instead of being stored separately for each tool
installation. By default:
~/.local/share/mise/http-tarballs/~/.local/share/mise/http-tarballs/Explicit mise install --system, mise install --shared, and mise install-into installations are extracted directly into their destination.
They do not use this persistent extraction cache, so the resulting installation
is self-contained and does not link to the installing user's home directory.
Cache keys are generated based on the file content to ensure identical downloads are shared across tools:
strip_components is included in the cache key since it affects the extracted structureExample cache directory structure:
~/.local/share/mise/http-tarballs/
├── 71f774faa03daf1a58cc3339f8c73e6557348c8e0a2f3fb8148cc26e26bad83f/
│ ├── bin/my-tool
│ └── metadata.json
└── 1c2af379bdf1fed266bc44b49271e2df5b0dafae09f1cc744b3505ec50c84719_strip_1/
├── my-tool
└── metadata.json
Normal user installations are symlinks to the cached extracted content:
~/.local/share/mise/installs/http-my-tool/1.0.0 → ~/.local/share/mise/http-tarballs/71f774...
This approach provides several benefits:
System, shared, and install-into destinations contain real files rather than these symlinks. This avoids leaving hidden cache entries behind after an uninstall and keeps shared installations independent of a specific user's data directory.
Each cache entry includes a metadata.json file with information about the cached content:
{
"url": "https://example.com/releases/my-tool-v1.0.0.tar.gz",
"checksum": "sha256:a1b2c3d4e5f6789...",
"size": 1024000,
"extracted_at": 1703001234,
"platform": "macos-arm64"
}
Normal HTTP installations store their cache in
$MISE_DATA_DIR/http-tarballs/. It is intentionally outside MISE_CACHE_DIR,
so mise cache clear does not remove content that installed symlinks still
reference.
System, shared, and install-into destinations do not create a persistent HTTP extraction cache.