Develop EMQX Plugins 
This page walks you through the process of developing custom EMQX plugins using the EMQX plugin template.
Prerequisites 
Before you begin, make sure you have the following:
- Knowledge of EMQX hooks.
- A working build environment (e.g., build_essential), includingmake.
- rebar3.
- Erlang/OTP of the same major version as the EMQX release you wish to target. For more information, see the org.opencontainers.image.otp.versionattribute in the Docker or refer to the.tool-versionsfile for the used version (e.g., https://github.com/emqx/emqx/blob/e5.9.0-beta.4/.tool-versions). It's recommended to use ASDF to manage Erlang/OTP versions. Alternatively, you can pull the emqx-builder images by running this command.
Install the Plugin Template 
EMQX provides an emqx-plugin-template to simplify the creation of custom EMQX plugins. To create a new plugin, you should install emqx-plugin-template as a rebar3 template.
For a Linux system, use the following commands to download the emqx-plugin-template:
$ mkdir -p ~/.config/rebar3/templates
$ pushd ~/.config/rebar3/templates
$ git clone https://github.com/emqx/emqx-plugin-template
$ popdTIP
If the REBAR_CACHE_DIR environment variable is set, the directory for templates should be $REBAR_CACHE_DIR/.config/rebar3/templates. Here is a related issue.
Generate the Plugin Skeleton 
Generate a new plugin project using the installed template:
$ rebar3 new emqx-plugin my_emqx_pluginThis command creates a working skeleton for your plugin in the my_emqx_plugin directory.
Directory Structure 
The rebar3 new emqx-plugin command creates a standard Erlang application with emqx included as a dependency, structured as follows:
my_emqx_plugin
├── LICENSE
├── Makefile
├── README.md
├── erlang_ls.config
├── priv
│   ├── config.hocon.example
│   ├── config_i18n.json.example
│   ├── config_schema.avsc.enterprise.example
│   └── config_schema.avsc.example
├── rebar.config
├── scripts
│   ├── ensure-rebar3.sh
│   └── get-otp-vsn.sh
└── src
    ├── my_emqx_plugin_app.erl
    ├── my_emqx_plugin.app.src
    ├── my_emqx_plugin_cli.erl
    ├── my_emqx_plugin.erl
    └── my_emqx_plugin_sup.erl- src: Contains the code for the plugin’s OTP application.
- priv: Holds the plugin’s configuration files and schema (with example files).
- rebar.config: The- rebar3configuration file used to build the application and package it into a release.
- Makefile: The entry point for building the plugin.
- scripts: Helper scripts for the- Makefile. Note: As the template depends on- emqx, it requires a custom version of- rebar3, which you can install using the included- ./scripts/ensure-rebar3.shscript.
- README.md: Documentation placeholder.
- LICENSE: Sample license file for the plugin.
Understand the Configuration File: rebar.config 
The rebar.config file is used to build the plugin and pack it into a release. Review the rebar.config file and adjust it as necessary for your plugin's requirements.
The most important sections are:
- Dependencies (deps) section;
- Release section (relx);
- Plugin description (emqx_plugin) section.
In the deps section, you can add dependencies to other OTP applications that your plugin depends on.
{deps,
    [
        ...
        %% this is my plugin's dependency
        {map_sets, "1.1.0"}
    ]}.The template adds a single dependency to the plugin: map_sets. You can remove this if it's not required. For more details on dependencies, refer to the rebar3 dependency documentation.
In the relx section, you specify the release name and version, and the list of applications to be included in the release.
{relx, [ {release, {my_emqx_plugin, "1.0.0"},
            [ my_emqx_plugin
            , map_sets
            ]}
       ...
       ]}.Normally, you would like to add the applications of the runtime dependencies from the deps section to the release.
The release name and version are important because they are used to identify the plugin when it is installed into EMQX. They form a single identifier for the plugin (my_emqx_plugin-1.0.0) by which it is addressed in the API or CLI.
In the plugin description section, you specify additional information about the plugin.
{emqx_plugrel,
  [ {authors, ["Your Name"]}
  , {builder,
      [ {name, "Your Name"}
      , {contact, "your_email@example.com"}
      , {website, "http://example.com"}
      ]}
  , {repo, "https://github.com/emqx/emqx-plugin-template"}
  , {functionality, ["Demo"]}
  , {compatibility,
      [ {emqx, "~> 5.0"}
      ]}
  , {description, "Another amazing EMQX plugin"}
  ]
}Overview of the src Directory 
The src directory contains the code of the plugin's OTP application.
my_emqx_plugin.app.src 
This is a standard Erlang application description file, which is compiled into my_emqx_plugin.app in the release.
- The version of the application does not have to match the release version and can follow a different versioning scheme.
- Pay special attention to the applicationssection. Since the plugin is built as an OTP application, starting, stopping, or restarting the plugin is the same as performing that action on the plugin's OTP application. If your plugin depends on other applications, make sure to list them in theapplicationssection of the plugin's configuration file.
my_emqx_plugin_app.erl 
This is the main module implementing the application behaviour ( start/2 and stop/1 functions) to start and stop the plugin's application and its supervision tree.
Common activities in the start/2 function include:
- Hook into EMQX hookpoints.
- Register CLI commands.
- Start the supervision tree.
Optionally, the _app.erl module can implement the on_config_changed/2 and on_health_check/1 callback functions.
- on_config_changed/2is called when the plugin's configuration is changed via the Dashboard, API or CLI.
- on_health_check/1is called when the plugin's status is requested. A plugin can report its status from this function.
Other Files 
The my_emqx_plugin_cli.erl module implements the CLI commands of the plugin. When registered, CLI commands are called via emqx ctl command.
my_emqx_plugin_sup.erl implements a typical supervisor for the plugin.
my_emqx_plugin.erl is the main module of the plugin, implementing the plugin's logic. In the skeleton, it implements several demonstrational hooks with simple logging. Any other modules may be added to the plugin.
Note
The application modules and files may be arbitrarily named with the only requirements:
- The application name must be the same as the plugin name.
- The application module (_app) must be named as{plugin_name}_app.
Overview of the priv Directory 
The priv directory holds the plugin's configuration files and schema.
config.hocon 
This file contains the plugin's initial configuration in HOCON format. You can use config.hocon.example for quick reference.
config_schema.avsc 
This file defines the schema for the plugin's configuration in Avro format. When present, EMQX will validate the plugin's configuration against this schema whenever it's updated. The release build will fail if config.hocon does not conform to the schema.
Additionally, this file can include UI hints, enabling interactive configuration through the EMQX Dashboard. For reference, see config_schema.avsc.enterprise.example.
config_i18n.json 
This file contains translations for the plugin's configuration UI in JSON format. For example:
{
  "$key": {
    "zh": "中文翻译",
    "en": "English translation"
  },
  ...
}The translations are referenced in the config_schema.avsc in UI hints. See config_i18n.json.example and config_schema.avsc.enterprise.example for more information.
Implement the Plugin 
Once the skeleton is ready, begin implementing your plugin's logic. To implement a plugin, the following logic is typically required:
- Implementing hooks and CLI commands.
- Handling configuration updates.
- Handling health checks.
Implement Hooks and CLI Commands 
EMQX defines hookpoints for various events. Any application (including a plugin) can register callbacks for these hookpoints to react to events or modify default behavior.
The most commonly used hookpoints are available in the skeleton file. A complete list of hookpoints, their arguments, and expected return values is also provided in the EMQX code.
To register a callback for a hookpoint, use the emqx_hooks:add/3 function. You need to provide the following parameters:
- The hookpoint name
- The callback module and function, potentially with additional arguments that EMQX will pass
- The callback’s priority (usually ?HP_HIGHESTto ensure it is called first)
To unregister a callback, use the emqx_hooks:del/2 function with the hookpoint name and callback module/function.
For example, to register/unregister callbacks for client.authenticate and client.authorize hookpoints:
-module(my_emqx_plugin).
...
hook() ->
  emqx_hooks:add('client.authenticate', {?MODULE, on_client_authenticate, []}, ?HP_HIGHEST),
  emqx_hooks:add('client.authorize', {?MODULE, on_client_authorize, []}, ?HP_HIGHEST).
unhook() ->
  emqx_hooks:del('client.authenticate', {?MODULE, on_client_authenticate}),
  emqx_hooks:del('client.authorize', {?MODULE, on_client_authorize}).Typically, hooks should be enabled and disabled together with the plugin, so you can call hook/unhook in the start/2 and stop/1 functions of the plugin's application:
start(_StartType, _StartArgs) ->
    {ok, Sup} = my_emqx_plugin_sup:start_link(),
    my_emqx_plugin:hook(),
    {ok, Sup}.
stop(_State) ->
    my_emqx_plugin:unhook().The signature of the callback functions is available in the hookpoint specification. For example:
-callback 'client.authorize'(
    emqx_types:clientinfo(), emqx_types:pubsub(), emqx_types:topic(), allow | deny
) ->
    fold_callback_result(#{result := allow | deny, from => term()}).
-callback 'client.authenticate'(emqx_types:clientinfo(), ignore) ->
    fold_callback_result(
        ignore
        | ok
        | {ok, map()}
        | {ok, map(), binary()}
        | {continue, map()}
        | {continue, binary(), map()}
        | {error, term()}
    ).Here is an example of callback function implementations:
%% Only allow connections with client IDs that match any of the characters: A-Z, a-z, 0-9, and underscore.
on_client_authenticate(_ClientInfo = #{clientid := ClientId}, Result) ->
  case re:run(ClientId, "^[A-Za-z0-9_]+$", [{capture, none}]) of
    match -> {ok, Result};
    nomatch -> {stop, {error, banned}}
  end.
%% Clients can only subscribe to topics formatted as /room/{clientid}, but can send messages to any topics.
on_client_authorize(_ClientInfo = #{clientid := ClientId}, subscribe, Topic, Result) ->
  case emqx_topic:match(Topic, <<"/room/", ClientId/binary>>) of
    true -> {ok, Result};
    false -> stop
  end;
on_client_authorize(_ClientInfo, _Pub, _Topic, Result) -> {ok, Result}.In the skeleton app, hooks are registered via my_emqx_plugin:load/1 and unregistered via my_emqx_plugin:unload/0.
Handle Configuration Updates 
When a user updates the plugin's configuration, the on_config_changed/2 callback function of the plugin's application is invoked.
In this callback, you typically need to:
- Validate the new configuration.
- React to the changes if the plugin is running.
While validating the configuration, keep in mind that the application may not yet be started. Therefore, use stateless checks and avoid environment-dependent checks that could cause inconsistencies across nodes.
If the plugin is running, you can apply the configuration changes. The usual pattern is as follows:
- On application start, a gen_serveris initiated to handle the configuration.
- This server, e.g., my_emqx_plugin_config_server, reads the current configuration and initializes its state.
- The on_config_changed/2callback validates the configuration and sends the new configuration to themy_emqx_plugin_config_server.
- If the server is running, it updates its state with the new configuration; if not, no action is taken.
Handle Health Checks 
The on_health_check/1 callback is called when EMQX requests the plugin's status. The plugin can report its health as follows:
- Return okif the plugin is healthy.
- Return {error, Reason}with a binary reason to indicate an issue with the plugin.
This callback is essential for plugins that rely on external resources that may become unavailable.
For more details, see my_emqx_plugin_app:on_health_check/1 in the skeleton app.
TIP
Although this function is invoked for running plugins, it may also be called during plugin startup or shutdown due to concurrency.
You can find more implementation examples in Implement Customized Plugin Logic.
Build the Plugin Package 
Execute the following command to make a release of the plugin:
$ cd my_emqx_plugin
$ make relThis will create the plugin release: _build/default/emqx_plugin/my_emqx_plugin-1.0.0.tar.gz. This package can be used to provision/install the plugin.
Package Structure 
When a plugin is built into a release, the package structure is as follows:
└── my_emqx_plugin-1.1.0.tar.gz
    ├── map_sets-1.1.0
    ├── my_emqx_plugin-0.1.0
    ├── README.md
    └── release.jsonThe tarball includes the compiled applications (as specified in the relx section of rebar.config), the README.md file, and release.json, which contains metadata about the plugin.
{
    "hidden": false,
    "name": "my_emqx_plugin",
    "description": "Another amazing EMQX plugin.",
    "authors": "Anonymous",
    "builder": {
        "name": "Anonymous",
        "contact": "anonymous@example.org",
        "website": "http://example.com"
    },
    "repo": "https://github.com/emqx/emqx-plugin-template",
    "functionality": "Demo",
    "compatibility": {
        "emqx": "~> 5.7"
    },
    "git_ref": "unknown",
    "built_on_otp_release": "27",
    "emqx_plugrel_vsn": "0.5.1",
    "git_commit_or_build_date": "2025-04-29",
    "metadata_vsn": "0.2.0",
    "rel_apps": [
        "my_emqx_plugin-0.1.0",
        "map_sets-1.1.0"
    ],
    "rel_vsn": "1.1.0",
    "with_config_schema": true
}