> ## Documentation Index
> Fetch the complete documentation index at: https://docs.myrax.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Quickstart

> A plugin with its own panel tab, from zero

A plugin is a directory with a manifest and a JS entry module. This page gets
you from nothing to a working sidebar tab, then adds a backend.

## 1. Create the plugin

```text theme={null}
hello/
├── myrax.plugin.toml
└── web/
    ├── plugin.js
    └── plugin.css
```

```toml myrax.plugin.toml theme={null}
id = "hello"
name = "Hello"
version = "0.1.0"
myrax = ">=0.1.0"

[ui]
mode = "native"
entry = "web/plugin.js"
styles = ["web/plugin.css"]
```

## 2. Add a tab

The panel imports your entry as an ES module and calls `register(myrax)`.
`sidebar.add` creates the tab; `mount` is called when the user opens it and
must return a cleanup function.

```js web/plugin.js theme={null}
export async function register(myrax) {
  myrax.sidebar.add({
    value: 'hello',
    label: 'Hello',
    icon: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"
      stroke-width="2"><circle cx="12" cy="12" r="9"/></svg>`,
    mount(node) {
      node.innerHTML = `
        <section class="hello-card">
          <h2>Hello from a plugin</h2>
          <button class="hello-button" type="button">toast</button>
        </section>
      `;
      const button = node.querySelector('.hello-button');
      const onClick = () => myrax.toast({
        title: 'Hello',
        message: 'It works.',
        tone: 'success',
        timeout: 3000
      });
      button.addEventListener('click', onClick);
      return () => button.removeEventListener('click', onClick);
    }
  });
}
```

```css web/plugin.css theme={null}
.hello-card {
  background: var(--panel);
  border-radius: 26px;
  padding: 24px;
}
```

Plugin CSS gets the panel's design tokens for free — `var(--panel)`,
`var(--text)`, `var(--bg)` and friends follow the active theme.

## 3. Install and look at it

On the server (plugin system must be on — `myrax add-ons enable`):

```sh theme={null}
myrax plugin install /path/to/hello
```

Reload the panel: the **Hello** tab is in the sidebar, the button fires a
toast. To iterate, edit the files and reinstall — the panel cache-busts
assets automatically.

## 4. Add a backend (optional)

Frontends are sandboxed to the browser. Anything that needs the server — a
process, files, another API — goes in a runtime: a process the panel starts,
supervises and proxies for you.

Declare it in the manifest:

```toml myrax.plugin.toml theme={null}
[runtime]
enabled = true
command = "/usr/bin/env"
args = ["node", "runtime/server.js"]
transport = "tcp"
port = 0          # 0 = panel picks a free port
```

Write a tiny HTTP server that binds the port the panel hands it:

<CodeGroup>
  ```js runtime/server.js theme={null}
  const http = require('http');

  const port = Number(process.env.MYRAX_PLUGIN_PORT);

  http.createServer((req, res) => {
    if (req.url === '/health') {
      res.writeHead(200, { 'content-type': 'application/json' });
      return res.end('{"ok":true}');
    }
    if (req.url === '/api/time') {
      res.writeHead(200, { 'content-type': 'application/json' });
      return res.end(JSON.stringify({ now: new Date().toISOString() }));
    }
    res.writeHead(404);
    res.end();
  }).listen(port, '127.0.0.1');
  ```

  ```go runtime/main.go theme={null}
  package main

  import (
  	"encoding/json"
  	"net/http"
  	"os"
  	"time"
  )

  func main() {
  	port := os.Getenv("MYRAX_PLUGIN_PORT")

  	http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
  		w.Header().Set("Content-Type", "application/json")
  		w.Write([]byte(`{"ok":true}`))
  	})
  	http.HandleFunc("/api/time", func(w http.ResponseWriter, r *http.Request) {
  		w.Header().Set("Content-Type", "application/json")
  		json.NewEncoder(w).Encode(map[string]string{
  			"now": time.Now().UTC().Format(time.RFC3339),
  		})
  	})

  	http.ListenAndServe("127.0.0.1:"+port, nil)
  }
  ```
</CodeGroup>

Call it from the frontend through the panel's proxy — same origin, same auth:

```js web/plugin.js theme={null}
const res = await fetch('/api/plugins/hello/proxy/api/time');
const { now } = await res.json();
```

Reinstall, and the panel starts the runtime, restarts it with the plugin and
writes its output to `myrax plugin logs hello`.

## Next

* [Manifest](/plugins/dev/manifest) — every field and its default.
* [Frontend API](/plugins/dev/frontend) — the full `myrax` object.
* [Runtime](/plugins/dev/runtime) — environment, transports, lifecycle.
* [Publishing](/plugins/dev/publishing) — shipping from a git repo.
