Author avatar

Brujo Benavides

10 Essential Erlang Tools for Erlang Developers

Brujo Benavides

  • Sep 6, 2019
  • 19 Min read
  • Sep 6, 2019
  • 19 Min read


In this tutorial, I'll cover the tools that I use the most in my day-to-day life as an Erlang developer. All or most of the topics that make the cut share the following characteristics:

  • they're built specifically for Erlang (i.e. no text-editors, no git, etc.).
  • they're not tailored to a particular kind of application (i.e. you can use them to develop any kind of Erlang application).

Working in the Shell

Like many other languages, the most common way to interact with an Erlang VM is through the Erlang shell. You can use it to test code, learn how to use Erlang, and debug production systems. The Erlang shell is a powerful thing, but you can add much more power to it with the three tools that I will show you below.

I see a blondie

1. user_default

In general, to evaluate functions outside a module in Erlang you have to prepend their names with the corresponding module name. For example, if you want to sum up a list, you have to do the following:

11> lists:sum([1, 2, 3]).

But you'll notice that for certain functions in the shell, you don't need explicit definitions. For example:

11> c(your_module).
32> h().
41: c(your_module)
5-> {ok,your_module}
73> e(1).

Where do such functions live? They're defined in the shell_default module. You can add your own default functions as well. To do that, you have to create and load a module called user_default. As an example, I like to run bash commands when using the Erlang shell; I don't want to leave the shell just to copy a file. Erlang/OTP gives you a a function to do that: os:cmd/1, but that will not print out the output of your command. Instead this command will return that output as a string. You have to print the string to see the output properly.

So, using user_default, I added the following lines to my user_default.erl file:

4cmd(Cmd) -> io:format("~s~n", [os:cmd(Cmd)]).

Now I can run, execute, and print the results of os:cmd/1 using my shell:

11> c(user_default).
2{ok, user_default}
32> cmd("cat user_default.erl").
7cmd(Cmd) -> io:format("~s~n", [os:cmd(Cmd)]).

2. ~/.erlang

But then, I have to manually load user_default in every single shell that I open. ~/.erlang to the rescue! ~/.erlang is a file that works almost like ~/.bashrc for bash. Upon starting, the shell reads ~/.erlang and evaluates each one of the expressions it finds there as if they were inputted to the shell.

So, I added the following lines to that file:

1UD = "/path/to/my/user_default".

My ~/.erlang is actually more complicated than that, but the shell_default should give you the gist of my modifications. Now, every shell that I open comes with support for cmd/1.

3. erlang-history

One of the most annoying things about the Erlang shell is that it does not keep track of command history very well. In fact, default settings will erase all command history upon closing the shell. I've seen many devs working around this limitation by having a notebook of erlang expressions from which they copy & paste into the console every time. (Some of them use user_default and/or ~/.erlang for that.)

But still, it's not a real solution, right? To properly fix the issue, right after installing a new version of Erlang/OTP, I install erlang-history. It's a really tiny almost-invisible hack to the Erlang/OTP distribution with one simple purpose: to keep track of your previous commands in your Erlang shells and let you reuse them. Simple, yet amazingly useful.

Managing your Apps

As soon as you move past the simple examples and into the realm of OTP apps, you'll be much better off working with a build tool that can help you organize your code, manage dependencies, build releases, run tests, and more.

When working with Erlang, you can either embrace the Makefile or try get as far away from it as possible.


If using Makefiles to build and manage your projects seems natural to you, you will probably like is basically a big Makefile script that you can include in your own Makefile with something like:


With that, you get access to many commands, like

  • $ make to build your project
  • $ make tests to run your test suites
  • $ make rel to generate a release

You can find the whole list using $ make help, and you can, of course, add more by just extending your Makefile. also provides a plugin infrastructure, where you can add build tools, like or

The best thing about is that it provides the necessary support for building almost any existing Erlang library, regardless of the tool that library owners use to maintain it. With you can include applications built with rebar, rebar3, (as expected :P), ad-hoc Makefiles, and others in your depositories. You can download deps from github,, bitbucket, your local filesystem and many other places. Clearly makes Erlang vastly more versatile.

5. rebar3

Now, if Makefiles are not your thing and you prefer to use the official build tool (since March 2016) sponsored by the Erlang/OTP team, then rebar3 is your way to go.

rebar3 is written entirely in Erlang and the idea is that you can use it without adding any Makefile to your project.

