Skip to main content
The panel imports your [ui] entry as an ES module and calls its exported register function with a per-plugin API object. The same object is also available globally as window.myrax[<plugin id>].
web/plugin.js
export async function register(myrax) {
  // myrax.id      — your plugin id
  // myrax.plugin  — the full manifest as the panel sees it
}
register may be async — the panel awaits it. If it throws, the panel shows a “Plugin failed” toast with the error message and keeps running.
myrax.sidebar.add({
  value: 'main',                  // page id, unique within your plugin
  label: 'My Plugin',             // sidebar text
  icon: '<svg ...>',              // see icon formats below
  mount(node) {                   // called when the user opens the tab
    node.innerHTML = '<h2>hi</h2>';
    return () => { /* cleanup: listeners, timers, sockets */ };
  }
});

myrax.sidebar.remove('processes'); // hide a built-in tab by its value
A page renders through exactly one of:
KeyMeaning
mount(node)full control — you get an empty container, return a cleanup function
htmlstatic HTML string
render()function returning an HTML string
myrax.routes.add(route) takes the same object — use it for pages that should exist without a sidebar entry, then navigate to them yourself.

Icon formats

ValueTreated as
<svg ...>inline SVG — use stroke="currentColor" / fill="currentColor" so it follows the theme
https://..., data:..., /pathimage URL as-is
icons/mark.svgfile inside your plugin, served from /addons/{id}/
emptythe default plug icon

toast

myrax.toast({
  title: "Saved",
  message: "Settings updated successfully",
  tone: "success",    // "success" | "error" | "info"
  timeout: 3000       // ms
});
myrax.navigate('main');   // open one of your pages by its value
Values are namespaced internally as plugin:<id>:<value> — pass either form.

styles

Stylesheets listed in the manifest load automatically; these are for dynamic cases:
myrax.styles.inject('.my-thing { color: var(--text); }'); // raw CSS
myrax.styles.load('web/extra.css');                       // file from your plugin
Relative paths are served from /addons/{id}/ with the same ?v= cache busting as your entry. Everything you inject is tagged with data-myrax-plugin="<id>" and removed when the plugin is disabled.

layout

Shorthands over styles.inject for hiding panel chrome:
myrax.layout.hide('.some-panel-selector');
myrax.layout.show('.some-panel-selector');

refresh

await myrax.refresh();   // re-fetch the panel's core data (config, plugins, stats)

Talking to your backend

The browser never reaches your runtime directly — go through the panel’s authenticated proxy:
// HTTP → your runtime's /api/status
const res = await fetch(`/api/plugins/${myrax.id}/proxy/api/status`);

// WebSocket → your runtime's /stream
const ws = new WebSocket(
  `${location.protocol === 'https:' ? 'wss' : 'ws'}://${location.host}` +
  `/api/plugins/${myrax.id}/ws/stream`
);
Same origin, same session cookie — no CORS, no extra auth.

Theme tokens

Plugin DOM lives inside the panel, so its CSS variables are yours: --bg, --panel, --panel-2, --text, --muted, --line and the rest. Build on them and both themes work without extra code.