Plugin manifest
A plugin describes itself entirely through one object: the PluginManifest. It declares what the plugin is — navigation, routes, widgets, data, permissions — and the runtime does the rest. Plugins declare; core resolves. This page is the tour of the fields; the exhaustive reference is in plugin manifest reference.
The required core
Four fields identify any plugin, plus the two seams that give it a place in the UI:
{
id: 'loyalty',
name: 'Loyalty',
icon: 'Puzzle',
version: '0.1.0',
navigation: [
{ section: 'main', position: 50, label: 'Loyalty', route: '/loyalty', icon: 'Puzzle' },
],
routes: [{ path: '/loyalty', component: Page }],
}
id— the plugin's unique identifier.name/icon/version— label, icon and version.navigation— the menu items (see Routing and navigation).routes— thepath → componentmapping.
A component can always be given directly (component) or by registry id (componentId) — exactly one of the two.
The extension seams
Beyond the core, the manifest has a family of optional fields — each one a "seam" through which the plugin extends the platform without touching any other plugin:
| Seam | What it declares |
|---|---|
settings | Tabs on the plugin's settings screen. |
widgets | Components injected into named zones of the shell (WidgetZone). |
dashboardWidgets | KPIs, charts and tables the plugin contributes to the dashboard. |
events | Events the plugin emits on the bus (agenda.booking.confirmed, …). |
aiTools | Tools an AI agent can call (read or persist mode). |
capabilities | The plugin's declared capabilities (for introspection). |
entities | Data entities the plugin registers for CRUD. |
permissions / declaredFeatures | Features and actions for access control. |
connectors | Connectors for external providers (the plugin as an add-on to another). |
migrations | The SQL for the tables the plugin owns (plg_ convention), each with tenant_id + RLS. |
diagnostics | Backend prerequisites that fayz doctor checks (RPCs, views, tables, env). |
onboarding | A first-run flow. |
locales | Translations (en, pt-BR, …). |
You declare only what you use. A minimal plugin — like the one the incubator generates — ships id, name, icon, version, navigation and routes, and nothing else.
apiVersion: the contract is versioned
The optional apiVersion field pins the plugin contract version the manifest was written against. The runtime refuses a plugin built for a contract newer than it supports — that's the mechanism that stops a plugin from a future generation from running half-broken on an older platform. Omitting it means "compatible/legacy".
Why declarative
The manifest being data (and not imperative code) is what gives you the three properties the platform rests on:
- Introspection — the platform knows exactly what a plugin contributes without executing it; that's how
fayz doctorchecks prerequisites and the editor builds its pickers. - Symmetry — an app-local plugin and an official one use the same type, so promoting one is packaging, not rewriting.
- Upgrade safety — the seams are a versioned contract; the implementation behind them can change freely.
Next: the conventions that keep a plugin well-behaved, in Patterns.