# Plugin frontend and Power SDK

> Add pages to the FeatherPanel sidebars, public pages and global scripts from a plugin, and use the window.FeatherPanel Power SDK and iframe bridge.

Source: https://featherpanel.com/docs/plugins/frontend/

A plugin has three ways to put something on screen:

- **Pages** in the client, admin, server, VDS or WebSpace sidebar, or at a public URL. Each page is an HTML file shown
  in an iframe.
- **Widgets** placed in named slots on existing pages. See [Widget slots](https://featherpanel.com/docs/plugins/widgets/).
- **Global code**, a script and a stylesheet loaded on every panel page.

Theme and UI packs, which restyle or replace parts of the panel, are covered in
[Themes and UI packs](https://featherpanel.com/docs/plugins/themes/).

## Sidebar pages

`Frontend/sidebar.json` has one object per sidebar. Keys are the page path relative to that sidebar.

```json
{
  "client": {
    "/demoshowcase": {
      "name": "Demo Showcase",
      "lucideIcon": "sparkles",
      "redirect": "/demoshowcase",
      "component": "/demoshowcase/dist/client-page.html",
      "description": "Example client page",
      "category": "client",
      "group": "Demo"
    }
  },
  "admin": {
    "/demoshowcase": {
      "name": "Demo Showcase",
      "lucideIcon": "puzzle",
      "component": "/demoshowcase/dist/admin-page.html",
      "category": "admin",
      "group": "Demo",
      "permission": "admin.plugins.view"
    }
  }
}
```

| Sidebar key | Page URL |
| --- | --- |
| `client` | `/dashboard/<path>` |
| `admin` | `/admin/<path>` |
| `server` | `/server/<uuidShort>/<path>` |
| `vds` | `/vds/<id>/<path>` |
| `webspace` | `/webspace/<uuidShort>/<path>` |

| Field | Notes |
| --- | --- |
| `name` | Label in the sidebar. |
| `lucideIcon` | A [Lucide](https://lucide.dev/icons/) icon name. |
| `component` | Path of the HTML file inside `Frontend/Components/`. It is served from `/components/<identifier>/`. |
| `redirect` | Path the entry opens, relative to the sidebar prefix. Defaults to the key. |
| `description` | Shown on hover and in search. |
| `category` | Same as the sidebar key. |
| `group` | Sidebar group. A built-in name puts the entry there: `management`, `files`, `networking`, `automation` or `configuration` in the server, VDS and WebSpace sidebars, or an admin group such as `users`, `infrastructure` or `system`. Any other name makes its own group. Without a group the entry goes to `plugins`. |
| `permission` | Permission node a user needs to see the entry. |
| `priority` | Order inside the group. In the server sidebar, built-in entries use steps of 100, for example Backups at 300 and Import at 400, so 350 lands between them. |

Admins can hide individual entries from users on the plugin's **Visibility** tab, and limit server entries to certain
spells on the **Spells** tab.

### What the page receives

The iframe URL gets query parameters you can read with `URLSearchParams`:

| Parameter | When |
| --- | --- |
| `serverUuid` | Server pages. |
| `vdsId` | VDS pages. |
| `webspaceUuid` | WebSpace pages. |
| `__theme` | Always: `light` or `dark`. |

If your `component` already contains `serverUuid=notFound` (or the VDS or WebSpace equivalent), the panel replaces the
placeholder instead of appending a new parameter.

## Public pages

`Frontend/public.json` adds pages outside the dashboard, for example a public status page:

```json
{
  "pages": [
    {
      "path": "/status-board",
      "name": "Status board",
      "component": "/myplugin/dist/status.html",
      "nav": { "label": "Status", "order": 50 }
    }
  ]
}
```

`path` and `component` are required. Paths that belong to the panel itself are reserved and ignored with a warning in
the log. `query` adds fixed query parameters, `fallbackPath` (default `/dashboard<path>`) is used when the page cannot
be shown, and `nav` puts a link in the public navigation, sorted by `order` (default 100).

## Global script and stylesheet

`Frontend/index.js` and `Frontend/index.css` from every installed plugin are joined and served at
`/api/system/plugin-js` and `/api/system/plugin-css`, and the panel loads both on every page. Each plugin's script is
wrapped in its own function, so top-level variables do not leak between plugins. The joined files are cached for two
minutes, so an edit can take that long to appear. Use them for small
additions: a keyboard shortcut, an action handler, a style fix. Code here runs in the panel window and can use the
Power SDK directly.

## The Power SDK

The panel exposes `window.FeatherPanel` (host API version 2) to code running in the host page.

| Namespace | Methods |
| --- | --- |
| `events` | `on`, `once`, `off`, `emit` |
| `actions` | `register`, `unregister`, `run` |
| `theme` | `getMode`, `getAccent`, `getPackId`, `subscribe` |
| `api` | `get`, `post`, `put`, `delete` (axios, with the user's session) |
| `toast` | `success`, `error`, `info`, `message` |
| `navigate` | `navigate(path)` |
| `context` | `getPathname`, `getUser`, `getUiPackId`, `subscribe` |
| `ui.modal` | `register`, `open`, `close` |
| `search` | `contribute`, `query` |
| `shortcuts` | `register` |

```js
// Frontend/index.js
window.FeatherPanel?.events.on('fp:server:power:result', (payload) => {
  console.log('Power action finished', payload);
});

window.FeatherPanel?.toast.info('My plugin is loaded');
```

### Events

The SDK declares these events on the event bus:

`fp:host:ready`, `fp:route:change`, `fp:page:mount`, `fp:page:unmount`, `fp:theme:change`, `fp:uipack:change`,
`fp:session:change`, `fp:server:context`, `fp:server:power:result`, `fp:server:console:ready`, `fp:files:saved`,
`fp:files:selection`, `fp:api:error`.

The current panel never emits `fp:files:selection`, and nothing runs the `fp:files:upload` action below. The
[Power SDK reference](https://featherpanel.com/docs/plugins/power-sdk/) lists every event and action with its payload and where it fires.

### Actions

Actions are named hooks that handlers can extend or cancel. The built-in ids are:

`fp:server:power`, `fp:files:save`, `fp:files:upload`, `fp:nav:push`, `fp:form:submit`, `fp:ui:confirm`,
`fp:api:request`, `fp:search:query`, `fp:shortcut:invoke`, `fp:data:action`.

Any element with a `data-fp-action` attribute runs that action on click. `data-fp-payload` takes JSON that is passed
to the handlers, and afterwards `fp:data:action` runs with the action id and payload:

```html
<button data-fp-action="myplugin:refresh" data-fp-payload='{"scope":"all"}'>Refresh</button>
```

## Talking to the panel from an iframe

Plugin pages and widgets run in iframes on the panel origin. They talk to the host with `postMessage`. The host only
accepts messages from its own origin.

```js
const host = window.parent;

// Emit an event on the host bus
host.postMessage(
  { type: 'featherpanel-bus', op: 'emit', event: 'myplugin:saved', payload: { id: 1 } },
  window.location.origin
);

// Subscribe to a host event
host.postMessage(
  { type: 'featherpanel-bus', op: 'on', event: 'fp:theme:change', subscriptionId: 'theme-1' },
  window.location.origin
);

window.addEventListener('message', (e) => {
  if (e.origin !== window.location.origin) return;
  if (e.data?.type === 'featherpanel-bus-event') {
    // e.data carries the event and payload
  }
  if (e.data?.type === 'featherpanel-bus-reply') {
    // acknowledgement for emit, on, off and runAction
  }
});
```

| `op` | Fields | Notes |
| --- | --- | --- |
| `emit` | `event`, `payload`, optional `requestId` | Emits on the host bus. |
| `on` / `off` | `event`, `subscriptionId` | Events arrive as `featherpanel-bus-event` messages. |
| `runAction` | `actionId`, `ctx`, `requestId` | Runs an action on the host and replies with the result. |
| `unregisterAction` | `actionId`, `handlerId` | Removes a handler. |
| `registerAction` | | Not supported from an iframe. Register handlers in `Frontend/index.js`. |

Because the page is same-origin, it can also call your [plugin routes](https://featherpanel.com/docs/plugins/routes-and-permissions/) with
`fetch('/api/addons/myplugin/...')`.

## Related

- [Widget slots](https://featherpanel.com/docs/plugins/widgets/)
- [Themes and UI packs](https://featherpanel.com/docs/plugins/themes/)
- [Examples](https://featherpanel.com/docs/plugins/examples/)

## Frequently asked questions

### Can a plugin page call the panel API?

Yes. Plugin pages are served from the panel's own origin, so a fetch to /api/... from the page carries the user's session. The request runs with that user's permissions.

### Can an iframe register an action handler?

No. Functions cannot cross postMessage, so the bridge refuses registerAction from an iframe. Register handlers in Frontend/index.js, which runs in the host page, and use emit or runAction from the iframe.
