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), inplugins/{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.