Once you install it in your system, you should add a rebar.config file in the root folder of your project. A minimal one looks something like this (although even the options below are optional):

1{deps, []}.
2{erl_opts, [debug_info]}.
3{cover_enabled, true}.

You can find the list of all the options you can speficy here. Then, you can run commands like the ones below in your shell:

  • $ rebar3 compile to fetch deps and build your project
  • $ rebar3 ct to run your tests
  • $ rebar3 release to generate a release

Of course, $ rebar3 help gives you the list of all available commands. And, just like, rebar3 is also extensible through plugins.

One of the best things about rebar3 is that, by using rebar.lock, it provides repeatable builds. That way, once you're sure your project works as expected, you can be sure it will keep working as expected no matter how many times or in how many different places you compile it.

Ensuring Code Quality

I've personally stated a couple of times online (in interviews and blog posts) how important it is (especially if you work on open-source projects) to maintain high code quality. Erlang comes with several tools that will help you with that. Erlang tools can even help you detect bugs that you'll probably find at runtime… without running your system!


6. dialyzer

Dialyzer is a static analysis tool that will help you identify type inconsistencies in your code, like when you are passing a binary() as the parameter to a function that expects a string(). This used to be a very very slow tool, but it has since been highly optimized and now runs smoothly, especially if you run it frequently. Plus, it's integrated in both rebar3 and (Follow the links to learn more.)

If you use dialyzer (dia.erl), you'll see results like the one below.

1  Checking whether the PLT dia.plt is up-to-date... yes
2  Proceeding with analysis...
3    compile    (+0.09s):   0.02s (   1 modules)
4    clean      (+0.00s):   0.00s
5    remote     (+0.00s):   0.18s
6    order      (+0.00s):   0.00s
7    typesig    (+0.12s):   0.01s (      3 SCCs)
8    order      (+0.00s):   0.00s
9    refine     (+0.00s):   0.01s (   1 modules)
10    warning    (+0.00s):   0.00s (   1 modules)
11              (+ 0.71s)
13dia.erl:5: Function bad/0 has no local return
14dia.erl:5: The call lists:flatten(<<_:168>>) will never return since it differs in the
15            1st argument from the success typing arguments: ([any()])
16 done in 0m1.16s
17done (warnings were emitted)

In this case in line 5 of dia.erl, we are calling lists:flatten/1 with a 168-bit long binary (<<_:168>>) when that function expects a list ([any()]) as its parameter. This warning was not raised at compile time, but using dialyzer you can find it before it pops up during run time.

7. xref

A much simpler tool that's been packed in the Erlang/OTP distribution is xref. xref is a cross-reference tool that analyses your code and finds calls to unexistent functions, deprecated functions and other similar things. It doesn't search as deeply as dialyzer, but it will spot obvious bugs in no time.

xref is also integrated with rebar3 and (The documentation is not there yet, but it uses xref_runner to check your code).

xref can find errors like:

12===> Running cross reference analysis...
3===> Warning: your_module:your_function/1 calls undefined function this_function:doesnt_exist/1 (Xref)

There xref detected that we're calling a function that doesn't exist from within the code of your_module:your_function/1. Again, that's something that the compiler will not warn you about.

8. elvis

Besides analyzing your code in search for bugs with xref and dialyzer, you can ensure that the code in your project is maintainable. elvis is a command-line tool (and an Erlang application as well) that you can use to verify the compliance of your code to certain style rules, which you pre-define in your elvis.config file. There is a plugin for rebar3 and another one for You can also use elvis online to check your github pull requests.

Its output looks like the following:

1Loading files...
2Loading src/dia.erl
3Applying rules...
4# src/dia.erl [FAIL]
5  - no_tabs
6    - Line 33 has a tab at column 0.

In this case, elvis is detecting the usage of tabs for indentation which is something that's specified as invalid in the project's elvis.config file.

Debugging your Servers

The next group of tools assume that you already created your app, packed it up as a release, and deployed it to some server. They also assume that the app's been running for a while, but has started behaving strangely. So you need to debug it.

First, you connect to your server with a remote shell (See the link for the -remsh command). Then, you use Erlang's dbg.

Alas, dbg is not a very intuitive, simple debugging tool. It's really powerful, but there are simpler tools for the task at hand. I'll show you 2 of them below.


9. redbug

redbug is a debugging application which is quite similar to dbg, but with a more intuitive interface. It comes with eper but, to be honest, redbug is the only eper component I've ever used. With redbug, you can specify a trace pattern as a string, and it will print out a message in your console everytime a function call matching that pattern is evaluated. It looks like this:

