All sections

08Extending

Plugins

A plugin is the box; a skill is one thing in the box. Bundle skills, agents, hooks, MCP servers and default settings into one versioned, namespaced unit a second repository can adopt in a single command.

Plugin vs skill

Not competing concepts. A plugin is the box; a skill is one thing in the box.

Skill Plugin
What it is One SKILL.md, a unit of knowledge or a workflow A directory bundling many components
Can contain Instructions, supporting files, scripts Skills, subagents, hooks, MCP servers, LSP configs, background monitors, executables, default settings
Invocation /skill-name Its components are invoked; the plugin itself is not
Name collisions Possible across scopes, one shadows another Impossible, namespaced as /plugin:component
Distribution Commit .claude/skills/, or copy by hand /plugin install name@marketplace, versioned
Updates Manual Marketplace

The namespacing is worth more than it sounds. A team skill called /deploy is silently shadowed by any contributor who happens to have a personal /deploy. Packaged as a plugin it becomes /platform:deploy and the collision cannot happen.

Anatomy

graph TD
    ROOT["my-plugin/"]
    ROOT --> META[".claude-plugin/<br/>plugin.json<br/><b>ONLY this file goes here</b>"]
    ROOT --> SK["skills/<br/>api-review/SKILL.md"]
    ROOT --> AG["agents/<br/>log-triage.md"]
    ROOT --> HK["hooks/<br/>hooks.json"]
    ROOT --> MCP[".mcp.json<br/>MCP server definitions"]
    ROOT --> BIN["bin/<br/>executables added to PATH"]

The single most common mistake is putting skills/, agents/, or hooks/ inside .claude-plugin/. Only plugin.json lives there. Everything else sits at the plugin root.

plugin.json:

{
  "name": "platform-tools",
  "version": "1.2.0",
  "description": "Shared review, release, and triage tooling for platform services."
}

A realistic plugin

What a platform team actually ships to twenty service repositories:

platform-tools/
├── .claude-plugin/
│   └── plugin.json
├── skills/
│   ├── api-review/SKILL.md          # /platform-tools:api-review
│   ├── release-notes/SKILL.md
│   ├── incident/SKILL.md
│   └── deploy/SKILL.md              # disable-model-invocation: true
├── agents/
│   ├── log-triage.md
│   ├── test-analyst.md
│   └── security-reviewer.md
├── hooks/
│   └── hooks.json                   # protect generated/, run the formatter
├── bin/
│   └── plat                         # internal CLI, added to PATH
└── .mcp.json                        # incident tracker, metrics backend

Each service repository then needs only:

{
  "enabledPlugins": ["platform-tools@company-marketplace"]
}

instead of twenty copies of the same .claude/ tree drifting apart over a year.

Lifecycle

Action Command
Install /plugin install platform-tools@company-marketplace
Test locally without installing claude --plugin-dir ./my-plugin
Reload after editing /reload-plugins
Enable per project enabledPlugins in .claude/settings.json

--plugin-dir is the development loop. Point it at a working copy, iterate, and only publish once the components behave.

When to convert

Start with standalone configuration in .claude/ for quick iteration. Convert to a plugin when you are ready to share. The practical trigger: a second repository needs the same setup. Before that, packaging is overhead.

Signal Convert?
One repository, still changing weekly No. Iterate in .claude/
A second repository copy-pasted your .claude/skills/ Yes, that copy will drift
Contributors keep hitting name collisions with personal skills Yes, namespacing solves it
You need the same hooks enforced across an org Yes, plus consider managed settings
You want version pinning and a rollback story Yes

Plugins contribute hooks you did not write. When debugging an unexpectedly blocked tool call, check the hooks from every enabled plugin, not just your own settings. See hooks.