Skip to content

# ejabberd for Elixir Developers

improved in 21.07

## Building ejabberd with Mix

You can build ejabberd with Elixir `mix` tool. This allows ejabberd to use Elixir libraries and ejabberd modules written in Elixir.

Please note: Elixir 1.10.3 or higher is required to build a release. Also, if using Erlang/OTP 24, then Elixir 1.11.4 or higher is required.

1. Make sure you have the requirements installed. On MacOS you need to use Homebrew and set up your environment.

2. Clone ejabberd project from Github:

       git clone https://github.com/processone/ejabberd.git
       cd ejabberd

3. Compile ejabberd:

       ./autogen.sh
       ./configure --with-rebar=mix
       make

4. Build a development release:

       make dev

5. There are many ways to start ejabberd, using the `ejabberdctl` or `ejabberd` scripts:

       _build/prod/rel/ejabberd/bin/ejabberdctl iexlive
       _build/prod/rel/ejabberd/bin/ejabberdctl live
       _build/prod/rel/ejabberd/bin/ejabberd start_iex

6. You should see that ejabberd is properly started:

       Erlang/OTP 23 [erts-11.1.8] [source] [64-bit] [smp:2:2] [ds:2:2:10] [async-threads:1]

       2021-08-03 13:37:36.561603+02:00 [info] Loading configuration from /home/bernar/e/git/ejabberd/_build/dev/rel/ejabberd/etc/ejabberd/ejabberd.yml
       2021-08-03 13:37:37.541688+02:00 [info] Configuration loaded successfully
       ...
       2021-08-03 13:37:40.201590+02:00 [info] ejabberd 21.7.9 is started in the node ejabberd@atenea in 3.86s
       2021-08-03 13:37:40.203678+02:00 [info] Start accepting TCP connections at [::]:5222 for ejabberd_c2s

       Interactive Elixir (1.10.3) - press Ctrl+C to exit (type h() ENTER for help)
       iex(ejabberd@localhost)1>

7. Now that ejabberd starts correctly, adapt to your needs the default ejabberd configuration file located at `_build/dev/rel/ejabberd/etc/ejabberd/ejabberd.yml` For example, enable this example Elixir ejabberd module:

       modules:
         'Ejabberd.Module.Example': {}
         mod_adhoc: {}

## Embed ejabberd in an elixir app

ejabberd is available as an Hex.pm application: ejabberd on hex.pm.

This means you can build a customized XMPP messaging platform with Elixir on top of ejabberd by leveraging ejabberd code base in your app and providing only your custom modules. This makes the management of your ejabberd plugins easier and cleaner.

To create your own application depending on ejabberd, you can go through the following steps:

1. Create a new Elixir app using `mix`:

       mix new myapp
       cd myapp

2. Add ejabberd package as a dependency in your `mix.exs` file:

         defp deps do
           [
            {:ejabberd, "~> 24.6"}
           ]
         end
       end

3. Get the dependencies and compile them:

       mix deps.get
       mix compile

4. Setup runtime options and ejabberd configuration file:

       mkdir config
       cp deps/ejabberd/config/runtime.exs config/runtime.exs
       mkdir conf
       cp deps/ejabberd/ejabberd.yml.example conf/ejabberd.yml

5. Start your app, ejabberd will be started as a dependency:

       iex -S mix # similar to: ejabberdctl iexlive
       mix run --no-halt # similar to: ejabberdctl foreground

6. You should see that ejabberd is properly started:

       $ iex -S mix
       2024-07-15 13:33:12.087 [info] Loading configuration from conf/ejabberd.yml
       2024-07-15 13:33:12.301 [info] Configuration loaded successfully
       ...
       2024-07-15 13:33:12.816 [info] ejabberd 24.6.0 is started in the node :nonode@nohost in 0.75s
       2024-07-15 13:33:12.842 [info] Start accepting TCP connections at [::]:5222 for :ejabberd_c2s
       Erlang/OTP 26 [erts-14.2.5] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [jit:ns]

       Interactive Elixir (1.16.3) - press Ctrl+C to exit (type h() ENTER for help)
       iex(1)>

