View .md

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.

On this page

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.
  • 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.

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

{
  "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 keyPage URL
client/dashboard/<path>
admin/admin/<path>
server/server/<uuidShort>/<path>
vds/vds/<id>/<path>
webspace/webspace/<uuidShort>/<path>
FieldNotes
nameLabel in the sidebar.
lucideIconA Lucide icon name.
componentPath of the HTML file inside Frontend/Components/. It is served from /components/<identifier>/.
redirectPath the entry opens, relative to the sidebar prefix. Defaults to the key.
descriptionShown on hover and in search.
categorySame as the sidebar key.
groupSidebar 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.
permissionPermission node a user needs to see the entry.
priorityOrder 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:

ParameterWhen
serverUuidServer pages.
vdsIdVDS pages.
webspaceUuidWebSpace pages.
__themeAlways: 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:

{
  "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.

NamespaceMethods
eventson, once, off, emit
actionsregister, unregister, run
themegetMode, getAccent, getPackId, subscribe
apiget, post, put, delete (axios, with the user’s session)
toastsuccess, error, info, message
navigatenavigate(path)
contextgetPathname, getUser, getUiPackId, subscribe
ui.modalregister, open, close
searchcontribute, query
shortcutsregister
// 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 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:

<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.

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
  }
});
opFieldsNotes
emitevent, payload, optional requestIdEmits on the host bus.
on / offevent, subscriptionIdEvents arrive as featherpanel-bus-event messages.
runActionactionId, ctx, requestIdRuns an action on the host and replies with the result.
unregisterActionactionId, handlerIdRemoves a handler.
registerActionNot supported from an iframe. Register handlers in Frontend/index.js.

Because the page is same-origin, it can also call your plugin routes with fetch('/api/addons/myplugin/...').

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.