Section: Plugins Order: 200 # Plugin system Orbit has a small, **operator-controlled** plugin system. A deployment lists plugin scripts in `config.json`; each is loaded at startup and registers against a global `Orbit` object to hook events, read state, send IRC, theme the UI, and add UI in predefined slots. > **Experimental.** Orbit is a work in progress and this API may change between > releases. Plugins are deployment-controlled — same trust level as the app > itself. There is no user-uploaded plugin marketplace, by design. ## Enabling plugins Add script URLs to `plugins` in [`config.json`](https://orbit.tchatou.fr/docs/config/): ```json { "plugins": ["/app/plugins/orbit-demo.js"] } ``` They load in order, after the app boots. Host them anywhere the page can reach (same-origin recommended). For a third-party origin, pin the file with Subresource Integrity by giving an object instead of a URL: ```json { "plugins": [{ "url": "https://cdn.example/x.js", "integrity": "sha384-…" }] } ``` (`crossorigin` defaults to `anonymous` when an `integrity` hash is set.) ## A quick plugin A plugin is a plain `.js` file that calls `Orbit.plugin()`. UI is authored with the `html` tagged template (runtime markup, no build step), or with `orbit.h(...)` (`React.createElement`). ```js Orbit.plugin('my-plugin', (orbit, log) => { log('loaded, Orbit v' + orbit.version); orbit.on('message', (m) => log(m.from, 'said', m.text, 'in', m.target)); orbit.addUi('composer_button', () => orbit.html``); }); ``` For anything substantial — real components with state — build a compiled plugin instead. See **[Compiled plugins](https://orbit.tchatou.fr/docs/compiled-plugins/)**. ## The `Orbit` API ### Global | Member | Description | |---|---| | `Orbit.version` / `Orbit.commit` | app version + git commit (build-time) | | `Orbit.apiVersion` | plugin API contract version (bumped on breaking changes) — guard with `if (Orbit.apiVersion < N) …` | | `Orbit.plugin(name, fn)` | register a plugin; `fn(orbit, log)` | | `Orbit.on/once/off/emit(event, …)` | the app event bus | | `Orbit.config()` | the resolved runtime config | | `Orbit.React` / `Orbit.ReactDOM` / `Orbit.jsxRuntime` / `Orbit.Fragment` / `Orbit.h` / `Orbit.html` | render primitives (externalization targets for compiled plugins) | ### Inside `Orbit.plugin(name, (orbit) => …)` | Member | Description | |---|---| | `orbit.on/once/off/emit` | event bus (see events below) | | `orbit.state.active()` | active buffer name | | `orbit.state.nick()` / `account()` | your nick / logged-in account | | `orbit.state.buffers()` | open buffer names | | `orbit.state.get()` | full store snapshot (read-only) | | `orbit.irc.send(line)` | send a raw IRC line | | `orbit.irc.msg(target, text)` | PRIVMSG a target | | `orbit.irc.say(text)` | send to the active buffer | | `orbit.irc.join(chan)` / `part(chan)` | join / part | | `orbit.irc.list()` | request the channel list | | `orbit.themes.current()/list()/set(id)` | read/set the theme | | `orbit.storage.get(key, def)/set(key, val)` | namespaced persistence | | `orbit.addUi(slot, render)` | add UI to a slot (returns a remover) | | `orbit.addSettingsSection({label, icon?, render})` | add a whole Settings section | | `orbit.addMessageDecorator(m => …)` | inline UI after every message's text; `m` = `{id, nick, text, kind, ts, mine}` | | `orbit.addMessageAction(m => …)` | a button in every message's hover action toolbar (next to reply/react) | | `orbit.h / orbit.html` | render helpers | | `log(…)` | namespaced console logger | ### Events `ready`, `connected` (`{nick}`), `status` (connection status string), `buffer.active` (buffer name), `message` (`{from, target, text, self}`), `raw` (the parsed IRC message). ### UI slots | Slot | Where | |---|---| | `composer_button` | a button in the message composer toolbar | | `topbar_item` | an item in the channel topbar action row (next to search / notifications) | | `sidebar_item` | an item in the conversation sidebar header (next to the compose button) | | `settings_section` | a whole section in Settings (own nav entry + pane) — use `orbit.addSettingsSection()` | Two per-message hooks run for every rendered message: `orbit.addMessageDecorator(m => …)` appends inline UI after the text, and `orbit.addMessageAction(m => …)` adds a button to the hover action toolbar next to reply/react. Every contributed slot, action and decorator renders inside its own error boundary, so a crashing plugin renders nothing instead of taking down the app. ## Trust & security Plugins are **operator-controlled**: a deployment lists them in `config.json`, so they run with the same trust as the app itself. There is no user-uploaded plugin mechanism. Orbit deliberately does **not** expose internal modules or runtime component replacement — that would couple plugins to internals that are still moving. The API above is the stable surface.