• Pl chevron_right

      Erlang Solutions: How to use Lua for flexible configurations in Erlang and Elixir

      news.movim.eu / PlanetJabber • 3 September 2026 • 12 minutes

    When I need to configure something in a complicated way, I find myself reviewing the embedded language that provided the server to create a flexible configuration. In Redis, you can improve the performance of requests, in Nginx, you can improve the handling of incoming requests, FreeSwitch offers alternatives for performing the same tasks using different embedded languages. Even in a software like TheGimp, you can add your own code to edit images.

    Among the embedded languages, JavaScript and Lua are the most commonly used languages. JavaScript is very well known to the Erlang community because it was integrated (as a port, it is not implemented on top of Erlang) in popular products such as CouchDB and Riak. But I think the more exciting option, raised by Erlang Co-Creator, Robert Virding, is to implement Lua on top of Erlang, which can be used as an embedded language.

    Why? Let’s take a look.

    Complex Configuration

    Usually, when tasked with fitting the definition of a behaviour we would like to configure, we would create an algorithm in a simple language such as Lua. This saves us from performing activities like:

    1. Defining the configuration to fit all of the cases.
    2. Reading and transmitting that information to be prepared for use.
    3. Writing specific code to handle that standardised information.

    This kind of implementation is used frequently. It is easy to think of examples you’re likely to come across in day-to-day life. For example, supermarket offers which have multiple dependencies, commissions for salespeople which might feature variable ranges and percentages based on the type of sale, amount of sale or tax brackets, even SMS, emails or HTTP requests could be considered examples.

    To demonstrate this, let’s look at an example of developing a load balancer. This is a simple project using cowboy, and luerl as dependencies.Dependingdependencies., and  depending on the headers and other information from the HTTP request, we can send it to the different web servers we have available and configured.

    Based on the above premise we can write the configuration as follows:

    {load_balancer, [
        {servers, [
            {odin, "1.1.1.1", [
                {in, method, [post]},
                {'>', <<"content-length">>, 10000},
                {in, <<"accept">>, [<<"json">>]}
            ]},
            {thor, "1.1.1.2", [
                {in, method, [get, post]},
                {'==', http_version, <<"2">>}
            ]},
            {balder, "1.1.1.3", [
                {in, method, [get, post]}
            ]}
        ]}
    ]}.

    As you can see, we have to define a 3-tuple system for the rules with the operation in the first element and the two operators as the following elements inside of the tuple. In addition, we are occasionally handling the second element as a header name (if it is a binary), but at other times it’s the method we use to perform the request (using the atom “method”) and other times still, the HTTP version is used to gather the information.

    The problem is that we have no closed specifications. We could add more elements or even change the meaning of them. What if we want to use logical modifiers like “and” and “or” to join the checks instead of assuming they are always using “and”? This change will add more complexity to our configuration and more complexity means more possibilities for making mistakes.

    At the moment, if the configuration is wrong or adds something that is not granted, it is up to us to trigger the corresponding error and point to where it is to make it easier to fix. As you can imagine, that is not an easy thing to do if you are handling it during runtime.

    Lua saves the day!

    It’s not unconventional to think about configuration in terms of a specific code. At this point, Lua code could be put in charge of the definition because it is based on Lua semantics.

    We only need the information for the configuration and running of the snippet to give us the desired behaviour we want to plug into the correct place. For example, the previous configuration could be written as:

    local odin = "1.1.1.1"
    local thor = "1.1.1.2"
    local balder = "1.1.1.3"
    
    local method = http.method()
    local size = tonumber(http.header("content-length")) or 0
    local accept = config.split(http.header("accept") or "", ", ")
    local httpver = http.version()
    
    if method == "post" and size > 10000 and config.member("json", accept) then
        return odin
    elseif config.member(method, {"get", "post"}) and httpver == "2" then
        return thor
    elseif config.member(method, {"get", "post"}) then
        return balder
    end


    As you can see, we are able to optimise and fix the code to suit our needs, it is shorter and clearer than the original configuration and, most importantly, we can now test and check to be sure it is compiling correctly.

    The important thing to keep in mind is that the configuration code must include the functions which are going to be needed to handle the request. In the example above, we are using functions like http_version() , http_header("...") or even split(...) and member(...) . These functions should be provided to the interpreter.

    Of course, the interpreter also has other functions available, we only need to provide the specific functions that are required for our business logic.

    In addition to improving the performance, using these functions we are also improving the security because it has not been able to access functions which are not used. You can check the file luerl_sandbox.erl where it is removing the access to the functions which use the underlying operating system.

    Where the code dwells?

    Inserting the Lua code into the configuration can be a little tricky. To avoid this, I recommend putting these scripts into the priv directory as a normal Lua file (using the extension .lua) this could even be done inside of a database if we are handling the configuration in an automated way using a key/value storage configuration such as etcd.

    The most important thing to keep in mind before running that code is to have a specific task which helps you to parse it and ensure the code is correct. One solution is to conduct a testing phase to ensure that the configuration is not breaking or negatively impacting other parts of the system.

    For example, in the previous code, we would write a couple of libraries, one called utils for the functions needed for strings and tables and another called http needed for the HTTP functions. An example would be:

    -module(luerl_lib_http).
    -export([load/1, install/1, put_request/2]).
    
    -include_lib("luerl/include/luerl.hrl").
    
    -define(REQUEST, http_request).
    
    load(St) ->
        luerl:load_module([<<"http">>], luerl_lib_http, St).
    
    install(St) ->
        luerl_heap:alloc_table(table(), St).
    
    put_request(Request, St) ->
        luerl:put_private(?REQUEST, Request, St).
    
    table() ->
      [
        {<<"method">>,  #erl_func{code = fun method/2}},
        {<<"version">>, #erl_func{code = fun version/2}},
        {<<"header">>,  #erl_func{code = fun header/2}}
      ].
    
    method(_Args, St) ->
      #{method := Method} = request(St),
      {[Method], St}.
    
    version(_Args, St) ->
      #{version := Version} = request(St),
      {[Version], St}.
    
    header([Name|_], St) when is_binary(Name) ->
      #{headers := Headers} = request(St),
      {[maps:get(Name, Headers, nil)], St};
    header(Args, St) ->
      luerl_lib:badarg_error(<<"header">>, Args, St).
    
    request(St) -> luerl:get_private(?REQUEST, St).

    defmodule LuerlLib.Http do
      require Record
    
      Record.defrecord(:erl_func, Record.extract(:erl_func, from_lib: "luerl/include/luerl.hrl"))
    
      @private_key :http_request
    
      def load(state), do: :luerl.load_module(["http"], __MODULE__, state)
    
      def install(state), do: :luerl_heap.alloc_table(exports(), state)
    
      def put_request(state, request), do: :luerl.put_private(@private_key, request, state)
    
      defp exports do
        [
          {"method", erl_func(code: &method/2)},
          {"version", erl_func(code: &version/2)},
          {"header", erl_func(code: &header/2)}
        ]
      end
    
      defp method(_args, state), do: {[request(state).method], state}
    
      defp version(_args, state), do: {[request(state).version], state}
    
      defp header([name | _], state) when is_binary(name),
        do: {[Map.get(request(state).headers, name)], state}
    
      defp header(args, state), do: :luerl_lib.badarg_error("header", args, state)
    
      defp request(state), do: :luerl.get_private(@private_key, state)
    end

    -module(luerl_lib_config).
    -export([load/1, install/1]).
    
    -include_lib("luerl/include/luerl.hrl").
    
    load(St) ->
        luerl:load_module([<<"config">>], luerl_lib_config, St).
    
    install(St) ->
        luerl_heap:alloc_table(table(), St).
    
    table() ->
      [
        {<<"split">>, #erl_func{code = fun split/2}},
        {<<"member">>, #erl_func{code = fun member/2}}
      ].
    
    member([Entry, #tref{}=Table], St) ->
      #table{a = Array} = luerl_heap:get_table(Table, St),
      Result = array:foldl(fun
        (_, V, false) when V =:= Entry -> true;
        (_, _, Acc) -> Acc
      end, false, Array),
      {[Result], St};
    member(Args, St) ->
      luerl_lib:badarg_error(<<"member">>, Args, St).
    
    split([String, Sep], St) when is_binary(String), is_binary(Sep) ->
      {Tref, St1} = luerl:encode(string:split(String, Sep, all), St),
      {[Tref], St1};
    split(Args, St) ->
      luerl_lib:badarg_error(<<"split">>, Args, St).

    defmodule LuerlLib.Config do
      require Record
    
      Record.defrecord(:erl_func, Record.extract(:erl_func, from_lib: "luerl/include/luerl.hrl"))
      Record.defrecord(:table, Record.extract(:table, from_lib: "luerl/include/luerl.hrl"))
    
      def load(state), do: :luerl.load_module(["config"], __MODULE__, state)
    
      def install(state), do: :luerl_heap.alloc_table(exports(), state)
    
      defp exports do
        [
          {"split", erl_func(code: &split/2)},
          {"member", erl_func(code: &member/2)}
        ]
      end
    
      defp split([string, separator | _], state) when is_binary(string) and is_binary(separator) do
        {table_ref, state} = :luerl.encode(String.split(string, separator), state)
        {[table_ref], state}
      end
    
      defp split(args, state), do: :luerl_lib.badarg_error("split", args, state)
    
      defp member([entry, table_ref | _], state) when Record.is_record(table_ref, :tref) do
        table(a: array) = :luerl_heap.get_table(table_ref, state)
        found = :array.sparse_foldl(fn _index, value, acc -> acc or value === entry end, false, array)
        {[found], state}
      end
    
      defp member(args, state), do: :luerl_lib.badarg_error("member", args, state)
    end


    As you can see, we are implementing the functions we need and making them available to our Lua interface under the config and http packages. To load these functions, we have to run the load function which is exported in both modules. NOTE: install is used as a callback function in load_module

    A great benefit of doing things this way is the “compile once, and ready many” approach. The config does not have to be loaded for every request. If we put it all together in our application, it would something  like like the following

    Add a build_request function and its helpers to your luerl_lib_http and LuerlLib.Http modules.

    -export([load/1, install/1, put_request/2, build_request/1]). % add export
    
    build_request(CowboyReq) ->
        #{method => string:lowercase(cowboy_req:method(CowboyReq)),
          version => version_to_binary(cowboy_req:version(CowboyReq)),
          headers => cowboy_req:headers(CowboyReq)}.
    
    version_to_binary('HTTP/1.0') -> <<"1.0">>;
    version_to_binary('HTTP/1.1') -> <<"1.1">>;
    version_to_binary('HTTP/2') -> <<"2">>.

      def build_request(cowboy_req) do
        %{
          method: cowboy_req |> :cowboy_req.method() |> String.downcase(),
          version: version_to_binary(:cowboy_req.version(cowboy_req)),
          headers: :cowboy_req.headers(cowboy_req)
        }
      end
    
      defp version_to_binary(:"HTTP/1.0"), do: "1.0"
      defp version_to_binary(:"HTTP/1.1"), do: "1.1"
      defp version_to_binary(:"HTTP/2"), do: "2"

    Add handler modules for handling the request

    -module(luerl_lib_handler).
    -export([init/2]).
    
    init(CowboyReq, {Form, St}) ->
        LuaReq = luerl_lib_http:build_request(CowboyReq),
        St1 = luerl_lib_http:put_request(LuaReq, St),
        Reply = case luerl:call_chunk(Form, St1) of
            {ok, [], _St2} ->
                cowboy_req:reply(404, #{}, <<"no matching route">>, CowboyReq);
            {ok, Rets, St2} ->
                [Backend] = luerl:decode_list(Rets, St2),
                cowboy_req:reply(200, #{}, Backend, CowboyReq);
            {lua_error, _Reason, _St2} ->
                cowboy_req:reply(500, #{}, <<"routing error">>, CowboyReq)
        end,
        {ok, Reply, {Form, St}}.

    defmodule LuerlLib.Handler do
      def init(cowboy_req, {form, state}) do
        lua_req = LuerlLib.Http.build_request(cowboy_req)
        state1 = LuerlLib.Http.put_request(state, lua_req)
    
        reply =
          case :luerl.call_chunk(form, state1) do
            {:ok, [], _state2} ->
              :cowboy_req.reply(404, %{}, "no matching route", cowboy_req)
    
            {:ok, returns, state2} ->
              [backend] = :luerl.decode_list(returns, state2)
              :cowboy_req.reply(200, %{}, backend, cowboy_req)
    
            {:lua_error, _reason, _state2} ->
              :cowboy_req.reply(500, %{}, "routing error", cowboy_req)
          end
    
        {:ok, reply, {form, state}}
      end
    end

    With all of that defined, we can now call the relevant functions from our application startup:


    -module(luerl_lib_app).
    -behaviour(application).
    -export([start/2, stop/1]).
    
    start(_StartType, _StartArgs) ->
        {ok, Form, St} = build_lua_state(),
        Dispatch = cowboy_router:compile([{'_', [{'_', luerl_lib_handler, {Form, St}}]}]),
        {ok, _} = cowboy:start_clear(luerl_lib_listener, [{port, 8080}], #{env => #{dispatch => Dispatch}}),
        luerl_lib_sup:start_link().
    
    stop(_State) ->
        ok = cowboy:stop_listener(luerl_lib_listener).
    
    build_lua_state() ->
        St0 = luerl_lib_http:load(luerl_lib_config:load(luerl_sandbox:init())),
        luerl:loadfile("priv/config.lua", St0).

    Supervisor

    -module(luerl_lib_sup).
    -behaviour(supervisor).
    -export([start_link/0, init/1]).
    
    start_link() ->
        supervisor:start_link({local, ?MODULE}, ?MODULE, []).
    
    init([]) ->
        {ok, {#{strategy => one_for_one, intensity => 1, period => 5}, []}}.

    defmodule LuerlLib.Application do
      use Application
    
      def start(_type, _args) do
        state =
          :luerl_sandbox.init()
          |> LuerlLib.Config.load()
          |> LuerlLib.Http.load()
    
        {:ok, form, state} = :luerl.loadfile(~c"priv/config.lua", state)
        dispatch = :cowboy_router.compile([{:_, [{:_, LuerlLib.Handler, {form, state}}]}])
    
        {:ok, _} =
          :cowboy.start_clear(:luerl_lib_listener, [port: 8080], %{env: %{dispatch: dispatch}})
    
        LuerlLib.Supervisor.start_link()
      end
    end

    Notice that we load the file once (with the lines below) and pass it down to the handler, it helps us obtain the forms that we need and the handler will always execute based on that initially loaded configuration.

    {ok, Form, St1} = luerl:loadfile("priv/config.lua", St0).

    {:ok, form, state} = :luerl.loadfile(String.to_charlist(path), state)

    Scaling up

    Lua scales up because it is built on top of Erlang. This means Lua is using the same processes as Erlang does, it also means we are not using ports to communicate with the Lua interpreter, we have the Lua interpreter running on Erlang.

    This makes a great difference in comparison to JavaScript because if you are handling millions of requests and all of them require a JavaScript snippet, this can cause a bottleneck very quickly if you have to limit the number of ports or requests.

    On the other hand, Lua is using native Erlang functions when it calls the functions we want to provide for the interpreter. That makes a clear improvement, saving us from performing data serialisation or transformation.

    Conclusion

    Using a language for flexible configuration gives us the possibility to create an easy interface to provide configurations, reduce the amount of code we need to write, and improve the maintenance without jeopardising the performance of the system. At the moment, you can use Lua as we have explained during the article or you can jump into PHP if you need to process text or templates on top of Erlang or Elixir.

    Alternatively you can join the community and provide other solutions which help us build better fit-for-purpose software. Get in touch if you need help building your system with one of these solutions.

    The post How to use Lua for flexible configurations in Erlang and Elixir appeared first on Erlang Solutions .

    • Pl chevron_right

      The XMPP Standards Foundation: XMPP at LinuxDays

      news.movim.eu / PlanetJabber • 31 August 2026

    Members of the community will operate an XMPP-themed stand at the upcoming LinuxDays conference in Prague – if you are around, you are welcome to come visit. Similarly, if you know of someone who might be interested in XMPP or whom you would like to introduce to it, Czech-speaking or not, please pass on the information to them.

    Two XMPP talks have also been submitted for the conference, you can support them in a vote which takes place over the first week of September.

    The conference takes place over the first weekend of October, 3rd and 4th, at the Faculty of Information Technology of the Czech Technical University and is free to attend (participants are asked to register, if possible). You can find more information at the conference’s website (in Czech).

    • Pl chevron_right

      Ignite Realtime Blog: Openfire 5.1.2 Release

      news.movim.eu / PlanetJabber • 17 August 2026 • 1 minute

    The Ignite Realtime community is pleased to announce the release of Openfire 5.1.2, a maintenance update to our open-source XMPP real-time communication server!

    This release primarily updates third-party libraries following reports of vulnerabilities in those dependencies. There’s no indication that Openfire itself was vulnerable to any of the reported issues, but as a precaution and to keep our dependencies up to date, we’ve included the updated libraries in this release.

    The full changelog has all the details, with 12 items resolved in total.

    You can obtain Openfire 5.1.2 for your platform from its download page . The SHA-256 checksums for the release artifacts are:

    bbe0e3bd7837aeb6a78a1878b3e23a53a61e99d827ec966ddc40500db416eb86  openfire-5.1.2-1.noarch.rpm
    354e14ebf80c03eee05649a43d52338a77797bb41529a4f1486c49e784119f9d  openfire_5.1.2_all.deb
    7da2582b4bc6c25640deecd9f22cdc1278c989cd39c0a8832fc15b3aeb208235  openfire_5_1_2.dmg
    b32e33bc0e5305dcac308a82447f970b59a095f1edbb425d81ac93203db2f667  openfire_5_1_2.exe
    0f45006e319fb36bf869ef9ca3a3bef930a1bd683a792883b0b0b4c9a3974680  openfire_5_1_2.tar.gz
    5e52d57fb9e20235ed31a6809c3ecd139fb6583c0b24575cab3459ad4ff0b0a1  openfire_5_1_2_x64.exe
    4f2bcdde684ed157f19df8a792db4f907a62864baa8a6c982c970dfaa43d0421  openfire_5_1_2.zip
    

    We’d love to hear from you! Please join our community forum or group chat and let us know what you think!

    For other release announcements and news follow us on Mastodon or X

    1 post - 1 participant

    Read full topic

    • Pl chevron_right

      The XMPP Standards Foundation: XMPP at FrOSCon 2026

      news.movim.eu / PlanetJabber • 12 August 2026

    From 15th to 16th August 2026, XMPP Community members will be present with an XMPP booth at FrOSCon 2026, the Free and Open Source Software Conference!

    As every year, the FrOSCon e.V. (club) organises together with the computer science department of the University of Applied Sciences Bonn-Rhein-Sieg in Sankt Augustin, Germany. The venue is free of cost and no registration is required.

    Find the program and more details on their webpage: froscon.org

    Come by and have a personal chat with XMPP Community members and XMPP developers at the XMPP (Jabber) booth!

    See you soon at FrOSCon 2026!

    • Pl chevron_right

      The XMPP Standards Foundation: XMPP stand at OmniOpenCon

      news.movim.eu / PlanetJabber • 11 August 2026

    XMPP Community members will have a stand at OmniOpenCon on the 16th-17th October 2026.

    OmniOpenCon is an annual FOSS conference held at Politehnica University of Bucharest in Bucharest, Romania. OmniOpenCon is free to attend, and is well linked by public transport making it very easy to reach from anywhere in the city.

    Currently a lot of XMPP community presence in Europe has been within Western Europe. Branching out to Eastern Europe will hopefully draw new members into the XMPP community and spread secure, open and decentralised messaging to more people!

    We are looking for volunteers if anyone is free to attend.

    Please join the OmniOpenCon MUC if you are attending the conference: omniopencon@muc.mohrie.net

    • Pl chevron_right

      The XMPP Standards Foundation: The XMPP Newsletter July 2026

      news.movim.eu / PlanetJabber • 5 August 2026 • 10 minutes

    XMPP Newsletter Banner

    XMPP Newsletter Banner

    Welcome to the XMPP Newsletter, great to have you here again! This issue covers the month of July 2026.

    The XMPP Newsletter is brought to you by the XSF Communication Team and contributors of the XMPP community.

    Just like any other product or project by the XSF, the Newsletter is the result of the voluntary work of its members and contributors. If you are happy with the services and software you may be using, please consider saying thanks or help these projects!

    Interested in contributing to the XSF Communication Team? Read more at the bottom.

    XSF Announcements

    XMPP Summit 29

    The XMPP Standards Foundation (XSF) is excited to announce the 29th XMPP Summit, the first XMPP Summit to take place fully online! The XMPP Summit will be held from Friday 4th September to Saturday 5th September 2026, both days between 13:00 - 16:00 UTC. The XSF invites everyone interested in development of XMPP technologies to attend, and discuss all things XMPP remotely!

    XSF Membership

    Being an elected member of the XMPP Standards Foundation signals a commitment to open standards and professional engagement in / with the XMPP community. Here, your membership helps position the XSF as a healthy organization, which in itself is valuable. It also grants voting rights on technical and administrative matters within the XSF. The application is a light-weight and free of cost process and you can use your membership to get more involved more easily, too. If you are interested in joining the XMPP Standards Foundation as a member, please apply to our 3rd quarterly call for members admission before August 16th, 2026 00:00UTC.

    XMPP Events

    • XMPP at FrOSCon 2026: This year’s edition of FrOSCon will take place during August 15th & 16th, at the Hochschule Bonn-Rhein-Sieg in Sankt Augustin, Germany, and the XMPP community will be part of it. Make sure to come by the XMPP booth and say hi!. We promise there will be stickers. ;)
    • The di.day takes place every first Sunday of the month where the XMPP community also promotes their solutions! di.day is a mainly German initiative to help people switch to open-source and privacy-friendly solutions.

    Videos and Talks

    XMPP Articles

    XMPP Software News

    XMPP Clients and Applications

    • aTalk has released version 6.4.0 of its encrypted instant messaging with video call and GPS features for Android. This release brings a lot of improvements, quite a few fixes and some really heavy work ‘under the hood’. Please refer to the release notes for all the details.
    • Cheogram has released version 2.19.0-6 of its XMPP client for Android. This release brings jump to searched message, fixes enabling the dialer integration on Android 17, improves the experimental export, won’t linkify invalid URLs, changes default ‘big’ media size to 1MB for new installs and fixes many crashes. Make sure to check out the changelog for all the details.
    • Fluux Messenger, has released versions 0.17.0, 0.17.1 and 0.17.2 of its modern, cross-platform XMPP client for communities and organizations. These releases are massive and improve almost every single aspect of the client with follow-up releases to fix what real-world use exposed. They bring an awful lot of additions, changes and fixes. So many that we could never even attempt to summarize them here. Please, go straight to the full changelog for all the details!
    A quick tour — messaging, group rooms, end-to-end encryption, and theming.

    A quick tour — messaging, group rooms, end-to-end encryption, and theming.

    • Gajim has released version 2.5.0 of its free and fully featured chat app for XMPP. This release comes with many improvements for Gajim’s integrated audio player, and it comes with some bug fixes. Thank you for all your contributions!
    Gajim: An updated look for Gajim’s audio player

    Gajim: An updated look for Gajim’s audio player

    • Kaidan has released version 0.16.0 of its user-friendly and modern chat app for XMPP. This release adds a media viewer, improves the emoji picker, and got a button to try another XMPP provider during registration. In addition, it adds support to record voice messages via long press. As always, it includes several smaller improvements and fixes. Most of the work being funded by NLnet via NGI Zero Entrust and NGI Zero Commons Fund with public money provided by the European Commission. You can find a detailed list of new features, bugfixes and notes in their respective release announcements, or the changelog.
    Kaidan 0.16.0: Media viewer.

    Kaidan 0.16.0: Media viewer.

    Kaidan 0.16.0: Message recording.

    Kaidan 0.16.0: Message recording.

    XMPP Servers

    • The Ignite Realtime community is pleased to announce the release of Openfire 5.1.1, a maintenance update to the open-source XMPP real-time communication server. This release comes with a focus on PubSub correctness and connection handling. Highlights: fixed excessive memory use from a bloated pubsub subscription table, resolved an IQBind thread-starvation bug, corrected IP encoding in certificate SANs, and resolved 24 issues in total. Make sure to read the full changelog for all the details!
    • ProcessOne has released ejabberd 26.07. This security release of ejabberd includes several security fixes, some improvements and other minor bugfixes. It is strongly encouraged that you update ejabberd as soon as possible. Make sure to read the changelog for all the details and a complete list of fixes and improvements on this release.

    XMPP Libraries & Tools

    Extensions and specifications

    The XMPP Standards Foundation develops extensions to XMPP in its XEP series in addition to XMPP RFCs. Developers and other standards experts from around the world collaborate on these extensions, developing new specifications for emerging practices, and refining existing ways of doing things. Proposed by anybody, the particularly successful ones end up as Final or Active - depending on their type - while others are carefully archived as Deferred. This life cycle is described in XEP-0001, which contains the formal and canonical definitions for the types, states, and processes. Read more about the standards process. Communication around Standards and Extensions happens in the Standards Mailing List (online archive).

    Proposed

    The XEP development process starts by writing up an idea and submitting it to the XMPP Editor. Within two weeks, the Council decides whether to accept this proposal as an Experimental XEP.

    • No proposed XEPs this month.

    New

    • No new XEPs this month.

    Deferred

    If an experimental XEP is not updated for more than twelve months, it will be moved off Experimental to Deferred. If there is another update, it will put the XEP back onto Experimental.

    • No XEPs deferred this month.

    Updated

    • No XEPs updated this month.

    Last Call

    Last calls are issued once everyone seems satisfied with the current XEP status. After the Council decides whether the XEP seems ready, the XMPP Editor issues a Last Call for comments. The feedback gathered during the Last Call can help improve the XEP before returning it to the Council for advancement to Stable.

    • Last Call for comments on XEP-0424 (Message Retraction)
      • This Last Call begins on 2026-07-03 and shall end at the close of business on 2026-07-20.

    Stable

    • No stable XEPs this month.

    Deprecated

    • No XEPs deprecated this month.

    Rejected

    • No XEPs rejected this month.

    Spread the news

    Please share the news on other networks:

    Subscribe to the monthly XMPP newsletter
    Subscribe

    Also check out our RSS Feed!

    Looking for job offers or want to hire a professional consultant for your XMPP project? Visit our XMPP job board.

    Newsletter Contributors & Translations

    This is a community effort, and we would like to thank translators for their contributions. Volunteers and more languages are welcome! Translations of the XMPP Newsletter will be released here (with some delay):

    • Contributors:

      • To this issue: cal0pteryx, emus, Gonzalo Raúl Nemmi, Ludovic Bocquet, poVoq, XSF iTeam
    • Translations:

      • French: Adrien Bourmault (neox), alkino, anubis, Arkem, Benoît Sibaud, mathieui, nyco, Pierre Jarillon, Ppjet6, seveso, Ysabeau
      • Italian: Mario Sabatino, Roberto Resoli

    Help us to build the newsletter

    This XMPP Newsletter is produced collaboratively by the XMPP community. Each month’s newsletter issue is drafted in this simple pad. At the end of each month, the pad’s content is merged into the XSF GitHub repository. We are always happy to welcome contributors. Do not hesitate to join the discussion in our XSF Communications Team group chat (MUC) and thereby help us sustain this as a community effort. You have a project and want to spread the news? Please consider sharing your news or events here, and promote it to a large audience.

    Tasks we do on a regular basis:

    • gathering news in the XMPP universe
    • short summaries of news and events
    • summary of the monthly communication on extensions (XEPs)
    • review of the newsletter draft
    • preparation of media images
    • translations
    • communication via media accounts

    Unsubscribe from the XMPP Newsletter

    For this newsletter either log in here and unsubscribe or simply send an email to newsletter-leave@xmpp.org. (If you have not previously logged in, you may need to set up an account with the appropriate email address.)

    License

    This newsletter is published under CC BY-SA license.

    • Pl chevron_right

      ProcessOne: ejabberd 26.07

      news.movim.eu / PlanetJabber • 30 July 2026 • 7 minutes

    ejabberd 26.07

    Contents:

    Changes in SQL schema

    If you upgrade ejabberd from a previous release to 26.07 , there are no changes in SQL schemas, but there is one for ejabberd Business Edition (see below ).

    Security fixes

    This release contains fixes for those security issues:

    • It&aposs possible to craft PLAIN auth request and authenticate as one user, but then open session for different one.
    • mod_caps persistent cache can be poisoned by using legacy version requests.This cache was only used to determine list of nodes that should trigger notifications in PubSub presence-based delivery.
    • SQL injection in mod_pubsub handling of paging requests.
    • Possible atom exhaustion that can be triggered by issuing REST requests to mod_http_api .
    • It was possible to make ejabberd send redirect response for OAuth requests to unvetted url. This required enabling ejabberd to act as OAuth provider (by adding request handler for ejabberd_oauth in http listener). As part of this fix we changed oauth_client_id_check default value to db .
    • using ejabberd as OAuth provider will be only allowed by clients
      that were previously registered with oauth_add_client_password or oauth_add_client_implicit commands.
    • Tokens generated by mod_bosh , captcha , mod_auth_fast , mod_http_upload and mod_invites used not cryptographically strong random number generators.
    • Files server by mod_http_upload didn&apost have XSS prevention headers.
    • Issues in authentication of SIP requests.
    • Request to web_admin were lacking CSRF protection.
    • It was possible to skip captcha verification in mod_register_web.
    • mod_conversejs allowed putting unescaped value from url in page content.

    mod_invites: New pages to create invites and WebAdmin

    mod_invites now includes a startpage where regular users can use their account credentials to generate new account creation invites.

    This is useful for people using XMPP clients that do no support that feature. The URL of that page is the root of mod_invites; you can find a link to that page in the bottom of WebAdmin left menu.

    There are also new WebAdmin pages to view the existing invites, generate new invites, expire, delete ... Go and take a look at WebAdmin > "Virtual Hosts" > one of your hosts > "Invites"

    mod_conversejs: Support ConverseJS 14

    ConverseJS published version 14.0.0 recently, and it requires some changes in the web server. In this sense, mod_conversejs is updated to support ConverseJS 14, and also got other minor cosmetic improvements.

    Erlang/OTP 27.0 as a soft minimum

    Are you compiling ejabberd with Erlang/OTP 25 or 26? Then please try to update to Erlang/OTP 27, 28, or 29. For example, the ejabberd installers are compiled with Erlang/OTP 28.5.0.4.

    ejabberd supports compilation with Erlang/OTP 25 and 26, and those versions are still tested in runtime.yml and weekly.yml , but those Erlang/OTP versions are not actively maintained anymore by Erlang/OTP.

    Following the erlang security recommendation to Use Actively Maintained Versions of Erlang/OTP , from now ejabberd softly rejects compilation with Erlang/OTP lower than 27.

    What does softly mean? If you really want to compile ejabberd with Erlang/OTP lower than 27 at your own risk, you can bypass that soft requirement by defining this option (Erlang/OTP 25.0 included Erlang Run-Time System 13.0 , and that is the number to provide in that option):

    ./configure --with-min-erlang=13.0
    

    Rebar/Rebar3: Update binaries to work with Erlang/OTP 26-29

    ejabberd source code includes Rebar and Rebar3 binaries, in case you don&apost have installed in your system. But those programs only support four Erlang releases (26 up to 29).

    If you want to compile ejabberd with Erlang 25, then you need to grab a compatible Rebar3 (or Rebar) binary: either install one from your operating system, or you can download the old binaries included with ejabberd 26.04 (those still supported Erlang 25):

    https://github.com/processone/ejabberd/raw/26.04/rebar
    https://github.com/processone/ejabberd/raw/26.04/rebar3
    

    ChangeLog

    Security fixes

    This release contains fixes for those issues:

    • It&aposs possible to craft PLAIN auth request and authenticate as one user, but then open session for different one.
    • mod_caps persistent cache can be poisoned by using legacy version requests.
      This cache was only used to determine list of nodes that should trigger notifications in PubSub presence-based delivery.
    • SQL injection in mod_pubsub handling of paging requests.
    • Possible atom exhaustion that can be triggered by issuing REST requests to mod_http_api
    • It was possible to make ejabberd send redirect response for OAuth requests to unvetted url. This required enabling ejabberd to act as OAuth provider (by adding request handler for ejabberd_oauth in http listener). As part of this fix we changed oauth_client_id_check default value to db
    • using ejabberd as OAuth provider will be only allowed by clients
      that were previously registered with oauth_add_client_password or oauth_add_client_implicit commands.
    • Tokens generated by mod_bosh, captcha, mod_auth_fast, mod_http_upload and mod_invites used not cryptographically strong random number generators.
    • Files server by mod_http_upload didn&apost have XSS prevention headers.
    • Issues in authentication of SIP requests.
    • Request to web_admin were lacking CSRF protection.
    • It was possible to skip captcha verification in mod_register_web.
    • mod_conversejs allowed putting unescaped value from url in page content.

    Core

    • Fixes delete_old_messages_batch command when used on pgsql
    • Adds export_db_ext which allows exporting db content to json files
    • Use constant time functions when doing password checks
    • Optimize room_unused_* commands when room hibernation is configured
    • We no longer add flag requesting client certificate for tls connections
      where certificate authentication is not enabled

    Modules

    • mod_auth_fast : Fixes exception for session that didn&apost set user agent
    • mod_invites : Add page for creating invites.
    • mod_invites : Fix generation of CSRF tokens.
    • mod_invites : Update to changes in latest XEP-0401
    • mod_http_upload : Attach custom headers from config when serving files.

    Full Changelog

    https://github.com/processone/ejabberd/compare/26.04...26.07

    Acknowledgments

    We would like to thank for the security reports provided by:

    the contributions to the source code by:

    and the translation by:

    And also to all the people contributing in the ejabberd chatroom, issue tracker...

    Improvements in ejabberd Business Edition

    Customers of the ejabberd Business Edition , in addition to all those bugfixes, also get the following changes:

    Changes in SQL schema

    This release modifies the push_customizations table in the SQL database schemas to support the new push notifications mute option (see below ). This task is performed automatically by ejabberd by default.

    However, if your configuration file has disabled update_sql_schema toplevel option, you must perform the SQL schema update manually yourself. Those instructions are valid for MySQL and PostgreSQL, both default and new schemas:

    MySQL

    ALTER TABLE push_customizations MODIFY COLUMN mute smallint;
    

    PostgreSQL

    ALTER TABLE push_customizations ALTER COLUMN mute TYPE smallint USING mute::int;
    

    Push notifications

    • Add a push customization option to allow mentioned users (as described in XEP-0513 ) to be notified even in muted conversation.
    • Make proxy connections in applepushv3 always use http connect
    • Report push gate tokens in the results of the user_push_state command
    • Implement push errors reporting in the results of the user_push_state command.
    • Make mod_webpush reject tokens that don&apost have correct format.
    • Fix mod_webpush key parser.
    • Add ability to retrieve user nodes from push_gate.
    • Handle wildcard certificates now used by Google FCM service.

    Commands

    Add a new local_health_status command to return information about local node state.

    Client certificate authentication

    • Add option require_xmpp_addr that forces client certs to have XmppAddr .
    • New recognize_email_addr option for mod_crl / mod_ocsp to add emailAddress from cert subject to be used beside xmppAddr for matching against provided user id.

    p1db backend

    • Make mam_p1db serialization properly serialize MUC archives.
    • Fix JID encoding in roster_p1db serialization.
    • Fix decoding of fast_tokens in p1db backend

    Clustering

    Make join_cluster robust to race conditions on leave_cluster .

    Docker

    sqlite backend has been fixed in Docker image.

    ejabberd 26.07 download & feedback

    As usual, the release is tagged in the Git source code repository on GitHub .

    The source package and installers are available in ejabberd Downloads page. To check the *.asc signature files, see How to verify ProcessOne downloads integrity .

    For convenience, there are alternative download locations like the ejabberd DEB/RPM Packages Repository and the GitHub Release / Tags .

    The ecs container image is available in docker.io/ejabberd/ecs and ghcr.io/processone/ecs . The alternative ejabberd container image is available in ghcr.io/processone/ejabberd .

    If you consider that you&aposve found a bug, please search or fill a bug report on GitHub Issues .

    • Pl chevron_right

      The XMPP Standards Foundation: XMPP at FOSSY 2026

      news.movim.eu / PlanetJabber • 23 July 2026 • 1 minute

    This year’s edition of FOSSY, the fourth Free and Open Source Software Yearly conference, will take place during the month of August, from Thursday 6th to Sunday 9th 2026 at the University of British Columbia, Vancouver, Canada. And as always, there will be an XMPP Track.

    As announced by the organization in the official Conference Schedule, the XMPP track wil be composed of seven presentations.

    Three presentations will take place during Saturday, August 8th at MCLD 3038:

    Presentation Speaker Time
    Encrypted messaging interoperability with hybrid bridges in XMPP Marvin W. 2:00 PM to 2:45 PM PDT.
    Beyond Chat: Delivering mini “apps” with great UI Stephen Paul Weber 3:00 PM to 3:45 PM PDT.
    “Beautiful XMPP Testing” revisited – How to Overcome the Mind-Body Duality by Staring at XML Phillip Davis 4:30 PM to 5:15 PM PDT.

    Followed by another four presentations during Sunday, August 9th at MCLD 3038:

    Presentation Speaker Time
    When Data Gets Heavy: Voice and Video for Large Groups Christopher Vollick 10:45 AM to 11:30 AM PDT.
    UnifiedPush - Push notifications. Decentralized and Open Source Daniel Gultsch 11:45 AM to 12:30 PM PDT.
    Adventures in Onboarding: Episode V - The Server Goes Down Gideon Mayhak 2:00 PM to 2:45 PM PDT.
    Snikket: Behind the scenes of our on-demand XMPP server hosting Matthew Wild 3:00 PM to 3:45 PM PDT.

    The FOSSY organizers kindly invite you to join the event and will be pleased to see you there. Don’t miss it out!

    • Pl chevron_right

      ProcessOne: Fluux Messenger 0.17.2: gapless history, faster transfers, and emoji autocomplete

      news.movim.eu / PlanetJabber • 21 July 2026 • 3 minutes

    History without gaps

    Fluux Messenger 0.17.2: gapless history, faster transfers, and emoji autocomplete


    The largest piece of work in this release is the kind you only notice when it is absent.

    • Interrupted archive syncs now heal. If a sync of the message archive (XEP-0313) was cut short, a conversation could be left with a silent gap: messages that were never fetched, and nothing on screen to say anything was missing. Interruptions are now detected and repaired from both directions.
    • History no longer depends on your read position. Opening a conversation you had already read elsewhere could show an empty history on a new or freshly cleared device. The archive now downloads independently of how far you had read.
    • A conversation read on another device opens at its live edge , instead of restoring a saved scroll position that multi-device sync had already made obsolete.
    • The new-messages divider and the unread badge on the scroll-to-bottom button follow your read position, consistent with the sidebar and with read-marker sync (XEP-0490).

    These are the paths we found and could reproduce. Archive sync has a long tail, and how it behaves depends a good deal on which server you are talking to. If you still see a conversation with history missing, we would love to hear about it.

    Faster file transfers

    • Desktop uploads and downloads are handled by native code. Transfers of large files are much faster and more reliable.
    • Encrypted attachments decrypt on download. An end-to-end-encrypted attachment used to be decrypted only for the inline preview, so saving it to disk gave you something unreadable. Files of every type now arrive readable.

    Emoji autocomplete & polish

    • Type a colon and a keyword to complete emoji inline. Arrow keys move through the matches, Enter or Tab inserts.
    • The send button answers with a press and a glow pulse when a message goes out.

    On the web

    • An unread badge on the app icon for the installed PWA.
    • Media is cached by the service worker , so images are not re-downloaded on every visit.
    • Repeated messages from one sender coalesce into a single "N new messages" notification instead of a stack.
    • An update that was downloaded but parked is now applied at launch, instead of the installed app trailing the deployed build indefinitely.

    Reliability and platform fixes

    Twenty-three fixes in this release. The ones most likely to have affected you:

    • Notifications behave. Clicking a reaction notification always jumps to the reacted message, reopening a conversation no longer re-posts a notification for something you have already seen, and an encrypted message with no readable content stays silent until it is decrypted instead of posting a blank notification and playing a sound.
    • The sidebar holds still. A delayed message, whether an offline replay or a catch-up copy older than what you already have, no longer drags the conversation preview back to older text. The sidebar also keeps its scroll position while conversations reorder during catch-up.
    • Authentication. A wrong saved password no longer triggers an endless keychain retry loop, and connecting to a server whose domain contains non-ASCII characters now works.
    • Key backup. The backup passphrase is used exactly as it is displayed, so backups restore in other XMPP clients. Older backups still open, and are healed to the portable format on restore.
    • Group chats. The public room directory no longer lists duplicate rooms or reaches past your server&aposs own directory, and a room notification banner no longer reappears when the room is reopened.

    Smaller ones round out the release: the quoted-message and reply cards stay visually distinct when a message is selected, your own message group re-fits its width once an image inside it finishes loading, the typing indicator is centered between the last message and the composer, and the macOS traffic-light buttons stay centered in the app bar.

    Get it

    Fluux Messenger 0.17.2 is available for macOS, Windows, and Linux, or directly in your browser.

    If you upgrade and something feels off, tell us. Bug reports and feature requests both go to GitHub Issues .