.claude-plugin/plugin.json that replaces or adds to that folder, and a name the user sees. For each key’s full field table, see the manifest reference.
Use this page to add a component to a plugin that already loads.
After you add a component, run /reload-plugins in a running session or start a new one so Claude Code loads it. To check the component’s file before loading it, run claude plugin validate . in your shell from the plugin directory.
These cases are covered on other pages:
- Building your first plugin: start with Create a plugin
- Installing someone else’s plugin: see Install plugins
- Your plugin’s users are on claude.ai or in Cowork: a different set of components loads there. See Plugins on claude.ai and in Cowork
Explore the plugin directory
The explorer shows an example plugin,my-plugin, that has one of every kind of component in its default location:
- A review skill and an
aboutcommand - A security-review subagent
- A hook that formats files after Claude edits them, and the
scripts/folder it calls - A log monitor
- An output style and a color theme
- A route-audit workflow
- A
hello-pluginexecutable - Default settings
- A local MCP server and a Go language server
Add each kind of component
Each section below covers one kind of component: where its files go in the plugin, an example that validates, what the user sees once the plugin loads, and the manifest key that changes the default location. Add the ones your plugin needs; none is required.Skills
A skill is aSKILL.md file that Claude can load when its description matches the task. The user can also run it as a command. Save each skill in its own directory under skills/:
SKILL.md a description so Claude knows when to use it:
skills/review/SKILL.md
/my-plugin:review runs the skill. The command name and who can invoke it follow these rules:
- Command name:
/<plugin>:<directory>, soskills/review/SKILL.mdinmy-pluginis/my-plugin:review. If you setnamein the frontmatter, it replaces the last segment and the plugin prefix stays. See how a skill gets its command name - Who invokes it: Claude, the user, or both, controlled by frontmatter. See Control who invokes a skill
skills/ directory:
- Additional directories: list them in the
skillsmanifest key. They add to the defaultskills/scan rather than replacing it, unlikecommandsandagents - A single skill at the plugin root: with no
skills/directory and noskillsmanifest key, aSKILL.mdat the plugin root loads as one skill. Setnamein its frontmatter, because otherwise a marketplace install names the skill after its cache directory rather than your plugin
CLAUDE.md at the plugin root, and claude plugin validate warns CLAUDE.md at the plugin root is not loaded as project context.
For frontmatter fields and supporting files, see Skills.
Commands
A command is a single Markdown file the user runs by name, such as/my-plugin:about.
Commands are the older format, and skills supersede them for new work. A skill runs by name the same way, and it can also carry supporting files in its directory. Keep
commands/ for files you’re moving over from .claude/commands/.commands/<file>.md and it becomes /<plugin>:<file>. A subdirectory adds a segment, so commands/db/migrate.md is /my-plugin:db:migrate.
Command files take the same frontmatter as skills.
Define commands in the manifest
You only need this if you want to keep command files somewhere other thancommands/, or to define a short command inside plugin.json without a separate Markdown file. Set the commands manifest key, and Claude Code reads it instead of scanning commands/. The key takes a path, an array of paths, or an object that maps each command name to either a source file or inline content.
This manifest defines /my-plugin:about inline, with no Markdown file:
.claude-plugin/plugin.json
/my-plugin:about in the session to confirm it loaded.
For the full key syntax, see commands.
Agents
A subagent is a separate assistant, with its own instructions and context window, that Claude can delegate a task to. Each Markdown file underagents/ defines one:
agents/security-reviewer.md
my-plugin:security-reviewer, and the user can invoke it explicitly with @agent-my-plugin:security-reviewer. The name form is <plugin>:<name>, where <name> comes from the frontmatter, or from the file name when there is none.
The agents manifest key replaces the agents/ scan.
Organize agents in subfolders
You can put plugin agent files in subfolders ofagents/. Claude Code loads them recursively and joins the plugin name, each subfolder name, and the file name with colons to form the agent’s scoped name. For example, agents/review/security.md in a plugin named my-plugin loads as my-plugin:review:security. Two settings change that name:
- Frontmatter
name: it replaces only the file name, soname: auditinagents/review/security.mdloads asmy-plugin:review:audit - Manifest
agentsfield: a file you list there loads without subfolder names, so"agents": "./custom/review/security.md"loads asmy-plugin:security
Frontmatter fields in plugin agents
A plugin agent’s frontmatter follows these rules:- Supported fields:
name,description,model,effort,maxTurns,tools,disallowedTools,skills,memory,background,omitClaudeMd,isolation,color, and thecacheTtlkey ofexperimental. The only validisolationvalue is"worktree". See supported frontmatter fields for what each one does - Ignored fields:
permissionMode,hooks,mcpServers, andinitialPrompt. An agent file can’t add hooks or MCP servers on its own, so add those as plugin hooks and MCP servers instead - Frontmatter that doesn’t parse: the agent still loads with every field ignored. It’s named after the file, and its description reads
Agent from my-plugin plugin. Runclaude plugin validatein your shell to find these files
Hooks
A hook runs something automatically at a point in Claude Code’s lifecycle, such as after every file edit: a shell command, an HTTP request, an MCP tool call, a prompt to a model, or a subagent. Save the plugin’s hooks inhooks/hooks.json at the plugin root, under a top-level "hooks" key, in the same shape as the hooks object in settings.json. That lets you copy an existing settings hook in unchanged.
This hook runs a bundled script after every Write or Edit:
hooks/hooks.json
scripts/format.sh and make it executable.
Load the plugin and ask Claude to edit a file. A PostToolUse hook that exits 0 shows nothing in the transcript, so confirm it ran with debug logging or by what the script itself changes.
Hooks in hooks/hooks.json and in the hooks manifest key both load. For every event and its payload, see Hook events.
When plugin hooks fire
A plugin’s hooks don’t wait for one of the plugin’s skills or commands to be used. Claude Code registers them when a session loads the plugin, and they fire on their events from then on. To limit when a hook runs, narrow itsmatcher.
If a hook never fires, see hooks that don’t fire.
Environment, quoting, and matching MCP tools
The hook’s environment, the quoting of${CLAUDE_PLUGIN_ROOT}, and matchers for the plugin’s own MCP tools work as follows:
- Environment: every hook process receives
CLAUDE_PLUGIN_ROOTandCLAUDE_PLUGIN_DATAin its environment, plusCLAUDE_PLUGIN_OPTION_<KEY>for each user configuration value, so your script can read them from there - Quoting: when
commandhas noargs, it runs through a shell, so wrap the${CLAUDE_PLUGIN_ROOT}path in double quotes, as thehooks/hooks.jsonexample under Hooks does, to keep the expanded path one shell word. When you passargsinstead, each element is passed as one argument with no shell and needs no quoting. See exec form and shell form - Matching the plugin’s own MCP tools: a tool from an MCP server this plugin declares is named
mcp__plugin_<plugin>_<server>__<tool>, so write that full name in the matcher. A matcher on the server name alone never fires. See Match MCP tools
MCP servers
An MCP server gives Claude tools from an external system. Declare it in.mcp.json at the plugin root, in the same shape as a project .mcp.json. This .mcp.json declares one server named db:
.mcp.json
mcpServers wrapper and put db at the top level of the file.
Load the plugin and run /mcp to confirm the server appears as plugin:my-plugin:db.
claude plugin validate checks .mcp.json and reports a server entry that Claude Code would drop at load time as an error. Requires Claude Code v2.1.281 or later.
For where a bad entry shows up at load time, see MCP servers that don’t start.
The mcpServers manifest key takes an inline server map, a path to a JSON file, or an array of those. When a manifest server has the same name as one in .mcp.json, the manifest server replaces it.
Reach users on claude.ai and Cowork
A local stdio server, such as thedb server under MCP servers, runs in Claude Code and in a Cowork session that runs on your machine in the Claude Desktop app, but not on claude.ai. To reach users there too, reference a remote server by its https:// URL, which claude.ai and Cowork offer to the user as a connector.
Server names, tool names, and reloads
The server’s names, variable substitution, and reload behavior follow these rules:- Server name:
plugin:<plugin>:<server>, so thedbserver inmy-pluginisplugin:my-plugin:dbin/mcp. Use the same form to name the server in anmcp_toolhook - Tool names:
mcp__plugin_<plugin>_<server>__<tool>, so aquerytool on thatdbserver ismcp__plugin_my-plugin_db__query. That is the name to use in permission rules and hook matchers - Substitution:
${CLAUDE_PLUGIN_ROOT}and the other path variables are substituted incommand,args, andenv. No quoting is needed inargs, because each element is passed as one argument - Reload: when the user runs
/reload-pluginsand the reload applies, a server whose configuration is unchanged keeps its connection. A server whose configuration changed reconnects, and one you removed disconnects
Include a packaged MCPB server
ThemcpServers key also accepts a packaged server as an MCPB file, whose extension is .mcpb or the older .dxt. Point the key at the file, as a path inside the plugin or an https:// URL:
.claude-plugin/plugin.json
name in the bundle’s manifest.
For transports and authentication, see MCP.
LSP servers
An LSP server gives Claude diagnostics and code navigation for a language. If an official code intelligence plugin already covers your language, install that instead of writing one. Otherwise declare the server in.lsp.json at the plugin root:
.lsp.json
command is the binary’s name, with its arguments in args. extensionToLanguage needs at least one extension, each starting with ..
claude plugin validate doesn’t read this file. When any entry is invalid, the whole file is skipped at load and Invalid LSP server config for ".lsp.json" appears in the /plugin Errors tab.
Your plugin configures the connection but doesn’t install the server binary, and each file extension gets one server:
- Missing binary: Claude Code starts
commandby name from the user’sPATH. When the binary isn’t there, the server fails to start andclaude --debuglogsLSP server <name> failed to start - Extension conflicts: when two enabled servers claim the same extension, the first registered handles those files and the other isn’t used for them, whether the servers come from one plugin or two. The
/pluginErrors tab shows the warningLSP server "<name>" is not used for <ext> files
lspServers manifest key takes the same map inline, a path to a JSON file, or an array of those, and its servers add to the ones in .lsp.json. When a manifest server has the same name as one in .lsp.json, the manifest server replaces it.
For transport, timeouts, restarts, and the other fields, see lspServers.
Send log output to stderr, not stdout. Claude Code reads a server’s stdout as protocol messages only, and accepts message headers up to 64 KiB and a message body up to 32 MiB.
Claude Code disconnects a server that exceeds either limit or writes non-protocol output to stdout, and counts the disconnect as a crash for restartOnCrash and maxRestarts. When you run with --debug, Claude Code writes an error naming the cause to the debug log.
Executables
Files inbin/ at the plugin root are on the PATH of the Bash tool’s shell while the plugin is enabled, so Claude can run them as bare commands. Add an executable script:
bin/hello-plugin
chmod +x bin/hello-plugin and load the plugin. When you ask Claude to run hello-plugin, the Bash tool result shows the script’s output.
Plugin bin/ directories come after the user’s own PATH entries, so a plugin can’t shadow git, ls, or another system command.
claude.ai and Cowork don’t install a plugin that has a top-level bin/ directory, including one you distribute through claude.ai organization settings.
Default settings
To set defaults that apply while the plugin is enabled, add asettings.json at the plugin root, or put the same object inline in the settings manifest key. Two keys take effect, agent and subagentStatusLine, and every other key is dropped.
Set agent to run one of the plugin’s own agents as the main thread:
settings.json
security-reviewer agent’s system prompt and model.
For everything the key controls, see the agent setting.
When the same key is set in more than one place, these rules decide which value applies:
- File over manifest: when both exist and
settings.jsonsets at least one supported key,settings.jsonapplies and the manifest’ssettingsis ignored - User settings over plugin defaults: across settings sources, plugin defaults are the lowest layer, so a user’s own
agentin~/.claude/settings.jsonoverrides yours - Two plugins set the same key: the value from the plugin loaded last applies, and
claude --debuglogsoverrides setting
subagentStatusLine shape, see subagent status lines.
Themes and output styles
A plugin can include color themes and output styles. Both appear in the same pickers as the user’s own. For either one, setting the manifest key replaces the folder scan.
Plugin themes are read-only, so when a user edits one in
/theme, the edit is saved as a copy in their own themes directory.
This theme recolors the prompt accent and error text on the dark preset:
themes/dracula.json
Channels
A channel lets an outside system such as a chat app send messages into a session. In a plugin, a channel is one of the MCP servers plus achannels entry that binds to it and can prompt for its own configuration. This manifest binds a channel to a telegram server and asks for a bot token:
.claude-plugin/plugin.json
server must match a key in mcpServers. The per-channel userConfig takes the same shape as the top-level userConfig key.
For what the server must implement and how users enable a channel plugin, see Package as a plugin in the channels reference. For the field table, see channels.
Monitors
A monitor is a shell command that runs in the background for the whole session. What it prints reaches Claude as notifications, so Claude can react to a log or a status change without being asked to watch it. Save the entries inmonitors/monitors.json:
monitors/monitors.json
- Interactive sessions only: plugin monitors start in an interactive session and never in non-interactive mode with the
-pflag. They also start only where the Monitor tool is available - No user configuration:
commandgets the path variables and${ENV_VAR}from the environment, but never${user_config.*}. A monitor that references one doesn’t start, and monitor processes don’t receiveCLAUDE_PLUGIN_OPTION_<KEY>either - Disabling mid-session: if you disable a plugin mid-session, Claude Code doesn’t stop monitors that are already running. They stop when the session ends
experimental.monitors manifest key takes the same array inline or a path to a JSON file, and is read instead of monitors/monitors.json.
For the when trigger and the other fields, see monitors.
Ask the user for configuration values
Declare the values your plugin needs from the user in theuserConfig manifest key, so users don’t edit settings.json themselves. Each option appears in a dialog with its title as the label and its description beneath it.
Set "sensitive": true for a token or password. The dialog then masks the input, and the value is stored in secure storage rather than settings.json.
This manifest asks for an endpoint and a token:
.claude-plugin/plugin.json
When the configuration dialog appears
The dialog appears only in the interactive/plugin interface. It opens for any option that isn’t set yet when the user does any of the following:
- Installs the plugin in
/plugin - Runs
/plugin install <plugin>@<marketplace>inside a session - Enables the plugin from the Installed tab in
/plugin
/plugin configure <plugin>@<marketplace>.
The claude plugin install shell command never prompts for userConfig values. To set values from the shell, pass each one as --config KEY=VALUE. When options remain unset, the command prints a userConfig options not yet set line that names both ways to set them. The userConfig dialog never appears quotes the line.
For the option fields, where each value is stored, how a component references a saved value, and which fields reject ${user_config.*}, see User configuration.
Reference plugin paths and store data
You don’t know where your plugin will be installed, so refer to its files and data through these variables rather than fixed paths. They’re substituted in skill, command, and agent content, in hook and monitor commands, and in MCP and LSP server configurations. They’re also exported to hook, MCP, and LSP processes:${CLAUDE_PLUGIN_ROOT}: the plugin’s install directory. Each version has its own cache directory, so the path changes when the plugin updates. Don’t write state there${CLAUDE_PLUGIN_DATA}: a directory that survives updates, fornode_modules, virtual environments, and caches. It resolves to~/.claude/plugins/data/<id>/and is created when first referenced${CLAUDE_PROJECT_DIR}: the project root, the same value hooks receive
<id> is the plugin identifier with every character other than letters, digits, _, and - replaced by -, so my-plugin@my-marketplace becomes my-plugin-my-marketplace.
On Windows, the substituted paths use forward slashes so a shell doesn’t read backslashes as escapes.
Install dependencies into the data directory
For a marketplace-installed plugin, Claude Code installs eligible Node.js package dependencies automatically when it caches the plugin, so you may not need to install them yourself. When you do, thisSessionStart hook installs node_modules into ${CLAUDE_PLUGIN_DATA} on first run and again after an update changes package.json:
hooks/hooks.json
~/.claude/plugins/data/<id>/node_modules exists. An MCP server can then set NODE_PATH to ${CLAUDE_PLUGIN_DATA}/node_modules in its env. For which fields substitute which variable, see Environment variables.
Next steps
- Plugin manifest reference:
plugin.jsonfields, path rules, and the standard layout - Test plugins with evals: check that the components you added change Claude’s behavior the way you intend
- Publish and distribute a plugin: version the plugin and put it in a marketplace
- Troubleshoot plugins: what to do when a component doesn’t load or a hook doesn’t fire