Skip to content

hello-panel — the ui-only example ​

The smallest complete plugin: a static frontend and nothing else. Source: examples/hello-panel.

hello-panel/
  manifest.json
  frontend/index.html

The manifest ​

json
{
  "manifest_version": 2,
  "name": "hello-panel",
  "version": "1.0.0",
  "api_version": "^1.1",
  "author": "UnCorded",
  "description": "The canonical ui-only plugin: a static frontend, no backend.",
  "license": "MIT",
  "type": "standalone",
  "icon": "PanelTop",
  "runtime": { "kind": "ui-only" },
  "frontend": { "entry": "frontend/index.html" },
  "permissions": []
}

The two runtime kinds ​

runtime.kindWhat runs
ui-onlyThe runtime serves frontend/ as static files to a sandboxed iframe. No process starts.
sandboxed-backendAdditionally runs bun --smol run <backend.entry> inside the seccomp + Landlock jail. See noteboard.

runtime.kind is inferred when omitted — a backend block means sandboxed-backend, frontend-only means ui-only — so existing plugins need no edit. This example spells it out because it is the pattern to copy.

It is orthogonal to type (core / standalone / extension), which describes how a plugin relates to other plugins, not what its own code is.

What a ui-only plugin cannot do ​

It cannot contribute sidebar items. Sidebar entries are served by the backend's sidebar.items handler over plugin IPC; with no backend there is no process to answer. Declaring sidebar.contributes: true without a backend is rejected at validation (SIDEBAR_REQUIRES_BACKEND) rather than silently producing an empty section.

The same goes for anything else that needs a server-side handler: data, events, proxy mounts, settings that the backend reads. Reach for sandboxed-backend when you need those.

Still do the handshake ​

A panel with no backend still talks to the shell:

html
<script src="/sdk/plugin-frontend.js"></script>
<script>
  window.UncodedPlugin.createPluginFrontend().catch((err) => {
    console.warn("[hello-panel] no shell handshake:", err);
  });
</script>

Two things depend on it. The handshake is what announces the panel as ready — without it the shell shows an "unreachable" overlay after 30 seconds, even though the page rendered fine. And it is what applies the resolved theme tokens, so a script-free page reads its CSS fallbacks and stays dark in Light mode.

Packaging consequence ​

A ui-only plugin ships zero node_modules. Frontend build inputs — a bundler, React, a diagramming library — are build-time only: put them in devDependencies, keep sources in frontend-src/, and ship only the built output under frontend/. That distinction is most of the difference between a 300 MB package and a 23 MB one.

Run it ​

Install it into a server from the desktop app's Plugin Dev workspace, or open frontend/index.html directly in a browser — the theme tokens fall back to sensible defaults outside the shell, so it renders standalone while you iterate.