Plugin Development
A plugin extends the panel itself: extra API routes, admin and server tabs, scheduled work, WebSocket messages, and plugin-owned storage. It is not the same thing as a game-server mod. Server owners install those from Mods & Plugins; this guide is for people who write code that runs inside the panel.
If you only need to call Catalyst from outside, use the REST API instead.
What a plugin can do
Section titled “What a plugin can do”| Capability | Where it shows up |
|---|---|
| HTTP routes | /api/plugins/<name>/…, same session and API key auth as the rest of the panel |
| Admin and server tabs | Sidebar and server detail pages |
| Standalone pages | A path such as /my-plugin |
| UI slots | Dashboard, sidebar, and profile |
| Scheduled work | Re-registered every time the plugin is enabled |
| Events | Host lifecycle (server:started) and your own events |
| Storage | Key-value plus document collections, scoped to the plugin |
| Sign-in | “Continue with …” buttons on the login page |
| Role permissions | Your own permission ids, listed in the role editor and usable to gate your UI |
| License validation | Vendor-owned license checks and encrypted payloads |
| Plugin-to-plugin calls | Named functions another plugin can invoke |
Plugins run in the panel process. A thrown error in your code can take the
backend down with it. There is no worker isolation to rely on: isolation is not
implemented, so a manifest that declares "runtime": "isolated" is refused at
load time instead of running in-process.
The pieces
Section titled “The pieces”Every plugin is one directory whose name equals the manifest name:
my-plugin/ plugin.json required manifest backend/index.js optional backend entry (ESM) frontend/index.ts optional UI entry frontend/frontend.mjs optional self-contained UI bundle README.md optional assets/ optional static filesThat layout is what the official
catalyst-plugins repository
uses, and it is what the panel installer accepts. A package may contain only
plugin.json, README.md, LICENSE, backend/, frontend/, and assets/.
Anything else is rejected.
| File | Job |
|---|---|
plugin.json |
Identity, permissions, config fields, events. Validated before the plugin loads. See Manifest. |
backend entry |
Default-exports { onLoad, onEnable, onDisable, onUnload }. See Backend. |
frontend entry |
Default-exports a frontend plugin definition. See Frontend. |
Two ways to ship
Section titled “Two ways to ship”Marketplace package. Build a .catpkg.zip, publish it from an index, and
admins install it from Admin → Extensions → Marketplace. The package stays
inert until an admin accepts the safety disclaimer and enables it. This is how
third-party plugins reach a panel. See Packaging.
Repository checkout. The panel image compiles first-party plugins from the
catalyst-plugins repository at build time. That path shares the panel’s React
copy, so React hooks work. A marketplace install that was not compiled into the
image falls back to frontend.mjs, which cannot use React hooks. If your UI
calls useState or useEffect, ship it as a repository plugin or keep the UI
free of hooks.
Related
Section titled “Related”- Create a plugin — scaffold, install, enable.
- Manifest — every
plugin.jsonfield. - Backend — routes, tasks, events, WebSocket, files.
- Frontend — tabs, pages, slots.
- Data & permissions — config, storage, what you may read.
- Sign-in plugins — OAuth and SSO.
- Plugin licensing — vendor-owned license validation and encrypted payloads.
- Packaging & marketplace —
.catpkg.zipand indexes. - API reference — the host REST API a plugin can call.
- Nothing executes before an accepted safety disclaimer. Discovery reads
plugin.jsononly; the backend module is imported andonLoadruns when an admin enables the plugin. A version or permission change forces a fresh acceptance and leaves the new code disabled until it happens. - Declare the smallest permission set you actually use. Admins see that list before they enable the plugin, and every host capability is gated on it.
- Never put secrets in
plugin.jsonor in source. Put them in config fields of typepassword, and document what the operator must set. - Register HTTP routes in
onLoad. Register tasks, WebSocket handlers, and event listeners inonEnable, because disable clears them. - Gate your routes on the caller, not only on your own grant:
config.requiredPermissionsis enforced by the host. See Backend. request.user.userIdis the authenticated user id. Usecontext.getUserId(request).context.getConfig(key)returns the configured value, not the schema object.
