---
title: Create a plugin
description: Package source and register server, browser, and native contributions
---

Publish a standard shadcn registry item with `meta.chatjs.kind: "plugin"`. Plugin IDs use lowercase letters, digits, and hyphens, beginning with a letter. External plugins use the same metadata and sync path as the built-ins.

## Prerequisites

Use a working ChatJS app and a shadcn registry. Read [Using plugins](./using-plugins) before packaging a new capability.

## 1. Organize the source

In this repository, keep a plugin's installation manifest in `packages/registry/src/plugins/<id>/manifest.ts`, its implementation in `src/`, and its framework entrypoints in `adapters/`. Map implementation files into `~/chatjs/plugins/<id>/`. Map thin Next and EVE entrypoints into their required `app/` and `agent/` locations. List every distributed file explicitly. Tests and authoring metadata stay in the repository.

`server-plugin.ts` and `client-plugin.ts` use distinct names to avoid shadcn import resolution collisions with host modules such as `lib/db/client.ts` and `trpc/server.ts`.

## 2. Define the integrations

A server factory returns an object satisfying `ChatPlugin` from `@/chatjs/core/installation/plugin`. It can contribute typed tRPC `routers` and an `instrumentation` registration. Keep the inferred router types with `satisfies`.

A client entry exports a module-scope `definePlugin` declaration. Bind components to typed slots:

```tsx
"use client";

import { ComposerExtensions, SettingsLinks } from "@/components/chat-slots";
import { definePlugin, slot } from "@/chatjs/core/ui/slots";
import { ConnectorsControl } from "./composer";
import { ConnectorsSettingsLink } from "./settings";

export const mcpClient = definePlugin({
  id: "mcp",
  slots: [
    slot(ComposerExtensions, ConnectorsControl),
    slot(SettingsLinks, ConnectorsSettingsLink),
  ],
});
```

The built-in MCP server factory is `mcp()`. Its client declaration is `mcpClient`, without a factory call. Keep credentials, database imports, and server implementations outside its import graph. Edit the bound components directly, or register normal React wrappers. Dynamic imports and Suspense can defer expensive components.

Core keeps explicit optional fields such as `projectId`. Plugins validate them against their own storage and lifecycle behavior. A separate runtime-safe `chat.ts` entrypoint participates in transactional creation, history, navigation, and deletion. See [Plugin integration points](../reference/plugin-api) for signatures and lifecycle guarantees.

## 3. Declare the installation

For example, MCP declares these contributions in `chatjs.json`:

```json
{
  "contractVersion": 1,
  "id": "mcp",
  "kind": "plugin",
  "server": { "export": "mcp", "routers": ["mcp"] },
  "client": {
    "export": "mcpClient",
    "composerControls": ["mcp"],
    "settingsItems": ["mcp"]
  }
}
```

The full manifest also declares `files`, `databaseSchemas`, `envRequirements`, and `environmentValidation`. The registry build writes the descriptor beside the installed implementation. Router keys must be unique and cannot replace core routers. Composer and settings IDs must be unique across plugins. The composer IDs `canvas`, `web-search`, `deep-research`, `image`, and `video`, and settings ID `general`, are reserved for the host. Only one plugin can supply uploads.

Use `registryDependencies` to install another item and `requiresPlugins` to require its plugin ID in the installed selection. Declare `requiresStorage: true` for plugins requiring durable file storage.

Database schema paths must belong to `chatjs/plugins/<id>/`. Sync composes them for Drizzle through `chatjs/generated/installed-database.ts`. Adding or syncing a plugin preserves migration history and does not apply SQL. Environment readers belong to the plugin and remain separate from client-visible configuration.

## 4. Install and verify

Run `chat-js sync` after a direct shadcn installation. Sync discovers plugin directories, validates descriptors and required source, checks contribution collisions, and then generates registrations. It reads metadata without importing server factories or requiring their runtime credentials.

`chat-js add` and `sync` update generated installed-plugin lists. They preserve plugin component edits and application JSX. Move slots or add local components in the layout to change placement. Within a slot, contributions follow the installed list and declaration order.

Verify both an app without the plugin and an app with it installed. Exercise the plugin's routes and UI, and check that client entrypoints cannot reach server-only code. In this repository, edit canonical source, run `bun demo:sync`, and commit the generated reference copies and mirror manifest with it.
