plugin.json Manifest Reference
The plugin.json file is the only required file in a plugin. It declares what the plugin provides and how Recursive should load it.
New plugins must declare manifestFormat: 2 and use grouped v2 keys (runtime, inference, hooks, storage, sync, auth, tools, triggers). The registry loader normalizes grouped keys to the flat fields documented below. See the repo-root VISION.md for the canonical capability taxonomy.
Minimal manifest
Section titled “Minimal manifest”{ "manifestFormat": 2, "id": "my-plugin", "name": "My Plugin", "version": "0.1.0", "description": "What this plugin does."}Complete schema
Section titled “Complete schema”Unknown fields are ignored with a warning in the plugin’s health panel (typos
like skilss get a did-you-mean suggestion); wrong-typed fields are dropped
with a warning. Only a missing id prevents the plugin from loading.
Validate anytime with the plugin_validate tool.
Identity & metadata
Section titled “Identity & metadata”| Field | Type | Required | Description |
|---|---|---|---|
$schema | string | No | Agent Plugins schema URL. Present on spec-conformant manifests; ignored by Recursive beyond format detection. |
manifestFormat | number | No | Must be 2 for new manifests. Grouped v2 keys are normalized to flat registry fields at load time. |
id | string | Yes | Unique kebab-case plugin identifier. |
name | string | No | Display name (falls back to id). |
version | string | No | Semver version, compared against the registry for update checks. |
description | string | No | One-line summary shown in the plugins UI and registry. |
author | string | object | No | Author attribution — a string or { name, url }. |
homepage | string | No | Project homepage or docs URL. |
repository | string | No | Source repository URL (Agent Plugins metadata). |
license | string | No | SPDX license identifier (Agent Plugins metadata). |
keywords | string[] | No | Discovery keywords (Agent Plugins metadata); merged with tags in the marketplace. |
color | string | No | Colour for the icon badge and any mode the plugin contributes. A hex literal, or a theme token such as var(—yellow) to follow the active theme. |
icon | string | No | Icon name from the Recursive icon set. |
thumbnail | string | No | Path to a thumbnail image within the plugin. |
category | string | No | Marketplace category. |
tags | string[] | No | Marketplace search tags. |
appScope | string | No | Legacy app/domain label used for discovery and grouping; it never gates plugin installation or provider boot. |
private | boolean | No | Excluded from registry publishing. |
platforms | string[] | No | OS allowlist (e.g. [“darwin”]); omit for all platforms. |
min_recursive_version | string | No | Minimum Recursive version this plugin supports; enforced at install. |
extensions | object | No | Agent Plugins client extensions, keyed by reverse-domain namespace. Recursive reads “org.recursive”. |
Capability paths
Section titled “Capability paths”Each points to a directory or file (relative to the plugin root) that Recursive loads. A string can be a directory (ending in /) or a single file; an array mixes both. On v2 manifests, use grouped keys such as runtime.operations, inference.adapters.modules, and tools.mcp — they normalize to these flat fields at load time. New product capabilities declare operations; MCP tools project from those contracts. tools.handlers is a legacy compatibility surface.
| Field | Type | Required | Description |
|---|---|---|---|
tools | string | string[] | No | Legacy MCP handler modules. New product capabilities use runtime.operations with mcp.exposed. |
routes | string | string[] | No | HTTP route handler modules. |
operations | string | string[] | No | Canonical OperationDefinition modules contributed to the product runtime. |
migrations | string | No | Directory of one-time data migrations ({ id, run }) applied before this plugin’s operations load. Each id is ledgered once per workspace. |
adapters | string | string[] | No | Agent CLI adapter modules (JS/TS). |
skills | string | string[] | No | Skill directories containing SKILL.md subfolders. |
rules | string | string[] | No | Agent rule markdown files. |
commands | string | string[] | No | Slash-command Markdown files. |
automations | string | string[] | No | Automation definitions. |
hooks | string | string[] | No | IDE-native hook JSON path (v2: hooks.ide). Normalized from grouped hooks.ide; not server lifecycle — use hooks.server → lifecycle after normalization. |
mcp | string | string[] | No | External MCP server configuration (JSON). |
themes | string | string[] | No | Theme JSON files. |
sounds | string | string[] | No | Sound pack audio files (mp3/wav/ogg/m4a/aac/aiff). |
greetings | string | string[] | No | Session-greeting catalog JSON files. |
providers | string | string[] | No | Inference-provider catalog JSON. Each approved row becomes a connectable OpenAI-compatible provider, with no adapter code. |
comms | string | string[] | No | Comms templates directory. |
Behavior blocks
Section titled “Behavior blocks”| Field | Type | Required | Description |
|---|---|---|---|
ui | object | No | UI extensions: views, panels, commands, chat renderers, components. |
settings | object | No | Settings schema and defaults rendered in the plugin settings page. |
detect | object | No | Project auto-detection: file patterns that suggest enabling this plugin. |
data | object | No | Logical collections and document stores with device, user, workspace, or project scope plus schema, sync, indexes, and quotas. |
dataset | object | No | Portable dataset semantics: declared storage collections plus field roles for generic platform readers. |
entities | object[] | No | Logical entity providers contributed by this plugin. Each declaration binds an entity type to one declared collection or document store and scope; referenceResolver maps ids to human-readable labels through a read operation. |
consumes | object | No | Kernel or cross-plugin operations this plugin may invoke from contributed server operations. |
provides | object | No | Structured provider declarations (sync_provider, auth_provider, data_provider) — each is { id, name, entry, config_schema }; auth_provider contributes a pluggable identity/permission backend (see core-identity provider-registry). |
agent | object | No | Agent catalog contributions: optional promoted Skill mode bindings and named agent templates. |
triggers | object | No | Declares the plugin as an event trigger source: module (exports verify/normalize, plus optional respond), optional channelAdapter (provider-neutral durable conversation transport), a non-empty events[] of { type, label, filters, firehose? }, optional setupSkill, setupInstructions, and requiredSettings. |
computerProvider | object | No | Isolated computer lifecycle provider normalized from computer.provider. |
remoteAccess | object | No | Provider-owned remote connectivity. Core owns authentication and dispatch. |
channelSetup | object | No | Channel-instance setup guidance: title, description, guideUrl, and optional guideLabel. |
channelConnection | object | No | Channel connection contract. managedOAuth declares a hosted gateway provider, authorization URL, scopes, identity response paths, and connection-setting mappings. |
pluginAccounts | object | No | Declares which settings keys make up one account so the platform can store, switch, and forget several of them. OAuth plugins get this for free from pluginOAuth. |
pluginOAuth | object | No | Direct plugin OAuth contract. The platform starts authorization, exchanges the callback code, stores token settings, and optionally resolves the connected identity. |
lifecycle | object | No | Server lifecycle hook handlers keyed by event name (normalized from hooks.server). |
client_lifecycle | object | No | Client (renderer) lifecycle hook handlers keyed by event name (normalized from hooks.client). |
soundDefaults | Record<string, string> | No | Sound packs: notification event → sound slug mapping. |
dependencies | string[] | No | Plugin ids this plugin requires. |
bundledOptIn | boolean | No | Bundled copy is marketplace-only until explicitly installed. |
npm | object | No | npm install behavior — set { “scripts”: true } to allow lifecycle scripts (disclosed at install). |
Terminal actions, onboarding & adapter contract
Section titled “Terminal actions, onboarding & adapter contract”| Field | Type | Required | Description |
|---|---|---|---|
loginCommand | string | No | Command that (re)authenticates the plugin’s CLI. |
setupCommand | string | No | First-time configuration command. |
diagnoseCommand | string | No | Health-check / repair command. |
installCommand | string | No | Command that installs the CLI itself. |
mcpLoginCommand | string | No | Authenticate one downstream MCP server through this CLI. Supports a {{server}} placeholder. |
mcpDiagnoseCommand | string | No | Inspect a downstream MCP server’s connection state. Supports a {{server}} placeholder. |
installUrl | string | No | Docs or download URL for installing the CLI. |
cli | object | No | How to probe whether the CLI is installed (bin, versionArgs). |
app | object | No | Desktop-app provider config (name, downloadUrl, healthUrl). |
setupSequence | object[] | No | Ordered getting-started steps shown after install. |
onboarding | object | No | Declarative onboarding steps (fields, verify, links, markdown instructions). Trigger-source plugins also get platform-generated copy-URL and await-event steps appended automatically. |
adapterManifest | object | No | Universal adapter runtime contract: capabilities, cli, bridge, instructionDelivery, mcpDelivery, configDelivery, logNoise. |
ui — Dashboard extensions
Section titled “ui — Dashboard extensions”The ui field is an object that declares frontend components and behaviors.
ui.views
Section titled “ui.views”Array of view definitions that add new pages to the dashboard.
{ "ui": { "views": [ { "id": "my-view", "label": "My View", "icon": "layers", "route": "my-view", "component": "./ui/MyView.svelte", "detailPanel": "./ui/MyDetailPanel.svelte" } ] }}| Field | Type | Description |
|---|---|---|
id | string | Unique view identifier. |
label | string | Display name in navigation. |
icon | string | Icon name. |
route | string | URL route segment (e.g., my-view → /my-view). |
component | string | Path to the main Svelte component. |
detailPanel | string | Optional path to a detail panel component. |
ui.nav
Section titled “ui.nav”Array of navigation entries added to the sidebar.
{ "ui": { "nav": [ { "route": "my-view", "label": "My View", "icon": "layers", "shortcut": "Mod+5", "order": 50, "surface": "sidebar.nav.my-view" } ] }}ui.commands
Section titled “ui.commands”Array of command palette entries.
{ "ui": { "commands": [ { "id": "nav-my-view", "label": "Go to My View", "icon": "layers", "section": "Navigation", "type": "navigate", "route": "my-view", "shortcut": "Mod+5", "keywords": ["custom", "view"] } ] }}ui.panels
Section titled “ui.panels”Panels added to the workspace detail area (alongside file viewer, preview, etc.).
ui.taskPanels
Section titled “ui.taskPanels”Panels added to the task detail view.
ui.chatRenderers
Section titled “ui.chatRenderers”Custom renderers for specific tool call results in the chat feed.
{ "ui": { "chatRenderers": [ { "kind": "goal_create", "component": "./ui/GoalCreateRenderer.svelte" } ] }}ui.entityTypes
Section titled “ui.entityTypes”Register custom entity types for mention resolution and search.
{ "ui": { "entityTypes": [ { "type": "goal", "storeKey": "allGoals", "nameKey": "title" } ] }}ui.sseEvents
Section titled “ui.sseEvents”SSE event channels that the plugin’s store should listen to.
Stores
Section titled “Stores”Frontend stores are discovered by convention, not declared: any
ui/*-store.ts module in a bundled plugin is auto-registered, its
load<Name>() export is called on mount, and SSE events matching the plugin
id (or declared via ui.sseEvents) re-invoke it.
ui.components
Section titled “ui.components”Map of named components that other parts of the UI can reference.
settings
Section titled “settings”Plugin configuration with defaults and a schema for the settings UI.
{ "settings": { "defaults": { "api_key": "", "enabled_features": ["basic"] }, "schema": [ { "key": "api_key", "type": "string", "label": "API Key", "description": "Your service API key.", "section": "Authentication" }, { "key": "enabled_features", "type": "multi-select", "label": "Features", "options": ["basic", "advanced", "experimental"], "section": "Features" } ] }}detect
Section titled “detect”Auto-detection rules that enable the plugin when a project matches.
{ "detect": { "files": ["package.json", "tsconfig.json"], "strategy": "all" }}| Field | Type | Default | Description |
|---|---|---|---|
files | string[] | — | File patterns to check in the project root. |
strategy | "any" | "all" | "any" | Whether any file match suffices or all must be present. |
lifecycle
Section titled “lifecycle”Server-side lifecycle event handlers. Keys are event names, values define the handler.
{ "lifecycle": { "session.complete": { "handler": "./hooks/on-session-complete.ts", "priority": 10, "type": "action" } }}client_lifecycle
Section titled “client_lifecycle”Client-side lifecycle event handlers. Same structure as lifecycle but handlers run in the browser.
{ "client_lifecycle": { "session.complete": { "handler": "./hooks/client-on-complete.ts", "priority": 10 } }}triggers
Section titled “triggers”Declares the plugin as an event trigger source for event-mode automations. See Building a Trigger Plugin for the full contract. When the plugin also supports bidirectional conversations, add channelAdapter and the channel manifest fields — see Building a Channel Plugin.
{ "triggers": { "module": "./triggers/index.js", "channelAdapter": "./channel/index.js", "setupSkill": "connecting-my-provider", "requiredSettings": ["signing_secret"], "events": [ { "type": "issue.created", "label": "Issue created", "filters": ["project"] } ] }}| Field | Required | Description |
|---|---|---|
module | Yes | Path to the file exporting verify(rawBody, headers, connection) and normalize(payload, headers, connection). Without it the whole block is dropped (with a warning). |
channelAdapter | No | Path to a module exporting createAdapter(settings) that returns a ChannelAdapter. Enables durable two-way conversations — distinct from one-shot event automations. |
events | Yes | Non-empty array of { type, label, description?, filters?, firehose? }. type (non-empty string) is matched against automations’ event_type; label is shown in the automation editor. A zero-event source is skipped by the trigger catalog — the validator warns about it. |
setupSkill | No | Skill id that walks the user through connecting the provider. Should correspond to a skill this plugin ships — plugin_validate warns when skills/<name>/SKILL.md is missing. |
setupInstructions | No | Markdown checklist of provider-side setup steps. Rendered as a platform-generated onboarding step between copy-URL and await-event. |
requiredSettings | No | Settings keys (from settings.schema) that must be non-empty for the source to show as “Connected”. Defaults to every password-type setting. |
channelSetup
Section titled “channelSetup”Human-readable guidance shown when a user configures a channel instance in settings.
{ "channelSetup": { "title": "Get your credentials", "description": "Create a provider app, install it in your workspace, then copy its signing secret and bot token.", "guideUrl": "https://api.example.com/apps", "guideLabel": "Open setup guide" }}| Field | Required | Description |
|---|---|---|
title | Yes | Short heading for the setup card. |
description | Yes | One or two sentences explaining what the user needs to do. |
guideUrl | Yes | External docs or console URL. |
guideLabel | No | Link button label (defaults to “Open guide”). |
channelConnection
Section titled “channelConnection”Declares how a channel instance connects to its provider. Core owns the OAuth state machine and connection UI; the plugin supplies provider metadata and response field mappings.
{ "channelConnection": { "managedOAuth": { "gatewayProvider": "recursive-slack", "authorizationUrl": "https://slack.com/oauth/v2/authorize", "scopes": ["chat:write", "app_mentions:read"], "scopeSeparator": "comma", "identity": { "idPath": "team.id", "labelPath": "team.name" }, "settings": { "bot_token": "access_token", "provider_team_id": "team.id" } } }}| Field | Required | Description |
|---|---|---|
managedOAuth.gatewayProvider | Yes | Named credentials registered by the Recursive gateway operator. Plugin users never supply the shared app secret. |
managedOAuth.authorizationUrl | Yes | Provider OAuth authorize endpoint. |
managedOAuth.scopes | Yes | Non-empty scope list requested at connect time. |
managedOAuth.scopeSeparator | No | "space" (default) or "comma" when building the authorize URL. |
managedOAuth.identity | Yes | idPath / optional labelPath — JSON paths into the token response for the connected account. |
managedOAuth.settings | Yes | Maps token-response paths into plugin settings keys saved on the connection. |
onboarding vs setupSequence
Section titled “onboarding vs setupSequence”These two fields solve different problems — don’t mix them up:
onboardingis declarative plugin setup: asubtitleplusstepsof kindlink(open an external console),field(save a settings value, optionally secret), andverify(confirm required credentials are present), rendered in the plugin’s settings page. Use it for integrations that need credentials — trigger sources, API-backed tools.setupSequenceis adapter/CLI onboarding only: ordered terminal-action steps (install/auth/setup/diagnose, falling back toinstallCommand/loginCommand/setupCommand/diagnoseCommand) shown after installing a provider CLI plugin. Use it when the plugin wraps a CLI that must be installed and authenticated.
{ "onboarding": { "subtitle": "Connect your workspace so agents pick up events.", "steps": [ { "kind": "link", "title": "Create an app", "url": "https://example.com/apps", "actionLabel": "Open console" }, { "kind": "field", "title": "Add your Signing Secret", "field": { "key": "signing_secret", "secret": true }, "actionLabel": "Save" }, { "kind": "verify", "title": "Verify setup", "actionLabel": "Verify" } ] }}