Link Search Menu Expand Document


  1. Why install Elm tools using elm-tooling instead of npm?
  2. When should I use elm-tooling to install tools?
    1. For Elm applications
    2. For Elm packages
    3. For Elm tools
  3. How does elm-tooling install work?
  4. Who uses elm-tooling?
  5. Is elm-tooling stable?
  6. Can I install the tools globally?
  7. How do I uninstall?
  8. What’s the difference compared to asdf?
  9. Which tools are supported?
  10. Are Node.js based tools such as elm-review supported?
  11. Why does elm-tooling put stuff in node_modules/.bin/?
  12. What does elm-tooling put in node_modules/.bin/?
  13. Is running stuff with npx slow?
  14. Is elm-tooling forever locked into the npm ecosystem?
  15. What’s the point of having elm-json in elm-tooling.json?
  16. Why not put stuff in elm.json instead?
  17. How do I use a proxy?

Why install Elm tools using elm-tooling instead of npm?

Installing elm, elm-format and elm-json using npm (version 7) and elm-tooling:

Metric npm elm-tooling
Number of packages 65 1
node_modules/ size 40 MiB 116 KiB
Installation time 8 s 2.5 s
Re-installation time 1.6 s 1.5 s
Download verification None SHA256


  • Number of packages: Why are 65 npm packages installed just to get elm, elm-format and elm-json onto my computer?

  • node_modules/ size: How can elm-tooling be so much smaller?

    • Some of the 65 npm packages are pretty heavy. elm-tooling has no dependencies, and instead uses the curl (or wget) and tar that come with basically all operating systems (even Windows!) out of the box.
    • elm-tooling puts the executables in a central location (~/.elm/elm-tooling) instead of in the local node_modules/ folder.

    (Measured on macOS; executables vary slightly in size between macOS, Linux and Windows (also disk block size).)

  • Installation and re-installation time: This of course depends on your Internet speed, and also on npm cache. npm used to be much slower, but has stepped up their game. Either way, with elm-tooling you get the other benefits mentioned here (fewer dependencies and improved security) besides good performance. (Note that re-installation time for elm-tooling includes re-installing elm-tooling itself with npm. That’s where most time is spent, while elm-tooling install is basically a no-op when the executables already exist in ~/.elm/elm-tooling.)

  • Download verification: Security. elm-tooling ships with SHA256 hashes for all tools it knows about and verifies that download files match. elm-tooling itself is hashed in your package-lock.json (which you commit). The elm, elm-format and elm-json npm packages on the other hand download stuff without verifying what they got.

Finally, the elm, elm-format and elm-json npm packages are essentially hacks. The elm npm package for instance does not contain Elm at all. It just contains some code that downloads Elm using a postinstall script (or at the first run).

When should I use elm-tooling to install tools?

In short: For the same reasons you’d install tools using npm.

If you’re just starting out, don’t forget to check out the official documentation as well:

If you’re just getting started, install Elm whatever way you think is the easiest so you can get started coding. Installing Elm globally using the installer can be a great way. But if you’re already familiar with installing stuff with npm it might be just as easy to start with elm-tooling. It doesn’t really matter. You can always change the installation method later.

For Elm applications

When a new Elm version comes out, your old projects will continue to work since they have a fixed local Elm version. Now, new Elm versions aren’t released very often so it’s not a super big deal, but when a new version does come out it’s nice not having to upgrade each and every project at the same time.

elm-format releases a little bit often than Elm, and even with a patch release there can be tiny formatting changes. You wouldn’t want two contributors using different elm-format versions and format files back and forth all the time.

For Elm packages

For the same reasons as for Elm applications. Make it easy for contributors to get the correct versions of all tools.

For Elm tools

Have you written an Elm related tool in Node.js? If your tool calls for example elm, elm-format or elm-json you’re gonna need those tools locally for development, as well as in CI. You can use elm-tooling.json and elm-tooling install for this purpose.


  • Don’t make elm-tooling.json part of your npm package. elm-tooling.json is only for development and CI, not for production code. Use the getExecutable API if you need to depend on some other tool.
  • Use "prepare": "elm-tooling install" instead of "postinstall": "elm-tooling install". See Quirks.

How does elm-tooling install work?

