Skip to main content

Building Plugin UI in the Core Shell

Your plugin renders its own pages inside the core's UI shell — the entity list page, the manage-entity page, and whatever domain-specific screens your operator needs. This is where your plugin becomes something an operator actually touches.

Your freedom, and what you're responsible for​

There's no mandatory component library and no enforced UI patterns beyond hitting the right API endpoints. The default plugin's UI is a reference to learn from, not a constraint. What you're responsible for:

  • the entity list page (e.g. ThingsPage.tsx) and the manage-entity page (e.g. ThingDetailPage.tsx), in plugins/{slug}/fe/pages/;
  • domain-specific drawers, forms, and components;
  • consuming core API endpoints for shared concerns;
  • a sidebar entry via manifest.menu.

The core endpoints your UI will call​

These are HTTP calls, not imports — your frontend calls them the same way it calls your own plugin endpoints:

  • Available workflows: GET /api/workspaces/:workspaceId/available-workflows — drives your workflow picker.
  • Start a workflow: POST /api/workflow-executions/start.
  • Execution history: GET /api/workflow-executions?workspaceId=....
  • Execution detail/logs: GET /api/workflow-executions/:id.

The workflow picker is a good UX pattern to copy: on page load it auto-fetches available workflows and renders nothing if there are none, a single "Run [workflow]" button if there's one, or a dropdown if there are several — the operator doesn't have to click to discover what they can run.

The API client pattern​

The default plugin uses a plain client module (fe/hooks/useDefaultPluginApi.ts) exporting async functions for both plugin and core endpoints — plain async functions, not stateful React hooks. The core doesn't prescribe a data-fetching approach; manage your own.

Route registration (currently manual)​

Plugin pages are registered with React Router via static imports in frontend/src/App.tsx, with routes matching your manifest.menu paths, and a sidebar entry in Layout.tsx:

import { ThingsPage } from '../../plugins/default_plugin/fe/pages/ThingsPage';
// <Route path="/things" element={<ThingsPage />} />

Adding a plugin means adding these import/route/sidebar entries by hand today — dynamic, manifest-driven frontend registration is planned but not yet in place.

One hard rule: no cross-plugin imports​

Each plugin is standalone. Plugin A must never import from Plugin B — including the default plugin, which is a boilerplate to copy, not a library to import from. If two plugins need the same component or hook, promote it to the core (frontend/src/); duplicating is acceptable, importing across plugins never is.