How Plugins Work
A plugin is one JSON file. The exchange stores it, runs its server script in the plugin service, shows its settings and admin view in Operator Controls, and passes its website parts to your exchange website.
The plugin JSON
| Field | What it's for |
|---|---|
name | The plugin's unique name. Its endpoints live under /plugins/<name>/. Only one plugin with a given name can be installed. |
version | A number. Raise it for every release. The exchange refuses an update with the version that is already installed. |
author | Who made the plugin. Required. |
bio, description, documentation, logo, icon, url | What Operator Controls shows about the plugin. |
type | Optional. Only one plugin of a given type can be installed at a time. For example, the KYC plugins share the type kyc, so you must remove one before you install another. Leave it null unless your plugin replaces a plugin of that type. |
meta | Private settings. Only the server script reads them. |
public_meta | Public settings. The exchange website can read them. |
prescript.install | npm packages the server script needs. See Libraries. |
script | The server script, minified into one string. |
admin_view | The admin view shown in Operator Controls. |
site_studio | What the plugin adds to a HollaEx Web v3 website: blocks, pages and slots. |
web_view | The web views for a Kit v2 Web website. |
enabled | Whether the plugin starts enabled after install. Defaults to enabled. |
prescript.run and postscript.run are accepted but the exchange doesn't run them. Leave them null.
You don't write the script, admin_view, web_view and site_studio fields by hand. A build script packs them from your source files. See Build, test and install.
Settings
Each entry in meta and public_meta describes one setting the operator fills in on the plugin's Configuration tab:
"api_secret": {
"type": "string",
"required": true,
"secret": true,
"description": "Your provider's API secret",
"value": ""
}
type: the kind of value, such asstring,numberorboolean.required: whether the plugin needs a value to work. Check it in your script too, and stop with a clear error if it's missing.description: the help text the operator sees. Write it for the operator.value: the default.secret: true: for API keys, secrets and passwords. Operator Controls never shows the saved value again. It shows********, and the operator can only replace it. Your script gets the real value. Use it for every credential.admin_view_only: true: hides the setting from the Configuration tab when your admin view edits it instead.
When you ship a new version, the operator's saved values are kept for every setting that still exists in the new version. A setting you remove is dropped with its value.
Libraries
The server script can use the libraries the plugin service already provides, including lodash, moment, mathjs, node-cron, jsonwebtoken, validator, nodemailer, ws, uuid and the HollaEx tools library.
For anything else, list the npm packages in prescript.install. The exchange installs them from npm when the plugin is installed or the plugin service starts. Write each entry as package or package@version, and scoped packages such as @scope/package@1.2.0 work. Pin an exact version so a new release of the package can't change your plugin. An entry that isn't a valid package name or version is rejected.
In the script, installedLibraries["<entry>"] holds each package, keyed by the entry exactly as you wrote it.
The server script
The script runs once when the plugin service starts. It gets:
app: a router for your endpoints. Every route must be under/plugins/<plugin-name>/. Routes match exactly, including case and any trailing slash, and query string values are always plain strings.metaandpublicMeta: your settings, with secrets decrypted.toolsLib: the HollaEx tools library, for users, balances, orders, deposits, withdrawals, the exchange's event channel and more. To protect a route, use its bearer-token middleware with the roles allowed to call it, for example admin only.loggerPlugin: the logger.installedLibraries: the packages fromprescript.install.
A few things that matter in practice:
- Webhooks. The raw request body is kept on
req.rawBody, so you can verify a provider's HMAC signature against the exact bytes it sent. - Browser calls. Your endpoints follow the same browser origin rules as the exchange API, so only your exchange's allowed domains can call them from a browser.
- Errors. If a route throws an unhandled error, the caller gets a generic error message and the details go to the log.
- Restarts. Installing, updating, enabling or disabling a plugin, and saving its settings, reload the plugin service. All plugins are briefly unavailable during the reload, and your script runs again from the start.
- Fail closed. If a required setting is missing or a provider can't be reached, reject the request or hold the action. Don't let it through.
If your script throws while it starts, the error is logged and your plugin's endpoints aren't registered. Other plugins keep running.
Admin views
An admin view adds an Admin view tab to your plugin in Operator Controls. Use one when the Configuration tab isn't enough: a dashboard, a table of records, forms for list-shaped settings, or buttons that run jobs.
New plugins use a declarative admin view: a JSON description of tabs and blocks (text, stats, key-value lists, tables, forms and action buttons) that Operator Controls draws with its own components. It runs no plugin code in the browser. Every request it makes must go to your plugin's own routes under /plugins/<plugin-name>/, and Operator Controls refuses the whole view if anything in it is invalid.
The older HTML admin views are still supported for existing plugins only. Don't build new ones.
Website views
How a plugin shows up on your exchange website depends on which website you run. See Web v3 or Kit v2 Web?
-
HollaEx Web v3. The plugin's
site_studiomanifest declares:- Blocks the operator can place in Site Studio. A block is either a declarative layout built from the website's own components, or a sandboxed frame served over HTTPS for UI the components can't express. A frame never receives the user's session. The website makes allowed calls to your plugin's routes on its behalf.
- Pages under
/app/<path>, with optional menu entries. - Slots: built-in steps the plugin fills, such as the identity check, bank account, fiat deposit and withdrawal for a currency, or the support chat.
The exchange checks the manifest on install and the website drops anything it doesn't accept. An update replaces the manifest, so a new version without one removes the pages and blocks the old version added.
-
Kit v2 Web. The plugin's
web_viewlists React component bundles, each loaded from a URL, that add a page, a verification tab or a fiat deposit and withdrawal page.
A plugin can ship both, so it works on either website.
For the current Site Studio manifest and admin view formats, open a ticket.
Need help? Open a ticket from your HollaEx dashboard.