In elm-tooling.json you can specify your tools:

    "tools": {
        "elm": "0.19.1",
        "elm-format": "0.8.4"

elm-tooling install downloads the tools you’ve specified to “Elm home” (if they don’t exist already). After using elm-tooling in a couple of projects you might end up with something like this:

├── elm
│  └── 0.19.1
│     └── elm
├── elm-format
│  ├── 0.8.3
│  │  └── elm-format
│  └── 0.8.4
│     └── elm-format
└── elm-json
   └── 0.2.8
      └── elm-json

elm-tooling install then creates links in your local ./node_modules/.bin/ folder:

./node_modules/.bin/elm -> ~/.elm/elm-tooling/elm/0.19.1/elm
./node_modules/.bin/elm-format -> ~/.elm/elm-tooling/elm-format/0.8.4/elm-format

The algorithm is roughly:

  1. Find an elm-tooling.json up the directory tree.

  2. For every tool/version pair in "tools":

    1. Look it up in the built-in list of known tools, to get the URL to download from and the expected number of bytes and SHA256 hash.

    2. Unless the executable already exists on disk in ~/.elm/:

      1. Download the URL using curl, wget or Node.js’ https module.

      2. Verify that the downloaded contents has the expected number of bytes.

      3. Verify that the downloaded contents has the expected SHA256 hash.

      4. Extract the executable using tar or Node.js’ zlib module (gunzip).

      5. Make sure the extracted file is executable (chmod +x).

    3. Create a link in ./node_modules/.bin/. (The node_modules/ folder is always located next to your elm-tooling.json.)

Who uses elm-tooling?

elm-test and elm-review both use elm-tooling to install elm-json.

The elm-pages-starter template uses elm-tooling.json and elm-tooling to install elm and elm-format.

Is elm-tooling stable?

Yes! It’s tested on macOS, Linux and Windows, and has great test coverage. It’s written in strict TypeScript, and focuses on handling errors at all points. There are no planned features, other than adding support for new tools and versions as they come, and adding validation for new fields in elm-tooling.json as they are invented.

Can I install the tools globally?

There’s no global elm-tooling.json. Only local, per-project ones.

As long as you define the needed tools in every project, you don’t really need global installations. Use npx elm and npx elm-format etc. A benefit of not having global installations is that you can never run the global version instead of the project version by mistake.

If you want a global elm command you could try the official installer.

On macOS and Linux, you could alternatively add symlinks in your $PATH. For example, on macOS:

ln -s ~/.elm/elm-tooling/elm/0.19.1/elm /usr/local/bin/elm

Another approach would be to create a “project” somewhere, and put its ./node_modules/.bin/ in $PATH. For example, you could add ~/my-global-elm-tooling/node_modules/.bin/. Beware that ./node_modules/.bin/ might contain more things than just elm and elm-format etc, depending on what npm packages you (indirectly) install.

How do I uninstall?

  • To remove elm-tooling itself from a project, run npm uninstall elm-tooling inside it.

  • To remove downloaded executables, remove the directory where elm-tooling puts them. The default locations are:

    • macOS and Linux: ~/.elm/elm-tooling/
    • Windows: %APPDATA%\elm (for example, C:\Users\John\AppData\Roaming\elm\elm-tooling)

    If you’d like to remove just one executable, here are some example paths to look at:

    • macOS and Linux: ~/.elm/elm-tooling/elm/0.19.1/elm
    • Windows: C:\Users\John\AppData\Roaming\elm\elm-tooling\elm\0.19.1\elm.exe

What’s the difference compared to asdf?

The asdf version manager has support for Elm and elm-format. Here are some differences:

  • asdf supports macOS and Linux, while elm-tooling also supports Windows.
  • asdf does not verify what it downloaded, while elm-tooling uses SHA256 to check downloads.
  • asdf requires collaborators to use asdf as well (or figure out themselves how to get the correct versions of all tools), while elm-tooling only requires Node.js and npm which are more commonly installed.

Which tools are supported?

Since Elm tools are so few and update so infrequently, elm-tooling can go with a very simple and reliable approach: Supported tool names, versions and SHA256 are hard coded – see KnownTools.ts.

Open an issue or pull request if you’d like to see support for another tool or version!

Will elm-tooling outgrow this approach some day? Yeah, maybe. But until then – KISS!

Only supporting Elm tooling rather than trying to be a generalized installation tool allows elm-tooling to be much simpler and laser focused on making the Elm experience as good as possible.

Are Node.js based tools such as elm-review supported?

The CLI for elm-review is written in Node.js and uses other npm packages. You can’t beat npm (or other Node.js package managers) when it comes to installing npm packages.

So Node.js based tools are not supported. Only tools that are distributed as platform specific executables are.

Why does elm-tooling put stuff in node_modules/.bin/?

Installing stuff into the local node_modules/.bin/ folder might sound strange at first, but piggy-backing on the well-supported npm ecosystem is currently the best way of doing things. This lets you use the tools field of elm-tooling.json without your editor and build tools having to support it.

What does elm-tooling put in node_modules/.bin/?

On macOS and Linux, node_modules/.bin/ only contains symlinks. These symlinks point to executable files. The executables don’t even need to invoke Node.js! So elm-tooling install simply puts symlinks in node_modules/.bin/ too. For example, node_modules/.bin/elm could point to ~/.elm/elm-tooling/elm/0.19.1/elm.

On Windows, npm creates three shell scripts (cmd, PowerShell and sh) per executable in node_modules/.bin/, in lieu of symlinks. elm-tooling install mimics these shell scripts, but they are much simpler. All they need to do is invoke an executable in %APPDATA%\elm, passing along all arguments.

Is running stuff with npx slow?

npx is written in Node.js. All Node.js tools have a ~200 ms startup cost. Other than that there’s no difference.

Is elm-tooling forever locked into the npm ecosystem?


The elm-tooling CLI could be written in something other than Node.js.

Instead of using for example npx elm to run tools, we could have elm-tooling run elm. elm-tooling run would read elm-tooling.json to find which version of the tool to use, and also add any other tools in elm-tooling.json to $PATH for the execution. This way tools can do stuff like spawn("elm") and get the correct version as if it was installed globally.

IDE:s and editors would have to support elm-tooling.json somehow, too, though, instead of looking for executables in ./node_modules/.bin/. I’m sure we would find a solution there, but for the time being it’s much easier for elm-tooling to mimic the npm stuff so things just work.

What’s the point of having elm-json in elm-tooling.json?

elm-test and elm-review both use elm-tooling to install elm-json.

By having elm-json in elm-tooling.json you can download all executables in parallel in one go.

For people who aren’t Elm experts, it’s nice to have elm-json available for all contributors. It’s often easier to install Elm packages using elm-json than elm install.

Why not put stuff in elm.json instead?

elm has a tendency to remove keys it does not recognize whenever it updates elm.json.

How do I use a proxy?

elm-tooling uses curl to download stuff if it exists, otherwise wget, and finally the https Node.js core module. So if you need to do any proxy stuff or something like that, you do that via the environment variables and config files that curl and wget understand. For example, curl proxy environment variables. Most systems – even Windows! – come with either curl or wget.

This also applies to any npm package that under the hood uses elm-tooling to install something.