@veap/plugins renders plugin-contributed UI through two mechanisms: extension points (a named slot that a plugin targets with a component) and widget areas (a dashboard-style region whose widgets a user can arrange). Both are declared by plugins and resolved by the PluginRegistry, filtered by the current user's roles and permissions.
Declaring contributions#
Plugins register their UI on the IPlugin definition (see the plugin contract):
const plugin: IPlugin = {
manifest,
extensions: [
{
id: "blog-setup-dialog",
target: "panel",
point: "plugin-setup-dialogs",
component: BlogSetupDialog,
priority: 10,
roles: ["admin"],
},
],
widgets: [
{
id: "blog-stats",
name: "Blog Stats",
area: "dashboard-stats",
component: BlogStatsWidget,
priority: 20,
defaultColSpan: 2,
defaultRowSpan: 1,
roles: ["admin"],
},
],
};PluginExtension:id,target,point,component, optionalpriority,metadata,roles,permissions.PluginWidget:id,name,area,component, optionalpriority,defaultColSpan,defaultRowSpan,roles,permissions.
Items without a priority sort as 100. Role/permission annotations are applied by the registry filter, but they are not a substitute for server-side authorization of the data or actions a component performs.
Extension points (server)#
PluginExtensionPoint (alias ExtensionPoint) is an async Server Component that resolves and renders the components registered for a target/point.
import { ExtensionPoint } from "@veap/plugins/server";
<ExtensionPoint target="app" point="navbar" mode="single" fallback={<DefaultNavbar />} />;| Prop | Type | Notes |
|---|---|---|
target | string | Required. The host surface, e.g. "app", "panel". |
point | string | Required. The named slot within the target. |
mode | "single" | "multiple" | Default "multiple". single renders only the highest-priority component. |
className | string | Applied to the wrapper element. |
props | any | Spread onto each rendered component. |
fallback | ReactNode | Rendered when no component matches. |
children | ReactNode | Alternative fallback (takes precedence over fallback). |
as | ElementType | Wrapper element; defaults to div when className is set. |
includeDisabled | boolean | Default false; when true, disabled plugins are also scanned. |
The server component waits for an in-flight bootstrap promise, reads the current user's roles/permissions, and returns the fallback when the plugins context is not bound yet. It renders extensions[0] in single mode.
Widget areas (server)#
PluginWidgetArea (alias WidgetArea) is an async Server Component that renders all widgets registered for an area, with the same user filtering.
import { WidgetArea } from "@veap/plugins/server";
<WidgetArea area="dashboard-stats" className="grid" fallback={<Empty />} />;Props: area (required), className, props, fallback, as (default div).
Widget composer (server)#
WidgetComposer renders a user-arrangeable widget grid for a slot. It loads the default widgets for the slot, reads the user's saved layout (see the getUserWidgetsState action), and hands both to the client composer.
import { WidgetComposer } from "@veap/plugins/server";
<WidgetComposer slot="dashboard-main" columns={4} fallback={<Empty />} />;Props: slot (required), columns (default 4), fallback.
The client counterpart is WidgetComposerClient; see Client hooks and components for the interactive drag/resize editor and how layouts are saved.
Resolution behavior#
Both PluginExtensionPoint and PluginWidgetArea resolve through PluginRegistry.getExtensions() / getWidgets(), which:
- scan enabled plugins only (unless
includeDisabledis set), - apply role/permission filtering against the current user,
- sort by
priority(default100).
The area/slot data is loaded on every render; the widgets are not cached across requests by these components.
Client-side rendering#
If a slot must re-render when a plugin is toggled at runtime without a page reload, use the client variants (ExtensionPointClient, WidgetAreaClient, usePluginExtensions, usePluginWidgets) described in Client hooks and components. They fetch the same data through server actions and subscribe to onPluginsChanged.