Frontend plugin development¶
This guide explains how to implement and register a new frontend plugin in the application. It is the frontend counterpart to the backend plugin development guide.
The plugin system is designed to be:
- Modular
- Lazy-loaded
- Route-based
All paths in this guide are relative to the
frontend/directory.
Plugin Directory¶
All plugins live under:
plugins/
1. Defining a new plugin¶
Create a new folder using the plugin’s kebab-case name:
plugins/<plugin-name>/
Example:
plugins/example-plugin/
2. Create the Plugin UI Component¶
Inside the new plugin directory, create a React component that represents the plugin’s UI.
File
plugins/<plugin-name>/<PluginName>.tsx
Example:
"use client";
export default function ExamplePlugin() {
return (
<div className="p-6">
<h1 className="text-xl font-semibold">Example Plugin</h1>
{/* Plugin UI goes here */}
</div>
);
}
Notes
- Must be a client component
- Can use plugin context via
usePlugins()orcreatePluginContext - This component is lazy-loaded
3. Define the Plugin (index.ts)¶
Each plugin must export a PluginDefinition from its own index.ts.
Example Plugin Structure
plugins/
└─ chat/
├─ ChatInterface.tsx
└─ index.ts
File
plugins/<plugin-name>/index.ts
Example
import { PluginDefinition } from "@/lib/plugins";
import { MessageSquare } from "lucide-react";
export const examplePlugin: PluginDefinition = {
name: "example-plugin",
displayName: "Example Plugin",
description: "Example plugin integration",
isCore: true,
loadComponent: () => import("./ExamplePlugin"),
showInSidebar: true,
sidebarIcon: MessageSquare,
sidebarLabel: "Example",
sidebarOrder: 1,
};
Note
At a minimum, a plugin must define:
name– unique plugin identifier (must match folder and route, and the backend plugin's derived name — see "Plugin Name Derivation" in the backend plugin development guide — so the UI resolves to the right backend plugin)displayName– user-facing namedescription– short descriptionloadComponent– lazy-loaded UI component
For more details, see the PluginDefinition type in
frontend/lib/plugins/types.ts.
4. Publicly Export the Plugin¶
Expose the plugin from the plugins barrel file.
File
plugins/index.ts
export * from "./chat"; // already existing
// your plugin export goes here
export * from "./example-plugin";
This makes the plugin available for registration.
5. Register the Plugin¶
Register the plugin with the plugin registry.
File
@/lib/plugins/index.ts
import { registerPlugin } from "./registry";
import { chatPlugin, examplePlugin } from "@/plugins";
registerPlugin(chatPlugin); // already existing
// your plugin registration goes here
registerPlugin(examplePlugin);
export * from "./registry";
export * from "./types";
export * from "./usePlugins";
⚠️ If a plugin is not registered here, it will not load, not render, and not appear in the sidebar.
6. Add the Plugin Route¶
Each plugin is rendered under:
dashboard/<pluginName>
Add a dynamic route for the plugin.
File
app/dashboard/[pluginName]/page.tsx
import PluginPageClient from "./page-client";
export function generateStaticParams() {
return [
{ pluginName: "chat" },
{ pluginName: "example-plugin" } // register your route here
];
}
export default function PluginPage() {
return <PluginPageClient />;
}
Notes
pluginNamemust match the plugin namePluginPageClienthandles loading the correct plugin dynamically- Required for static builds
Plugin Loading Flow (Summary)¶
plugins/<plugin>
↓
PluginDefinition
↓
registerPlugin()
↓
Plugin registry
↓
Sidebar + routing
↓
Dynamic import
↓
Rendered plugin UI
Troubleshooting¶
Plugin not showing?
- Ensure it’s exported from
plugins/index.ts - Ensure it’s registered in
lib/plugins/index.ts
Plugin page blank?
- Check
loadComponentpath - Ensure component is a default export
- Ensure route param matches plugin name