Publishing
A plugin reaches a deployment as an npm package. The admin console finds it in a registry, the server installs the exact version an administrator picked, and every server copy checks it against the integrity hash the registry published. This page covers what to get right before you publish, how to test a build on a real server first, and what an operator has to configure before their server will install a plugin that isn't part of the RapidMX project.
Before you publish
Name
Publish the plugin as a scoped package whose name ends in -plugin: @acme/greeting-plugin. The admin
console's search lists the packages in each configured namespace (an npm scope) whose names end in -plugin; a package
named any other way can still be added by its exact name, but nobody will find it by searching.
Package contents
filesmust includedistand, when the plugin has pages,apps(the TSX sources the server builds from).@rapidrest/core,@rapidrest/service-core,@rapidmx/restapiand, for pages,@rapidmx/web-client,reactandreact-domare peer dependencies. The server installs plugins without their peer dependencies and refuses one that brings its own copy of the first three. A peer dependency that is also a runtime library you need, such as an editor component the web client already ships, is shared the same way: the CRM plugin declares@tiptap/*,@dnd-kit/coreandreact-iconsas peers for that reason.- Everything in
dependenciesis installed from the registry with--ignore-scripts, so a dependency that needs an install script to work won't work. The installed size of all plugins together is capped at 500 MB by default (system:plugins:max_install_bytes). - Build in
prepublish, as the published plugins do ("prepublish": "yarn build"), so the tarball always has a freshdist.
Manifest
Check the manifest against the server's own validator before every release. parsePluginManifest() from
@rapidmx/restapi is what the server runs, and a test that calls it on the package's package.json catches an invalid
setting, a bad mount or a duplicate id. See Routes, models and jobs.
Pay particular attention to setting keys. The server refuses a setting under any of its own configuration namespaces,
and that list grows as the server does. A key that was allowed when you published can become reserved in a later
server release, and the whole plugin is then refused, so keep every key under your plugin's own namespace
(mail:yourplugin:*) and recheck the manifest against each new @rapidmx/restapi release. The list and the reasons
for it are on the Settings page.
Versions
- Use exact semver versions. The server stores and installs the exact version an administrator chose, never a range.
- The admin console offers the registry's
latestrelease by default, or the highest release whenlatestdoesn't name one. Prereleases (1.0.0-beta.2) are only offered when the administrator asks for them, so publish a prerelease with a dist-tag other thanlatest. apiVersionstays1until the server publishes a new plugin contract.- Declare the
@rapidmx/restapiand@rapidmx/web-clientversions you built against as the lower bound of the peer range (">=0.33.0 <1"), so the range says which servers you have tested with. - Keep model class names and setting keys stable across releases. Renaming a model orphans its data, and renaming a setting drops the value saved under the old key.
Integrity
The server records the registry's dist.integrity hash when an administrator adds a plugin and refuses an installed
package that doesn't match it. npm publishes that hash for you. A private registry that publishes only legacy shasum
values works only if the operator turns off system:plugins:require_integrity, which also turns off the check.
Test a build on a real server first
Two ways to run an unpublished build, both from the file npm pack writes.
From the server's configuration, for development. Point system:plugins:sources at the tarball, and list the
plugin in system:plugins:defaults so it gets a row:
export system__plugins__sources='{"@acme/greeting-plugin":"/home/me/src/greeting-plugin/acme-greeting-plugin-0.2.0.tgz"}'
export system__plugins__defaults='[{"name":"@acme/greeting-plugin","enabled":true}]'
A plugin listed in sources is installed from the tarball in place of the registry version, and isn't checked against
a registry integrity hash. Create a plugin walks through this on a local
server. Use a tarball, never the plugin's folder: a folder resolves its own development copies of the shared packages
and is refused.
From the admin console, on a deployment you don't control the configuration of. On the Plugins page, Upload
installs a plugin from the .tgz file npm pack produced. This needs an administrator with an elevated sign-in. The
server stores the pack in its blob store so every server copy installs the same bytes, records its own integrity hash,
and skips the allow-list (the administrator vouches for the code). Packs are limited to 50 MiB
(system:plugins:uploads:max_bytes) and the feature can be switched off (system:plugins:uploads:enabled). An uploaded
pack's dependencies must still come from the registry: a dependency on a path, a git URL or a tarball URL is refused. An
uploaded plugin isn't offered registry updates; choosing a registry version for it later switches it back to the
registry.
After deploying either way, check the admin console's Plugins page and the server log for each server copy. "Loaded plugin" and "Serving plugin ... pages" lines mean it worked; a refused check, an import error or a page build error is reported there with the reason. Exercise the plugin's real routes and pages too: a plugin that loads can still fail at the first request.
Publish
npm publish --access public
The RapidMX project's own plugins are released with the same tooling as the rest of the project, which builds the changelog, tags and publishes. See Releasing.
Getting a third-party plugin onto a deployment
Out of the box a server only installs packages in the @rapidmx scope. A plugin runs with the server's full privileges,
so only the operator, through the server's configuration, can allow more. There are three ways, and the plugin's
documentation should tell operators which one to use:
-
Add the plugin's scope as a namespace.
system:plugins:namespaceslists the scopes the admin console searches, and packages in them may be added. Keep@rapidmxin the list. An entry can name its own registry and token for a private scope:export system__plugins__namespaces='["@rapidmx", {"name": "@acme", "registry": "https://npm.acme.example", "token": "..."}]' -
Allow individual packages with
system:plugins:allowed_packages(package names, or a scoped wildcard such as@acme/*). An unscoped wildcard is ignored, so nothing can allow the whole registry. -
Upload the pack from the admin console, as above.
A namespace or allowed package doesn't install anything by itself. An administrator still adds the plugin on the Plugins page, and only from then on does every server copy install it. If the allow-list is later narrowed, plugins already installed from it are refused at the next start and reported on the Plugins page.
Security expectations
Because a plugin has the server's full access, treat it like code in the server itself:
- follow the same rules as core code for mailbox data: check access with trusted roles stripped (see Routes, models and jobs);
- rate-limit every route that answers anonymous callers;
- mark sensitive settings
"secret": true; - report vulnerabilities in RapidMX's own plugins the way Security describes.