# asdf: one CLI for every runtime version, driven by plugins

> asdf is a version manager that keeps Ruby, Node.js, Python, Erlang and anything else behind one command set and one .tool-versions file per project. It is the right tool when you are tired of juggling nvm, rbenv and pyenv, and the wrong one when you need a language's own toolchain to drive the install.

**asdf-vm/asdf** — Extendable version manager with support for Ruby, Node.js, Elixir, Erlang & more

- Repository: https://github.com/asdf-vm/asdf
- Website: https://asdf-vm.com/
- Stars: 25,589 · Forks: 944
- Language: Go
- License: MIT
- Published: 2026-09-21 · Updated: 2026-09-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/asdf-vm-asdf

## The problem asdf solves, and who feels it

A project that mixes Ruby, Node.js and Python normally means three version managers, three sets of commands, and three places where the pinned version lives. asdf collapses that into one CLI and one configuration file per project. The README describes it as a tool that can manage multiple language runtime versions on a per-project basis, comparing it to gvm, nvm, rbenv and pyenv combined, with the instruction to simply install your language's plugin.

The audience is developers who move between repositories with different runtime requirements, and teams that want a version pin to travel with the code rather than live in a shell profile. The README also lists support for existing config files, naming .node-version, .nvmrc and .ruby-version, which matters for migration: a repository does not have to abandon its current pin file on day one. Shell completion is offered for Bash, Zsh, Fish and Elvish, and the repository topics extend that list to PowerShell, nushell and elvish, so the tool is not limited to one shell culture.

## How the plugin mechanism actually works

asdf core does not know how to install Ruby. It knows how to call a plugin that does. The README frames this as a plugin system to add support for your language of choice, with a separate asdf-plugins repository listing what is available and an asdf-plugin-template for writing your own. That split is the whole architecture: the core binary handles version resolution, the .tool-versions file, and the command surface, while each plugin owns the download, build and install steps for one runtime.

The practical consequence is that the quality of your asdf experience is the quality of the plugins you pick. A plugin that lags behind a new language release means asdf cannot install that release, even though the core binary was updated yesterday. The repository layout reflects this separation: cmd/ holds the entry point, internal/ holds the implementation, and the Go module requires urfave/cli for the command layer, go-git for repository access, and an INI parser, which is consistent with reading version files. The README does not describe the plugin interface itself, so anyone writing a plugin is pointed at the documentation site and the template rather than the core README.

## Building asdf and pinning your first runtime

The README does not carry install commands. It links to a Getting Started page on asdf-vm.com for that, so treat the documentation site as the source of truth for the installation step rather than copying a command from a blog post. The repository itself is a Go module, and the Makefile defines a build target that compiles ./cmd/asdf into a binary named asdf in the repository root:

```bash
make build
```

Once the binary is on your PATH, the workflow is plugin first, version second. The README's own framing is to install your language's plugin, and the asdf-plugins repository is where the list lives. The README does not print the subcommand sequence for adding a plugin or installing a version, so check the All Commands page on the docs site before scripting it. The file that results is .tool-versions, described in the README as a single config file per project. Commit it, and a teammate who has the same plugin installed gets the same runtime when they enter the directory, because asdf switches versions as you traverse your directories.

The Makefile also exposes targets that matter if you intend to follow the project rather than just consume it. go fmt and gofumpt run under fmt, go vet runs under vet, and test runs the suite with the race detector:

```bash
make test
```

Those targets are the maintainers' own definition of a healthy checkout, which is a useful signal when you are deciding whether a plugin or a fork is worth trusting.

## Where asdf gets in your way

The plugin boundary is also the failure boundary. If a plugin is unmaintained, asdf will happily list it, and the install will fail or produce a stale build. The core team does not control plugin behaviour, and the README draws that line explicitly by pointing to a separate asdf-plugins repository and a separate template for authors. There is no statement in the README that plugin quality is vetted.

The second limitation is that asdf is an indirection. When a language's own manager adds a feature, asdf only benefits once a plugin exposes it. If your workflow depends on a manager's specific behaviour, such as a build flag or an environment hook, you are relying on the plugin author to surface it. The README lists .node-version, .nvmrc and .ruby-version compatibility as a migration aid, but that is file compatibility, not command compatibility: the language-specific commands you learned elsewhere do not carry over.

Finally, the README is thin on the operational side. It does not document rollback, does not describe what happens when two plugins claim the same executable name, and does not cover uninstalling a plugin's runtimes. Those questions are answered on the documentation site or not at all in the repository.

## asdf against rbenv, pyenv and nvm

The honest comparison is scope, not speed. rbenv manages Ruby, pyenv manages Python, nvm manages Node.js. Each is maintained by people who care about one runtime, and each can afford to know that runtime's build system in detail. asdf trades that depth for breadth: one command set, one .tool-versions file, one global config keeping defaults in one place, as the README puts it.

If your work is single-language, a dedicated manager costs you nothing extra and removes the plugin layer entirely. The case for asdf starts when a repository needs two or three runtimes at once, because then the dedicated managers stop being a convenience and start being three shell integrations that can disagree with each other. The README's ballad makes the same argument in verse, and it is worth reading as a statement of intent rather than documentation.

## Maintenance, upgrade cost and licence

The repository is not archived, and the last push was on 2026-09-03, which is recent. Releases follow a steady cadence: v0.20.0 on 2026-07-07, v0.19.0 on 2026-04-24, and v0.18.1 on 2026-03-04. The project is still on a 0.x version line, so the maintainers have not declared a stable API. Expect the command surface and configuration details to move between minor releases, and read the CHANGELOG before upgrading a pinned CI image.

The upgrade cost is mostly on the plugin side. Upgrading the core binary is one step; upgrading the runtimes a plugin can install depends on that plugin's release schedule, which asdf core does not control. Budget for both.

The licence is MIT, stated in the repository and in the LICENSE file. That is permissive and imposes few conditions on how you redistribute or embed the binary, but it also means no warranty. If you need a support contract or an indemnity, this is not the project for you. Nothing here is legal advice; read the LICENSE file itself.

## Conclusion

Adopt asdf if you work across several runtimes in one repository and want a single command set plus a single .tool-versions file to pin them. Skip it if your team already depends on a language-specific manager's own features, such as nvm's shell integration or pyenv's build hooks, because asdf delegates that work to plugins you have to trust. Before committing, verify that a plugin exists for every runtime you need, check how often that plugin is updated, and confirm that your CI can install asdf itself, since the README points at the docs site for those steps rather than spelling them out in the repository.

## FAQ

### What is asdf used for?

asdf manages multiple language runtime versions on a per-project basis through a single CLI. The README describes it as comparable to having gvm, nvm, rbenv and pyenv in one tool, with support added through plugins.

### How do I install asdf?

The README does not include installation commands. It links to a Getting Started page on asdf-vm.com, so that page is where the install steps live.

### What does ASDF stand for in asdf-vm?

The README does not expand the name into a phrase. It presents asdf as the name of the version manager and links to asdf-vm.com for documentation.

## Sources

- [asdf-vm/asdf on GitHub](https://github.com/asdf-vm/asdf)
- [License: MIT](https://github.com/asdf-vm/asdf/blob/master/LICENSE)
- [Project website](https://asdf-vm.com/)
- [README](https://github.com/asdf-vm/asdf/blob/master/README.md)
- [Releases](https://github.com/asdf-vm/asdf/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/asdf-vm-asdf
