# 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](/en/docs/reference/plugin-manifest).

## The required core

Four fields identify any plugin, plus the two seams that give it a place in the UI:

```ts
{
  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](/en/docs/apps/routing-and-navigation)).
- **`routes`** — the `path → component` mapping.

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 doctor` checks 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](/en/docs/building-plugins/patterns).