7. Register an account from Elixir console:

       :ejabberd_auth.try_register("test", "localhost", "passw0rd")

8. You are all set, you can now connect with an XMPP client! Notice that the default configuration doesn't have certificates or encryption.

## Call elixir code in erlang code

It's possible to use Elixir libraries in an Erlang module, both the ones included in Elixir, or any other you add as a dependency.

This simple example invokes Elixir's String.duplicate/2 function as shown in one of its documentation examples, and uses the result in the ejabberd vCard nickname field:

    --- a/src/mod_vcard.erl
    +++ b/src/mod_vcard.erl
    @@ -209,6 +209,7 @@ process_local_iq(#iq{type = get, to = To, lang = Lang} = IQ) ->
         VCard = case mod_vcard_opt:vcard(ServerHost) of
       undefined ->
           #vcard_temp{fn = <<"ejabberd">>,
    +    nickname = 'Elixir.String':duplicate(<<"abc">>, 2),
         url = ejabberd_config:get_uri(),
         desc = misc:get_descr(Lang, ?T("Erlang XMPP Server")),
         bday = <<"2002-11-16">>};

Notice that the elixir code:

    String.duplicate("abc", 2)

is written in erlang as:

    'Elixir.String':duplicate(<<"abc">>, 2),

Check Erlang/Elixir Syntax: A Crash Course for details.

## Use elixir library in erlang code

This example demonstrates how to add an elixir library as a dependency in ejabberd, and use it in an ejabberd module written in erlang.

It will use QRCodeEx elixir library to build a QR code of ejabberd's URI and return it as the server vCard photo.

First add the dependency to `mix.exs`:

    --- a/mix.exs
    +++ b/mix.exs
    @@ -46,7 +46,7 @@ defmodule Ejabberd.MixProject do
                         :p1_utils, :stringprep, :yconf],
          included_applications: [:mnesia, :os_mon,
                                  :cache_tab, :eimp, :mqtree, :p1_acme,
    -                             :p1_oauth2, :pkix, :xmpp]
    +                             :p1_oauth2, :pkix, :xmpp, :qrcode_ex]
          ++ cond_apps()]
       end

    @@ -113,6 +113,7 @@ defmodule Ejabberd.MixProject do
          {:p1_oauth2, "~> 0.6"},
          {:p1_utils, "~> 1.0"},
          {:pkix, "~> 1.0"},
    +     {:qrcode_ex, "~> 0.1.1"},
          {:stringprep, ">= 1.0.26"},
          {:xmpp, "~> 1.5"},
          {:yconf, "~> 1.0"}]