11> redbug:start("your_module:your_private_function->return").
32> your_module:your_public_function().
4% 19:55:12 <0.1.0>({erlang,apply,4})
5% your_module:your_private_function(some, parameters)
7% 19:55:12 <0.1.0>({erlang,apply,4})
8% your_module:your_private_function/2 -> ok

I actually tend to always use the same options when starting redbug. You can check the available startup options using redbug:help(). As such, I've added this start code to my user_default (Don't do this in production, kids!!):

1redbug(What) ->
2  catch redbug:stop(),
3  timer:sleep(100),
4  redbug:start(What, [{time, 9999999}, {msgs, 9999999}, {print_msec, true}]).

Now I can easily get redbug going.:

14> redbug("your_module:your_private_function->return").
35> your_module:your_public_function().
4% 19:59:13 <0.1.0>({erlang,apply,4})
5% your_module:your_private_function(some, parameters)
7% 19:59:13 <0.1.0>({erlang,apply,4})
8% your_module:your_private_function/2 -> ok

10. recon

But tracing function calls is just one of the many things you can do to check the health of your Erlang servers. Fred Hebert recollected tons of experiences dealing with that in both a great book (Stuff Goes Bad: Erlang In Anger) and a great tool: recon.

With recon you can do many things, you can: trace function calls (just like redbug), recover the source code of your compiled modules, check app memory consumption, figure out message queue lengths, clean up binary memory, find leaks and much more. I strongly recommend you to read the book and play around with the different recon tools described in it. You'll want to know them by heart if your server starts behaving erratically.

Bonus Tracks

Now some pointers that don't really fit in the list above, but deserve to be mentioned anyway.


11. Meta Testing

If you use TDD (even if you just use common test to test your Erlang apps and you are interested in maintaining your code quality using the tools I mentioned above, you have to check katana-test. With the code below, you'll be dialyzing, xrefing, and rocking your code every time you run your tests.

8init_per_suite(Config) -> [{application, your_app} | Config].

I call that Meta Testing, and I've written two blog posts about it already. Check them out here and here.

12. The Community

No list of great Erlang things will be complete without a shout out to the great community of developers behind it. If you're going to work on Erlang or even if you're just approaching the language and you want to know what it's all about, you have to meet the erlangers.

There are a couple of ways to accomplish that:

  • The erlang-questions mailing list is the most popular and arguably the official communication medium for the community. Yes, it's a mailing list. No, it will not fill up your inbox like spam.
  • We also have an IRC room on freenode and a server on Slack where we constantly meet.
  • And you can find some of us on ErlangCentral, too.

If you're serious about Erlang development, keep an eye on those platforms and stay connected with us for updates and other opportunities!

13. Guidelines

Finally, I want to mention the Erlang Guidelines, published by Inaka on github. As I said in the mailing list when we first talked about them:

erlang_guidelines is the set of standards that we (at Inaka) enforce in our Erlang code. It will only reflect the ideas of the ~8 erlangers that were/are working at Inaka. We accept contributions because we love good and constructive debate, but we don’t expect anybody to like our guidelines :)

In other words, the guidelines on that repo don't need to be your guidelines, but you should have some guidelines of your own. Forking that repo and then adapting its contents to your ideas may be a good way to start. Others have done exactly that already. As Iñaki once explained:

Code formatting, spacing, layout and "making the code look good" is not the goal of having guidelines. Similarly, the idea is not to engage in bondage programming in which everyone is forced to do something they don't want, but to have a baseline of order and agreed-upon-terms.

The purpose of having consensus and following it is to free up energy, not to consume it in favor of poorly-defined or subjective goals such as "it looks better". Having each one one make up his/her own rules "and damn the rest" is just as antisocial as forcing everyone to adhere to strict guidelines which dictate where you put your commas or spacing. Discipline provides freedom to the group, because once you achieve consensus on a matter, the effort of dealing with other's trespasses is greater than the effort of keeping oneself from trespassing.

Issues such as line length restrictions are a means to an end: they make the code easier to change, debug, and extend to respond faster to requirement changes. To agree with any of this, one must acknowledge that programming is a social endeavor, and group behaviour applies. It may be easier to write for you, at this time, but others will have to read it. This is the sole objective of code guidelines.

Hopefully this guide cleared up some of the questions you may have had regarding Erlang-based application development and also boosted your interest in creating Erlang apps! Thank you for reading.