Plugins are Claude Code’s highest-level extension mechanism: they bundle skills, subagents, hooks, MCP servers, and LSP servers into a single installable package, so a team gets everything configured the moment they install one. This module covers how a plugin is structured and what its manifest minimally contains, which manifest fields shape its behavior — from secure configuration and persistent data directories to background monitors and LSP servers — and how you test plugins locally, distribute them through marketplaces, and manage their lifecycle.
Anatomy of a Plugin
A plugin is, at its core, a directory with a fixed structure. The only file Claude Code strictly requires is .claude-plugin/plugin.json — the manifest that declares the plugin’s identity. Everything else is optional but follows conventions Claude Code recognizes automatically: skills live under skills/, subagents under agents/, hook configuration in hooks/hooks.json, MCP servers in .mcp.json, LSP servers in .lsp.json, default settings in settings.json, and executables under bin/, which get added to the Bash tool’s PATH while the plugin is enabled. Note that all of these directories belong at the plugin’s top level, not inside .claude-plugin/ — that directory holds only the manifest file itself.
my-plugin/
├── .claude-plugin/
│ └── plugin.json # Required manifest
├── skills/ # SKILL.md files
│ └── my-skill/
│ └── SKILL.md
├── agents/ # Subagent definitions
│ └── specialist.md
├── commands/ # Legacy command files (also work)
│ └── my-command.md
├── hooks/
│ └── hooks.json # Plugin-scoped hooks
├── .mcp.json # MCP server configs
├── .lsp.json # LSP server configs
├── settings.json # Default settings
└── bin/
└── helper.sh
Skills get a shortcut: a plugin that ships exactly one skill can place SKILL.md directly at the plugin root instead of creating a dedicated skills/ directory. Claude Code then loads it as a single skill and uses the frontmatter name field as the invocation name. For plugins that may grow to more than one skill, the skills/ layout with one subdirectory per skill is the right choice; older plugins sometimes still use flat markdown files under commands/ instead, which continues to work.
The manifest identifies the plugin through fields such as name, description, version, author, repository, and license. Of these, only name is strictly required — a unique, kebab-case identifier.
{
"name": "pr-review",
"description": "Complete PR review workflow with security and test coverage checks",
"version": "1.0.0",
"author": {
"name": "Your Name"
},
"repository": "https://github.com/you/pr-review",
"license": "MIT"
}
That same name also determines the namespace: a hello skill from a plugin named my-first-plugin becomes /my-first-plugin:hello, and an agent-creator agent from a plugin named plugin-dev appears as plugin-dev:agent-creator. You always invoke plugin skills in this full, namespaced form. A plugin works without a manifest too — Claude Code then looks for components at their default locations and derives the plugin name from the directory name; a manifest becomes worthwhile once you need metadata or custom component paths.
Advanced Manifest Fields
The manifest’s userConfig field declares values that Claude Code prompts for when the plugin is enabled, instead of forcing you to hand-edit settings.json. Mark a field sensitive: true and the value never lands in plain text: it goes to the macOS Keychain, or, on platforms without a supported keychain, falls back to ~/.claude/.credentials.json — the same store used for OAuth tokens, with a combined limit of roughly 2 KB. Non-sensitive values are stored normally, under the pluginConfigs key in your settings.json.
{
"name": "my-plugin",
"version": "1.0.0",
"userConfig": {
"apiKey": {
"description": "API key for the integration",
"sensitive": true
},
"region": {
"description": "Deployment region",
"default": "us-east-1"
}
}
}
For state that should outlive a single session, Claude Code provides ${CLAUDE_PLUGIN_DATA} (since v2.1.78): a persistent data directory that survives plugin updates and suits caches, state files, or small databases. Separate from that is ${CLAUDE_PLUGIN_ROOT}, the path to the plugin’s current installation directory — essential for referencing bundled scripts and config files reliably from hooks and MCP configurations.
{
"hooks": {
"PostToolUse": [
{
"hooks": [
{
"type": "command",
"command": "node ${CLAUDE_PLUGIN_ROOT}/bin/audit.js"
}
]
}
]
}
}
As of Claude Code v2.1.105, plugins can also ship background monitors. Declare them under experimental.monitors in the manifest; the older top-level monitors form still works, but claude plugin validate will warn about it, and a future release will require the nested form. A monitor wires the plugin into the Monitor tool: the moment the plugin is enabled at session start, or one of its skills is invoked, its background watch auto-arms — with nothing for you to set up.
{
"name": "ci-watcher",
"version": "1.0.0",
"experimental": {
"monitors": "./monitors.json"
}
}
If the manifest points at a custom monitors path, the default monitors/monitors.json location is no longer scanned automatically; list it explicitly if you still want it loaded alongside your custom file.
For real-time language intelligence, place a .lsp.json at the plugin root. It configures language servers that give Claude instant diagnostics, go-to-definition, and symbol search as files are edited. For common languages such as TypeScript, Python, or Rust, a custom LSP plugin usually isn’t necessary — the official marketplace already ships pre-built LSP plugins for them, so a hand-written .lsp.json is most useful for languages that aren’t covered yet.
{
"typescript": {
"command": "typescript-language-server",
"args": ["--stdio"],
"extensionToLanguage": {
".ts": "typescript",
".tsx": "typescriptreact"
}
}
}
Testing, Distributing, and Managing Plugins
Before distributing a plugin, test it locally. The --plugin-dir flag loads it for the current session only, with no installation; besides a directory it also accepts a .zip archive (v2.1.128 and later), and you can repeat the flag to load several plugins at once.
claude --plugin-dir ./my-plugin
# Test multiple plugins simultaneously:
claude --plugin-dir ./my-plugin --plugin-dir ./another-plugin
For a plugin already hosted as a .zip archive, such as a CI build artifact, --plugin-url fetches it at startup and likewise loads it for that session only.
claude --plugin-url https://example.com/my-plugin.zip
claude --plugin-url https://example.com/a.zip --plugin-url https://example.com/b.zip
Only point --plugin-url at URLs you trust, since loading a remote archive executes third-party code with your user privileges.
For plugins you keep developing locally without installing them from a marketplace, claude plugin init <name> scaffolds a starter plugin directly under ~/.claude/skills/<name>/. On the next session it loads automatically as <name>@skills-dir — with no marketplace entry and no separate install step. During development, pick up plugin file changes with /reload-plugins without restarting the session; the command re-reads manifests, skills, agents, hooks, and every plugin MCP and plugin LSP server.
The /plugin interface has four tabs: Discover, Installed, Marketplaces, and Errors. As of v2.1.145, browsing a marketplace catalog in the Discover tab shows a full preview of what a not-yet-installed plugin will add — commands, agents, skills, hooks, and any MCP or LSP servers. The Installed tab shows the same component breakdown in its detail view for plugins already installed, matching what claude plugin details reports. That turns vetting a plugin into a one-screen decision before any of its components ever runs.
Distribution follows a marketplace model. The official marketplace, claude-plugins-official, is automatically available the moment you start Claude Code; add further marketplaces with /plugin marketplace add owner/repo, and install plugins with /plugin install plugin-name@marketplace-name.
# Install from official marketplace
/plugin install pr-review
# Install from GitHub
/plugin install github:username/my-plugin
# Install from local path (for testing)
/plugin install ./path/to/plugin
For a GitHub owner/repo shorthand, Claude Code defaults to cloning over SSH — that fails in CI environments with no SSH key configured. Set CLAUDE_CODE_PLUGIN_PREFER_HTTPS=1 in that case to clone over HTTPS instead.
# Force HTTPS for plugin clones in CI
CLAUDE_CODE_PLUGIN_PREFER_HTTPS=1 claude plugin install owner/repo
For organizations, managed-mcp.json controls which MCP servers plugins are allowed to use at all; the managed-settings fields enabledPlugins, extraKnownMarketplaces, strictKnownMarketplaces, and blockedMarketplaces determine, organization-wide, which plugins and marketplaces are allowed. Separately — and not limited to enterprise environments — plugin subagents are restricted everywhere: their frontmatter cannot define hooks, mcpServers, or permissionMode; for security reasons, these fields are simply ignored whenever an agent is loaded from a plugin.
Useful lifecycle commands include claude plugin list, enable, disable, uninstall, and validate. claude plugin prune (new in v2.1.121, aliased autoremove) removes auto-installed dependencies that no other installed plugin still needs — plugins you installed directly are never touched; claude plugin uninstall <plugin> --prune uninstalls a plugin and cleans up its dependencies in one step. claude plugin details <name> shows a plugin’s components grouped as Skills, Agents, Hooks, MCP servers, and LSP servers, along with an estimated token cost split into always-on and on-invoke. claude plugin tag (new in v2.1.118) creates a release git tag with version validation; --push pushes it in the same step, and --dry-run previews the result.
enable and disable also accept a --scope of user, project, or local; omit it and Claude Code auto-detects the scope the plugin is installed in. Disabling at project scope writes the choice into .claude/settings.json so the whole team picks it up.
# Personal: turn off a noisy plugin just for you
claude plugin disable formatter@anthropics/claude-plugins
# Team: keep the plugin in settings but turn it off project-wide
claude plugin disable formatter --scope project
# Re-enable later without touching its install or version
claude plugin enable formatter --scope project
For small, team-internal tools that don’t justify a repository of their own, there’s also the inline plugin pattern: with "source": "settings" you embed a plugin definition directly in a settings file, with no separate marketplace.
{
"pluginMarketplaces": [
{
"name": "internal-tools",
"source": "settings",
"plugins": [
{
"name": "code-standards",
"source": "./local-plugins/code-standards"
}
]
}
]
}