Then call QRCodeEx.encode/2, QRCodeEx.png/2, and provide the result as the photo in the server vcard:

    --- a/src/mod_vcard.erl
    +++ b/src/mod_vcard.erl
    @@ -206,9 +206,13 @@ process_local_iq(#iq{type = set, lang = Lang} = IQ) ->
         xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
     process_local_iq(#iq{type = get, to = To, lang = Lang} = IQ) ->
         ServerHost = ejabberd_router:host_of_route(To#jid.lserver),
    +    PhotoEncoded = 'Elixir.QRCodeEx':encode(ejabberd_config:get_uri()),
    +    PhotoBin = 'Elixir.QRCodeEx':png(PhotoEncoded, [{color, <<17, 120, 0>>}]),
    +    PhotoEl = #vcard_photo{type = <<"image/png">>, binval = PhotoBin},
         VCard = case mod_vcard_opt:vcard(ServerHost) of
       undefined ->
           #vcard_temp{fn = <<"ejabberd">>,
    +    photo = PhotoEl,
         url = ejabberd_config:get_uri(),
         desc = misc:get_descr(Lang, ?T("Erlang XMPP Server")),
         bday = <<"2002-11-16">>};

## Write ejabberd module in elixir

If you plan to write an ejabberd module that heavily depends on Elixir dependencies, you may want to write it in elixir from scratch.

The Elixir source code is placed in the ejabberd's lib/ path. Any elixir module placed in `lib/` will be compiled by Mix, installed with all the other erlang modules, and available for you to use.

As you can see, there's a file named mod_example.ex which defines an ejabberd module written in elixir. To enable `Ejabberd.Module.Example`, add it to `ejabberd.yml` like this:

    modules:
      'Ejabberd.Module.Example': {}

Let's write a new ejabberd module in elixir, add it to ejabberd's source code, compile and install it. This example module requires the QRCodeEx Elixir library, and adds a simple web page that generates QR code of any given JID.

1. Copy the mod_qrcode.ex source code to ejabberd's `lib/` path:

       lib/mod_qrcode.ex

2. Recompile and reinstall ejabberd.

3. Enable the module in `ejabberd.yml`:

       listen:
         -
           port: 5280
           request_handlers:
             /qrcode: 'Elixir.ModQrcode'

       modules:
         'Elixir.ModQrcode': {}

4. When restarting ejabberd, it will show in the logs:

       2022-07-06 13:14:35.363081+02:00 [info] Starting ejabberd module Qrcode

5. Now the ejabberd internal web server provides QR codes of any given JID. Try visiting an URL like `http://localhost:5280/qrcode/anyusername/somedomain/`

## Elixir module in ejabberd-contrib

Using ejabberd-contrib it's possible to install additional ejabberd modules without compiling ejabberd, or requiring ejabberd source code. This is useful if you install ejabberd using binary installers or a container image.

And it's possible to write a custom module and add your module to an existing ejabberd installation...

Let's write a new ejabberd module in elixir, compile and install in an existing ejabberd deployment without requiring its source code. This example module adds a simple section listing PIDs in the users page in ejabberd WebAdmin.

1. First, create this path

       $HOME/.ejabberd-modules/sources/mod_webadmin_pid/lib/

2. and copy the mod_webadmin_pid.ex source code to:

       $HOME/.ejabberd-modules/sources/mod_webadmin_pid/lib/mod_webadmin_pid.ex

3. Create a specification file in YAML format as `mod_webadmin_pid.spec` (see examples from ejabberd-contrib). So, create the file

       $HOME/.ejabberd-modules/sources/mod_webadmin_pid/mod_webadmin_pid.spec

   with this content:

       summary: "Display PIDs in User page in Web Admin"

4. From that point you should see it as available module:

       ejabberdctl modules_available
       mod_webadmin_pid Display PIDs in User page in Web Admin

5. Now you can compile and install that module:

       ejabberdctl module_install mod_webadmin_pid

6. Enable the module in `ejabberd.yml`:

       modules:
         'Elixir.ModWebAdminPid': {}

7. When restarting ejabberd, it will show in the logs:

       2022-07-06 13:14:35.363081+02:00 [info] Starting ejabberd module WebAdminPid

8. Finally, go to ejabberd WebAdmin -> Virtual Hosts -> your vhost -> Users -> some online user -> and there will be a new section "PIDs".

## Record definition

To use an erlang record defined in ejabberd's header file, use Elixir's Record to extract the fields and define an Elixir record with its usage macros.

For example, add this to the beginning of mod_example.ex:

    require Record

    Record.defrecord(:presence,
      Record.extract(:presence, from_lib: "xmpp/include/xmpp.hrl"))

Later you can use those macros, named like your record, see the examples.

In our example, let's improve the `on_presence` function and use the `presence` macros to get the `to` field:

    def on_presence(_user, _server, _resource, packet) do
      to_jid = presence(packet, :to)
      to_str = :jid.to_string(to_jid)
      info('Received presence for #{to_str}:~n~p', [packet])
      :none
    end

## `mod_qrcode.ex`

Example ejabberd module written in elixir:

mod_qrcode.ex

    defmodule ModQrcode do
      use Ejabberd.Module

      def start(host, _opts) do
        info('Starting ejabberd module Qrcode')
        :ok
      end

      def stop(host) do
        info('Stopping ejabberd module Qrcode')
        :ok
      end

      def process([username, hostname] = _path, _query) do
        uri = <<"xmpp:", username::binary, "@", hostname::binary>>
        qr = QRCodeEx.svg(QRCodeEx.encode(uri), [{:color, "#3fb0d2"}])
        qxmlel = :fxml_stream.parse_element(qr)
        {200,
         [{<<"Server">>, <<"ejabberd">>},
          {<<"Content-Type">>, <<"image/svg+xml">>}],
         :ejabberd_web.make_xhtml([], [qxmlel])}
      end

      def process(path, _query) do
        info('Received HTTP query with path: ~p', [path])
        {404, [], "Not Found"}
      end

      def depends(_host, _opts) do
        []
      end

      def mod_options(_host) do
        []
      end

      def mod_doc() do
        %{:desc => 'This is just a demonstration.'}
      end

    end

## `mod_webadmin_pid.ex`

Example ejabberd module written in elixir:

mod_webadmin_pid.ex

    defmodule ModWebAdminPid do
      use Ejabberd.Module

      require Record

      Record.defrecord(:xmlel,
        Record.extract(:xmlel, from_lib: "xmpp/include/xmpp.hrl"))

      Record.defrecord(:request,
        Record.extract(:request, from: "include/ejabberd_http.hrl"))

      ##====================================================================
      ## gen_mod callbacks
      ##====================================================================

      def start(host, _opts) do
        info('Starting ejabberd module WebAdminPid')
        :ejabberd_hooks.add(:webadmin_user, host, __MODULE__, :webadmin_user, 60)
        :ejabberd_hooks.add(:webadmin_page_host, host, __MODULE__, :webadmin_page, 60)
        :ok
      end

      def stop(host) do
        info('Stopping ejabberd module WebAdminPid')
        :ejabberd_hooks.delete(:webadmin_user, host, __MODULE__, :webadmin_user, 60)
        :ejabberd_hooks.delete(:webadmin_page_host, host, __MODULE__, :webadmin_page, 60)
        :ok
      end

      def depends(_host, _opts) do
        []
      end

      def mod_options(_host) do
        []
      end

      def mod_doc() do
        %{:desc => 'This is just a demonstration.'}
      end

      ##====================================================================
      ## Web Admin
      ##====================================================================

      def webadmin_user(acc, user, server, _lang) do
        resources = :ejabberd_sm.get_user_resources(user, server)

        pids_elements = Enum.map(resources,
          fn resource ->
            pid = :ejabberd_sm.get_session_pid(user, server, resource)
            pid_string = :erlang.pid_to_list(pid)
            xmlel(name: "a", attrs: [{"href", "pid/#{pid_string}"}], children: [xmlcdata: pid_string])
          end)

        pids_separated = Enum.intersperse(pids_elements, {:xmlcdata, ", "})

        new_element = xmlel(name: "h3", children: [xmlcdata: "PIDs:"])

        acc ++ [new_element] ++ pids_separated
      end

      def webadmin_page(_acc, host, request(path: ["user", user, "pid", pid])) do
        res = webadmin_pid(user, host, pid)
        {:stop, res}
      end

      def webadmin_page(acc, _host, _request) do
        acc
      end

      def webadmin_pid(user, host, pid_string) do
        us = :jid.to_string(:jid.make(user, host))
        page_title = 'Pid #{pid_string} of #{us}'

        pid = :erlang.list_to_pid(String.to_charlist(pid_string))
        pid_info = Process.info(pid)
        pid_info_string = :io_lib.format("~p", [pid_info])

        [xmlel(name: "h1", children: [xmlcdata: page_title]),
         xmlel(name: "pre", children: [xmlcdata: pid_info_string])]
      end

    end