Skip to content
ChatJS
Esc
↑↓navigate↵open⌘Jpreview
On this page

Create a plugin

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 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:

"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 for signatures and lifecycle guarantees.

3. Declare the installation

For example, MCP declares these contributions in chatjs.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.

Last updated on Nov 8, 2026

Was this page helpful?