PageSourceSearch

https://www.rabbitmq.com/assets/js/12607352.8f5402a2.js

js rabbitmq.com collected 2026-09-24 06:05:14 UTC 16,853 bytes, 1 lines download raw bytes

1"use strict";(self.webpackChunkrabbitmq_website=self.webpackChunkrabbitmq_website||[]).push([["14096"],{32146(e,n,i){i.r(n),i.d(n,{metadata:()=>t,default:()=>h,frontMatter:()=>l,contentTitle:()=>a,toc:()=>d,assets:()=>o});var t=JSON.parse('{"type":"mdx","permalink":"/plugin-development","source":"@site/src/pages/plugin-development.md","title":"Plugin Development Basics","description":"\x3c!--","frontMatter":{"title":"Plugin Development Basics"},"unlisted":false}'),r=i(74848),s=i(28453);let l={title:"Plugin Development Basics"},a="Plugin Development Basics",o={},d=[{value:"Why Develop a Plugin?",id:"pros",level:2},{value:"Why To Not Develop a Plugin",id:"cons",level:2},{value:"Getting Started",id:"getting-started",level:2},{value:"Activating Plugins During Development",id:"installing-plugins-during-development",level:2},{value:"Plugin Quality Tips",id:"plugin-quality-tips",level:2},{value:"Broker and Dependency Version Constraints",id:"plugin-version-constraints",level:2},{value:"Example Plugin: Metronome",id:"plugin-hello-world",level:2},{value:"Development Process",id:"development-process",level:2}];function c(e){let n={a:"a",code:"code",h1:"h1",h2:"h2",header:"header",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,s.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(n.header,{children:(0,r.jsx)(n.h1,{id:"plugin-development-basics",children:"Plugin Development Basics"})}),"\n",(0,r.jsx)(n.p,{children:"This guide covers the basics of RabbitMQ plugin development. It is expected that\nbefore reading this guide, the reader has a basic understanding of the RabbitMQ plugin mechanism."}),"\n",(0,r.jsxs)(n.p,{children:["Readers are also expected to have a basic understanding of ",(0,r.jsx)(n.a,{href:"https://www.erlang.org",children:"Erlang"}),"\nand the ",(0,r.jsx)(n.a,{href:"http://www.erlang.org/doc/system_principles/users_guide.html",children:"OTP design principles"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.a,{href:"https://learnyousomeerlang.com",children:"Learn You Some Erlang"})," is a great way to get\nstarted with Erlang and OTP."]}),"\n",(0,r.jsx)(n.h2,{id:"pros",children:"Why Develop a Plugin?"}),"\n",(0,r.jsx)(n.p,{children:"Writing a RabbitMQ plugin provides a number of appealing possibilities:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Enable your application to access internal RabbitMQ functionality that is not exposed\nvia one of the supported protocols."}),"\n",(0,r.jsx)(n.li,{children:"Running in the same Erlang VM as the broker may increase performance for certain workloads."}),"\n",(0,r.jsx)(n.li,{children:"Plugins can implement features that otherwise would have to be implemented by every application\n(service) in the system, creating duplication and increasing maintenance load"}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"cons",children:"Why To Not Develop a Plugin"}),"\n",(0,r.jsx)(n.p,{children:"As with any plugin mechanism, consideration should be given when developing functionality as to whether\nembedding it as a plugin is the most appropriate path to take. Some reasons that you might not want to\ndevelop your functionality as a plugin:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Depending on internal RabbitMQ APIs can result in your application requiring changes when\nnew RabbitMQ come out, including patch releases. If you can do what you need to do without\nusing RabbitMQ internals, then your application will be far more forward-compatible"}),"\n",(0,r.jsxs)(n.li,{children:["A poorly written plugin can result in the ",(0,r.jsx)(n.strong,{children:"entire node becoming unavailable"})," or misbehaving"]}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"getting-started",children:"Getting Started"}),"\n",(0,r.jsx)(n.p,{children:"To develop a RabbitMQ plugin, first make sure the following\nrequirements are met:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["Ensure that you have a working installation of ",(0,r.jsx)(n.a,{href:"http://git-scm.com/",children:"Git"})]}),"\n",(0,r.jsxs)(n.li,{children:["Ensure that the dependencies detailed in the ",(0,r.jsx)(n.a,{href:"/docs/build-server#prerequisites",children:"Server Build"})," guides are installed and functional"]}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.a,{href:"http://erlang.mk",children:"Erlang.mk"})," is used to\nbuild RabbitMQ and its plugins. The easiest way to start on\na new plugin is probably to copy an existing plugin such as\n",(0,r.jsx)("tt",{children:"rabbitmq-metronome"}),", ",(0,r.jsx)("a",{href:"#plugin-hello-world",children:"used\nas an example below"}),"."]}),"\n",(0,r.jsx)(n.h2,{id:"installing-plugins-during-development",children:"Activating Plugins During Development"}),"\n",(0,r.jsx)(n.p,{children:"To test the plugin during development, use the following make target to start\na RabbitMQ node with the local plugin built from source and enabled:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"make run-broker\n"})}),"\n",(0,r.jsx)(n.h2,{id:"plugin-quality-tips",children:"Plugin Quality Tips"}),"\n",(0,r.jsx)(n.p,{children:"A badly-written plugins can pose a risk to the stability of the broker.\nTo ensure that your plugin can safely operate without affecting RabbitMQ core,\na couple of safety best practices are highly recommended."}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsx)(n.li,{children:"Always use a top-level supervisor for your application."}),"\n",(0,r.jsx)(n.li,{children:"Never start the plugin application directly,\ninstead opting to create a (possibly quite trivial) supervisor that will prevent the Erlang VM from\nshutting down due to a crashed top-level application."}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"plugin-version-constraints",children:"Broker and Dependency Version Constraints"}),"\n",(0,r.jsxs)(n.p,{children:["It's possible to specify broker and dependency version\nrequirements for a plugin using the\n",(0,r.jsx)("code",{children:"broker_version_requirements"})," key in plugin's\napplication environment. The requirements are specified as a list of\
1nminimum version in each release series.\nConsider the following example:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-erlang",children:'{application, my_plugin,[\n    %% ...\n    {broker_version_requirements, ["3.11.15", "3.10.22"]}\n]}\n'})}),"\n",(0,r.jsxs)(n.p,{children:["The above requires RabbitMQ\n3.10.x starting with 3.10.22 and 3.11.x starting with 3.11.15.\nNote that when new major and minor (feature) RabbitMQ versions\ncome out, ",(0,r.jsx)(n.strong,{children:"it is necessary for plugin maintainers to update the list"}),"."]}),"\n",(0,r.jsx)(n.p,{children:"Plugins can have dependencies. It is possible to specify supported\nversion series for dependencies, too. This is quite similar\nto the above but uses a dictionary-like data structure (proplist)."}),"\n",(0,r.jsx)(n.p,{children:"For example:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-erlang",children:'{application, my_plugin, [\n    %% ...\n    {dependency_version_requirements, [{rabbitmq_management, ["3.11.0", "3.10.22"]}]}\n]}\n'})}),"\n",(0,r.jsxs)(n.p,{children:["means the plugin depends on ",(0,r.jsx)(n.code,{children:"rabbitmq_management"})," 3.10.x starting\nwith 3.10.22 and all versions in the 3.11.x series."]}),"\n",(0,r.jsx)(n.h2,{id:"plugin-hello-world",children:"Example Plugin: Metronome"}),"\n",(0,r.jsx)(n.p,{children:"Seeing as no development guide would be complete without a Hello World example, the following tries to\nprovide the basics of how your would build your very own RabbitMQ plugin."}),"\n",(0,r.jsx)(n.p,{children:"The following example details how you might build a simple plugin that acts like a metronome."}),"\n",(0,r.jsxs)(n.p,{children:["Every second, it fires a message that has a routing key in the form ",(0,r.jsx)(n.code,{children:"yyyy.MM.dd.dow.hh.mm.ss"})," to a\ntopic exchange called ",(0,r.jsx)(n.code,{children:"metronome"})," by default. Applications can attach queues to this exchange with\nvarious routing keys in order to be invoked at regular intervals. For example, to receive a message\nevery second, a binding of ",(0,r.jsx)(n.code,{children:"*.*.*.*.*.*.*"})," could be applied. To receive a message every minute, a\nbinding of ",(0,r.jsx)(n.code,{children:"*.*.*.*.*.*.00"})," could be applied instead."]}),"\n",(0,r.jsxs)(n.p,{children:["The ",(0,r.jsx)(n.a,{href:"https://github.com/rabbitmq/rabbitmq-metronome",children:"rabbitmq-metronome"})," repository on GitHub\ncontains a copy of the code for this plugin."]}),"\n",(0,r.jsx)(n.p,{children:"The following table should explain the purpose of the various files in the repository."}),"\n",(0,r.jsxs)("table",{children:[(0,r.jsxs)("tr",{children:[(0,r.jsx)("th",{children:"Filename"}),(0,r.jsx)("th",{children:"Purpose"})]}),(0,r.jsxs)("tr",{children:[(0,r.jsx)("td",{children:(0,r.jsx)("a",{href:"https://github.com/rabbitmq/rabbitmq-metronome/blob/master/Makefile",children:(0,r.jsx)("code",{children:"Makefile"})})}),(0,r.jsx)("td",{children:(0,r.jsxs)(n.p,{children:["This top-level Makefile defines the name\nof your plugin and its dependencies. The\nname must match the Erlang application name.\nDependencies are declared using erlang.mk's\nvariables. Just after that, the Makefile includes\n",(0,r.jsx)("tt",{children:"rabbitmq-components.mk"})," and ",(0,r.jsx)("tt",{children:"erlang.mk"}),",\nas well as ",(0,r.jsx)("tt",{children:"rabbitmq-plugins.mk"})," using erlang.mk\nplugins facility. See below for a description of those\nfiles."]})})]}),(0,r.jsxs)("tr",{children:[(0,r.jsx)("td",{children:(0,r.jsx)("a",{href:"https://github.com/rabbitmq/rabbitmq-metronome/blob/master/erlang.mk",children:(0,r.jsx)("code",{children:"erlang.mk"})})}),(0,r.jsx)("td",{children:(0,r.jsxs)(n.p,{children:["A local copy of ",(0,r.jsx)("tt",{children:"erlang.mk"}),". This is not a vanilla\ncopy because RabbitMQ relies on a few modifications\nwhich have not been merged upstream at the time of\nthis writing. That's why ",(0,r.jsx)("tt",{children:"ERLANG_MK_REPO"})," and\n",(0,r.jsx)("tt",{children:"ERLANG_MK_COMMIT"})," are overridden for now."]})})]}),(0,r.jsxs)("tr",{children:[(0,r.jsx)("td",{children:(0,r.jsx)("a",{href:"https://github.com/rabbitmq/rabbitmq-metronome/blob/master/rabbitmq-components.mk",children:(0,r.jsx)("code",{children:"rabbitmq-components.mk"})})}),(0,r.jsxs)("td",{children:[(0,r.jsxs)(n.p,{children:["A local copy of ",(0,r.jsx)("tt",{children:"rabbitmq-components.mk"}),". The\noriginal file is in ",(0,r.jsx)("tt",{children:"rabbitmq-common"})," which your\nplugin will depend on automatically. It contains other\nerlang.mk extensions and helpers which must be defined\nbefore ",(0,r.jsx)("tt",{children:"erlang.mk"})," inclusion. This file must be\nkept up-to-date w.r.t. ",(0,r.jsx)("tt",{children:"rabbitmq-common"}),": when it\nis out-of-date, you will get the following error:"]}),(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{children:"error: rabbitmq-components.mk must be updated!\n"})}),(0,r.jsx)(n.p,{children:"In this case, just run the following command to update\nyour copy:"}),(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{children:"make rabbitmq-components-mk\n"})})]})]}),(0,r.jsxs)("tr",{children:[(0,r.jsx)("td",{children:(0,r.jsx)("a",{href:"https://github.com/rabbitmq/rabbitmq-metronome/blob/master/priv/schema/rabbitmq_metronome.schema",children:(0,r.jsx)("code",{children:"rabbitmq_metronome.schema"})})}),(0,r.jsxs)("td",{children:[(0,r.jsxs)(n.p,{children:["A ",(0,r.jsx)("a",{href:"https://github.com/Kyorai/cuttlefish",children:"Cuttlefish"})," configuration schema.\nUsed to translate ",(0,r.jsx)("a",{href:"/docs/configure#configuration-files",children:"configuration file"}),"\nto the internal format used by RabbitMQ and its runtime."]}),(0,r.jsxs)(n.p,{children:["Metronome schema contains mappings for the ",(0,r.jsx)("code",{children:"metronome.exchange"})," setting,\nsetting the exchange used by the plugin."]}
1),(0,r.jsx)(n.p,{children:"Configuration will be regenerated when the plugin is\nenabled. Plugin-specific values in the config will cause error if\nplugin has not been enabled."}),(0,r.jsxs)(n.p,{children:["More information about writing schema files can be found\n",(0,r.jsx)("a",{href:"https://github.com/basho/cuttlefish/wiki/Cuttlefish-for-Erlang-Developers",children:"in the Cuttlefish docs"}),"."]})]})]}),(0,r.jsxs)("tr",{children:[(0,r.jsx)("td",{children:(0,r.jsx)("a",{href:"https://github.com/rabbitmq/rabbitmq-metronome/blob/master/src/rabbit_metronome.erl",children:(0,r.jsx)("code",{children:"src/rabbit_metronome.erl"})})}),(0,r.jsx)("td",{children:(0,r.jsx)(n.p,{children:'Implementation of the Erlang "application" behaviour. Provides a means for the Erlang VM to start and\nstop the plugin.'})})]}),(0,r.jsxs)("tr",{children:[(0,r.jsx)("td",{children:(0,r.jsx)("a",{href:"https://github.com/rabbitmq/rabbitmq-metronome/blob/master/src/rabbit_metronome_sup.erl",children:(0,r.jsx)("code",{children:"src/rabbit_metronome_sup.erl"})})}),(0,r.jsx)("td",{children:(0,r.jsx)(n.p,{children:'Implementation of the Erlang "supervisor" behaviour. Monitors the worker process and restarts it if\nit crashes.'})})]}),(0,r.jsxs)("tr",{children:[(0,r.jsx)("td",{children:(0,r.jsx)("a",{href:"https://github.com/rabbitmq/rabbitmq-metronome/blob/master/src/rabbit_metronome_worker.erl",children:(0,r.jsx)("code",{children:"src/rabbit_metronome_worker.erl"})})}),(0,r.jsx)("td",{children:(0,r.jsx)(n.p,{children:"The core of the plugin. The worker will connect internally to the broker, then create a task that\nwill be triggered every second."})})]}),(0,r.jsxs)("tr",{children:[(0,r.jsx)("td",{children:(0,r.jsx)("a",{href:"https://github.com/rabbitmq/rabbitmq-metronome/blob/master/test/metronome_SUITE.erl",children:(0,r.jsx)("code",{children:"test/metronome_SUITE.erl"})})}),(0,r.jsx)("td",{children:(0,r.jsx)(n.p,{children:"Automated tests for the plugin."})})]})]}),"\n",(0,r.jsx)(n.h2,{id:"development-process",children:"Development Process"}),"\n",(0,r.jsxs)(n.p,{children:["Clone your plugin into the ",(0,r.jsx)(n.code,{children:"deps"})," directory of your ",(0,r.jsx)(n.code,{children:"rabbitmq-server"})," repository:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"git clone https://github.com/rabbitmq/rabbitmq-server.git\ncd rabbitmq-server\ngit clone https://github.com/user/my-rabbitmq-plugin.git deps/my_rabbitmq_plugin\n"})}),"\n",(0,r.jsx)(n.p,{children:"Run make to build the plugin:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"make -C deps/my_rabbitmq_plugin\n"})}),"\n",(0,r.jsxs)(n.p,{children:["To start a node with your ",(0,r.jsx)(n.code,{children:"my_rabbitmq_plugin"})," and the management plugin enabled:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"make RABBITMQ_ENABLED_PLUGINS='rabbitmq_management my_rabbitmq_plugin' run-broker\n"})}),"\n",(0,r.jsx)(n.p,{children:"To ensure that the new plugin is up and running, run the following command:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"rabbitmq-diagnostics status\n"})}),"\n",(0,r.jsx)(n.p,{children:"If your plugin has loaded successfully, you should see it in the enabled plugin list:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"# => Plugins\n# =>\n# => Enabled plugin file: /var/folders/gp/53t98z011678vk9rkcb_s6ph0000gn/T/rabbitmq-test-instances/rabbit@warp10/enabled_plugins\n# => Enabled plugins:\n# =>\n# =>  * rabbitmq_metronome\n# =>  * amqp_client\n# =>  * my_rabbitmq_plugin\n"})}),"\n",(0,r.jsx)(n.p,{children:"To run Common Test test suites, use"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"make -C deps/my_rabbitmq_plugin tests\n"})}),"\n",(0,r.jsxs)(n.p,{children:["Finally, you can produce an ",(0,r.jsx)("code",{children:".ez"})," file, suitable for distribution with:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"make -C deps/my_rabbitmq_plugin DIST_AS_EZS=yes dist\n"})}),"\n",(0,r.jsxs)(n.p,{children:["The file appears in the ",(0,r.jsx)("tt",{children:"plugins"})," directory under repository root."]})]})}function h(e={}){let{wrapper:n}={...(0,s.R)(),...e.components};return n?(0,r.jsx)(n,{...e,children:(0,r.jsx)(c,{...e})}):c(e)}},28453(e,n,i){i.d(n,{R:()=>l,x:()=>a});var t=i(96540);let r={},s=t.createContext(r);function l(e){let n=t.useContext(s);return t.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function a(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:l(e.components),t.createElement(s.Provider,{value:n},e.children)}}}]);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.