The two sides of this change are more than 400 edits apart, too far apart to line up, so this is the differ's own diff of it and the words inside a line are not marked.
from line 2
22
33> Add skills, hooks, MCP servers, and every other component type to a Claude Code plugin, with an example that validates for each.
44
5export const Piece = ({id, children}) => <div className="pe-piece" data-piece={id}>{children}</div>;
6
7export const PluginExplorer = ({children}) => {
8 const PIECES = [{
9 id: 'manifest',
10 name: 'Manifest',
11 path: '.claude-plugin/plugin.json',
12 lines: [{
13 depth: 0,
14 kind: 'folder',
15 text: '.claude-plugin/'
16 }, {
17 depth: 1,
18 kind: 'file',
19 text: 'plugin.json'
20 }],
21 href: '/en/plugins/manifest-reference#manifest-file',
22 linkText: 'Go to the manifest reference'
23 }, {
24 id: 'skills',
25 name: 'Skills',
26 path: 'skills/review/SKILL.md',
27 lines: [{
28 depth: 0,
29 kind: 'folder',
30 text: 'skills/'
31 }, {
32 depth: 1,
33 kind: 'folder',
34 text: 'review/'
35 }, {
36 depth: 2,
37 kind: 'file',
38 text: 'SKILL.md'
39 }],
40 href: '/en/plugins/components#skills',
41 linkText: 'Go to the Skills section'
42 }, {
43 id: 'commands',
44 name: 'Commands',
45 path: 'commands/about.md',
46 lines: [{
47 depth: 0,
48 kind: 'folder',
49 text: 'commands/'
50 }, {
51 depth: 1,
52 kind: 'file',
53 text: 'about.md'
54 }],
55 href: '/en/plugins/components#commands',
56 linkText: 'Go to the Commands section'
57 }, {
58 id: 'agents',
59 name: 'Agents',
60 path: 'agents/security-reviewer.md',
61 lines: [{
62 depth: 0,
63 kind: 'folder',
64 text: 'agents/'
65 }, {
66 depth: 1,
67 kind: 'file',
68 text: 'security-reviewer.md'
69 }],
70 href: '/en/plugins/components#agents',
71 linkText: 'Go to the Agents section'
72 }, {
73 id: 'hooks',
74 name: 'Hooks',
75 path: 'hooks/hooks.json',
76 lines: [{
77 depth: 0,
78 kind: 'folder',
79 text: 'hooks/'
80 }, {
81 depth: 1,
82 kind: 'file',
83 text: 'hooks.json'
84 }],
85 href: '/en/plugins/components#hooks',
86 linkText: 'Go to the Hooks section'
87 }, {
88 id: 'monitors',
89 name: 'Monitors',
90 path: 'monitors/monitors.json',
91 lines: [{
92 depth: 0,
93 kind: 'folder',
94 text: 'monitors/'
95 }, {
96 depth: 1,
97 kind: 'file',
98 text: 'monitors.json'
99 }],
100 href: '/en/plugins/components#monitors',
101 linkText: 'Go to the Monitors section'
102 }, {
103 id: 'output-styles',
104 name: 'Output styles',
105 path: 'output-styles/terse.md',
106 lines: [{
107 depth: 0,
108 kind: 'folder',
109 text: 'output-styles/'
110 }, {
111 depth: 1,
112 kind: 'file',
113 text: 'terse.md'
114 }],
115 href: '/en/plugins/components#themes-and-output-styles',
116 linkText: 'Go to the Themes and output styles section'
117 }, {
118 id: 'themes',
119 name: 'Themes',
120 path: 'themes/dracula.json',
121 lines: [{
122 depth: 0,
123 kind: 'folder',
124 text: 'themes/'
125 }, {
126 depth: 1,
127 kind: 'file',
128 text: 'dracula.json'
129 }],
130 href: '/en/plugins/components#themes-and-output-styles',
131 linkText: 'Go to the Themes and output styles section'
132 }, {
133 id: 'workflows',
134 name: 'Workflows',
135 path: 'workflows/audit-routes.js',
136 lines: [{
137 depth: 0,
138 kind: 'folder',
139 text: 'workflows/'
140 }, {
141 depth: 1,
142 kind: 'file',
143 text: 'audit-routes.js'
144 }],
145 href: '/en/workflows#distribute-a-workflow-in-a-plugin',
146 linkText: 'Go to Distribute a workflow in a plugin'
147 }, {
148 id: 'bin',
149 name: 'Executables',
150 path: 'bin/hello-plugin',
151 lines: [{
152 depth: 0,
153 kind: 'folder',
154 text: 'bin/'
155 }, {
156 depth: 1,
157 kind: 'file',
158 text: 'hello-plugin'
159 }],
160 href: '/en/plugins/components#executables',
161 linkText: 'Go to the Executables section'
162 }, {
163 id: 'scripts',
164 name: 'Scripts',
165 path: 'scripts/format.sh',
166 lines: [{
167 depth: 0,
168 kind: 'folder',
169 text: 'scripts/'
170 }, {
171 depth: 1,
172 kind: 'file',
173 text: 'format.sh'
174 }],
175 href: '/en/plugins/components#hooks',
176 linkText: 'Go to the Hooks section'
177 }, {
178 id: 'settings',
179 name: 'Default settings',
180 path: 'settings.json',
181 lines: [{
182 depth: 0,
183 kind: 'file',
184 text: 'settings.json'
185 }],
186 href: '/en/plugins/components#default-settings',
187 linkText: 'Go to the Default settings section'
188 }, {
189 id: 'mcp',
190 name: 'MCP servers',
191 path: '.mcp.json',
192 lines: [{
193 depth: 0,
194 kind: 'file',
195 text: '.mcp.json'
196 }],
197 href: '/en/plugins/components#mcp-servers',
198 linkText: 'Go to the MCP servers section'
199 }, {
200 id: 'lsp',
201 name: 'LSP servers',
202 path: '.lsp.json',
203 lines: [{
204 depth: 0,
205 kind: 'file',
206 text: '.lsp.json'
207 }],
208 href: '/en/plugins/components#lsp-servers',
209 linkText: 'Go to the LSP servers section'
210 }];
211 const [selectedId, setSelectedId] = useState('manifest');
212 const [isFullscreen, setIsFullscreen] = useState(false);
213 const rootRef = useRef(null);
214 useEffect(() => {
215 const onFsChange = () => setIsFullscreen(!!document.fullscreenElement);
216 document.addEventListener('fullscreenchange', onFsChange);
217 return () => document.removeEventListener('fullscreenchange', onFsChange);
218 }, []);
219 const toggleFullscreen = () => {
220 if (!rootRef.current) return;
221 if (document.fullscreenElement) document.exitFullscreen(); else rootRef.current.requestFullscreen().catch(() => {});
222 };
223 const selected = PIECES.find(p => p.id === selectedId) || PIECES[0];
224 const onTreeKeyDown = e => {
225 const keys = ['ArrowDown', 'ArrowUp', 'Home', 'End'];
226 if (keys.indexOf(e.key) === -1) return;
227 const i = PIECES.findIndex(p => p.id === selectedId);
228 let next = i;
229 if (e.key === 'ArrowDown') next = Math.min(PIECES.length - 1, i + 1);
230 if (e.key === 'ArrowUp') next = Math.max(0, i - 1);
231 if (e.key === 'Home') next = 0;
232 if (e.key === 'End') next = PIECES.length - 1;
233 e.preventDefault();
234 if (next === i) return;
235 const id = PIECES[next].id;
236 setSelectedId(id);
237 const el = document.getElementById('pe-node-' + id);
238 if (el) el.focus();
239 };
240 const FolderIcon = () => <svg className="pe-icon" width="15" height="15" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.3" strokeLinejoin="round" aria-hidden="true">
241 <path d="M1.5 4.5a1 1 0 0 1 1-1h3.2l1.3 1.5h6a1 1 0 0 1 1 1V12a1 1 0 0 1-1 1h-10.5a1 1 0 0 1-1-1z" />
242 </svg>;
243 const FileIcon = () => <svg className="pe-icon" width="15" height="15" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.3" strokeLinejoin="round" aria-hidden="true">
244 <path d="M4 1.5h5.5L13 5v9.5H4z" />
245 <path d="M9.5 1.5V5H13" />
246 </svg>;
247 return <div ref={rootRef} className={isFullscreen ? 'pe-root pe-fullscreen not-prose' : 'pe-root not-prose'} data-selected={selected.id}>
248 <style>{`
249 .pe-root {
250 --pe-mono: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
251 --pe-accent: #D97757;
252 --pe-accent-text: #A8502F;
253 --pe-accent-bg: rgba(217,119,87,0.10);
254 --pe-bg: #FFFFFF;
255 --pe-surface: #FAFAF7;
256 --pe-hover: #F0EEE6;
257 --pe-border: #E8E6DC;
258 --pe-text: #141413;
259 --pe-text-2: #3D3D3A;
260 --pe-text-3: #5E5D59;
261 font-family: inherit;
262 background: var(--pe-bg);
263 color: var(--pe-text);
264 border: 1px solid var(--pe-border);
265 border-radius: 12px;
266 margin: 1.5rem 0;
267 overflow: hidden;
268 box-sizing: border-box;
269 }
270 .dark .pe-root {
271 --pe-accent-text: #EBA98F;
272 --pe-accent-bg: rgba(217,119,87,0.18);
273 --pe-bg: #1A1918;
274 --pe-surface: #232221;
275 --pe-hover: #2E2D2B;
276 --pe-border: #3A3936;
277 --pe-text: #F1EFE9;
278 --pe-text-2: #D6D4CA;
279 --pe-text-3: #B8B5AD;
280 }
281 .pe-root *, .pe-root *::before, .pe-root *::after { box-sizing: border-box; }
282 .pe-head { display: flex; align-items: flex-start; gap: 12px; padding: 18px 24px 16px; border-bottom: 1px solid var(--pe-border); }
283 .pe-head-text { flex: 1; min-width: 0; }
284 .pe-fs-btn { flex-shrink: 0; width: 32px; height: 32px; display: inline-flex; align-items: center; justify-content: center; border: 1px solid var(--pe-border); border-radius: 6px; background: var(--pe-surface); color: var(--pe-text-2); font-size: 15px; line-height: 1; cursor: pointer; }
285 .pe-fs-btn:hover { background: var(--pe-hover); }
286 .pe-fs-btn:focus-visible { outline: 2px solid var(--pe-accent); outline-offset: 2px; }
287 .pe-fullscreen { border-radius: 0; height: 100vh; display: flex; flex-direction: column; overflow: auto; }
288 .pe-fullscreen .pe-body { flex: 1; }
289 .pe-title { font-size: 19px; font-weight: 600; line-height: 1.3; color: var(--pe-text); margin: 0; }
290 .pe-sub { font-size: 15px; line-height: 1.5; color: var(--pe-text-3); margin: 4px 0 0; }
291 .pe-sub code { font-family: var(--pe-mono); font-size: 0.88em; padding: 1px 5px; border-radius: 4px; background: var(--pe-surface); border: 1px solid var(--pe-border); }
292 .pe-body { display: flex; align-items: stretch; }
293 .pe-tree-pane { width: 270px; flex-shrink: 0; background: var(--pe-surface); border-right: 1px solid var(--pe-border); padding: 16px 0 12px; }
294 .pe-panel { flex: 1; min-width: 0; padding: 16px 24px 24px; }
295 .pe-caption { font-size: 13px; font-weight: 600; color: var(--pe-text-3); margin: 0 0 10px; }
296 .pe-tree-pane .pe-caption { padding: 0 16px; }
297 .pe-rootline { display: flex; align-items: center; gap: 7px; padding: 3px 16px; font-family: var(--pe-mono); font-size: 13.5px; color: var(--pe-text-3); }
298 .pe-node {
299 display: block; width: 100%; margin: 0; padding: 3px 16px 3px 30px; text-align: left; cursor: pointer;
300 background: transparent; color: var(--pe-text-2);
301 border: none; border-left: 3px solid transparent;
302 font-family: var(--pe-mono); font-size: 13.5px; line-height: 1.4;
303 }
304 .pe-node:hover { background: var(--pe-hover); }
305 .pe-node:focus-visible { outline: 2px solid var(--pe-accent); outline-offset: -2px; }
306 .pe-node[aria-pressed="true"] { background: var(--pe-accent-bg); border-left-color: var(--pe-accent); color: var(--pe-accent-text); font-weight: 600; }
307 .pe-line { display: flex; align-items: center; gap: 7px; padding: 2px 0; }
308 .pe-line-tree { flex-wrap: wrap; }
309 .pe-line-tree .pe-req { flex-basis: 100%; margin: 2px 0 0 22px; white-space: normal; width: fit-content; max-width: calc(100% - 22px); }
310 .pe-line span { overflow-wrap: anywhere; }
311 .pe-piece { display: none; font-size: 16px; line-height: 1.6; color: var(--pe-text-2); }
312 .pe-root[data-selected="manifest"] .pe-piece[data-piece="manifest"],
313 .pe-root[data-selected="skills"] .pe-piece[data-piece="skills"],
314 .pe-root[data-selected="commands"] .pe-piece[data-piece="commands"],
315 .pe-root[data-selected="agents"] .pe-piece[data-piece="agents"],
316 .pe-root[data-selected="hooks"] .pe-piece[data-piece="hooks"],
317 .pe-root[data-selected="monitors"] .pe-piece[data-piece="monitors"],
318 .pe-root[data-selected="output-styles"] .pe-piece[data-piece="output-styles"],
319 .pe-root[data-selected="themes"] .pe-piece[data-piece="themes"],
320 .pe-root[data-selected="workflows"] .pe-piece[data-piece="workflows"],
321 .pe-root[data-selected="bin"] .pe-piece[data-piece="bin"],
322 .pe-root[data-selected="scripts"] .pe-piece[data-piece="scripts"],
323 .pe-root[data-selected="settings"] .pe-piece[data-piece="settings"],
324 .pe-root[data-selected="mcp"] .pe-piece[data-piece="mcp"],
325 .pe-root[data-selected="lsp"] .pe-piece[data-piece="lsp"] { display: block; }
326 .pe-piece p { margin: 0 0 10px; }
327 .pe-piece p:last-child { margin-bottom: 0; }
328 .pe-piece code { font-family: var(--pe-mono); font-size: 0.88em; padding: 1px 5px; border-radius: 4px; background: var(--pe-surface); border: 1px solid var(--pe-border); }
329 .pe-piece .code-block { margin: 12px 0 0; }
330 .pe-piece pre code { padding: 0; border: none; background: none; }
331 .pe-piece a { color: var(--pe-accent-text); }
332 .pe-line-compact { display: none; }
333 .pe-icon { flex-shrink: 0; }
334 .pe-req { margin-left: 8px; padding: 0 6px; border-radius: 999px; font-size: 11px; line-height: 18px; letter-spacing: .02em; color: var(--pe-accent-text); border: 1px solid var(--pe-border); background: var(--pe-surface); white-space: nowrap; font-weight: 500; vertical-align: middle; }
335 .pe-name { font-size: 22px; font-weight: 600; line-height: 1.25; letter-spacing: -0.2px; color: var(--pe-text); margin: 0; }
336 .pe-path { font-family: var(--pe-mono); font-size: 13.5px; color: var(--pe-accent-text); margin: 4px 0 0; overflow-wrap: anywhere; }
337 .pe-block { margin: 20px 0 0; }
338 .pe-link {
339 display: inline-block; margin: 24px 0 0; padding: 8px 14px; border-radius: 8px;
340 font-size: 14.5px; font-weight: 600; text-decoration: none;
341 color: var(--pe-accent-text); background: var(--pe-accent-bg); border: 1px solid var(--pe-accent);
342 }
343 .pe-link:hover { filter: brightness(0.97); }
344 .pe-link:focus-visible { outline: 2px solid var(--pe-accent); outline-offset: 2px; }
345 @media (max-width: 700px) {
346 .pe-head { padding: 16px 16px 14px; }
347 .pe-body { flex-direction: column; }
348 .pe-tree-pane { width: 100%; border-right: none; border-bottom: 1px solid var(--pe-border); }
349 .pe-line-tree { display: none; }
350 .pe-line-compact { display: flex; }
351 .pe-panel { padding: 16px 16px 20px; }
352 }
353 `}</style>
354
355 <div className="pe-head">
356 <div className="pe-head-text">
357 <div className="pe-title">What goes in a plugin</div>
358 <div className="pe-sub">This example plugin, <code>my-plugin</code>, has one of every kind of component, each in its default location. Select a file or folder to read what it’s for and see what goes in it.</div>
359 </div>
360 <button type="button" className="pe-fs-btn" onClick={toggleFullscreen} aria-label={isFullscreen ? 'Exit fullscreen' : 'Fullscreen'} title={isFullscreen ? 'Exit fullscreen' : 'Fullscreen'}>
361 {isFullscreen ? '⤡' : '⛶'}
362 </button>
363 </div>
364
365 <div className="pe-body">
366 <div className="pe-tree-pane">
367 <div className="pe-caption" id="pe-tree-caption">Plugin directory</div>
368 <div role="group" aria-labelledby="pe-tree-caption" onKeyDown={onTreeKeyDown}>
369 <div className="pe-rootline"><FolderIcon /><span>my-plugin/</span></div>
370 {PIECES.map(p => <button key={p.id} id={'pe-node-' + p.id} type="button" className="pe-node" aria-pressed={p.id === selected.id} aria-label={p.name + ', ' + p.path} onClick={() => setSelectedId(p.id)}>
371 {p.lines.map((line, i) => <span key={i} className="pe-line pe-line-tree" style={{
372 paddingLeft: line.depth * 18 + 'px'
373 }}>
374 {line.kind === 'folder' ? <FolderIcon /> : <FileIcon />}
375 <span>{line.text}</span>
376 {p.required && i === p.lines.length - 1 ? <span className="pe-req">{p.required}</span> : null}
377 </span>)}
378 <span className="pe-line pe-line-compact">
379 <FileIcon />
380 <span>{p.path}</span>
381 {p.required ? <span className="pe-req">{p.required}</span> : null}
382 </span>
383 </button>)}
384 </div>
385 </div>
386
387 <div className="pe-panel" role="region" aria-labelledby="pe-panel-caption" aria-live="polite" aria-atomic="true">
388 <div className="pe-caption" id="pe-panel-caption">Selected piece</div>
389 <div className="pe-name">{selected.name}{selected.required ? <span className="pe-req">{selected.required}</span> : null}</div>
390 <div className="pe-path">{selected.path}</div>
391
392 <div className="pe-block">{children}</div>
393
394 <a className="pe-link" href={selected.href}>{selected.linkText}</a>
395 </div>
396 </div>
397 </div>;
398};
399
4005A Claude Code plugin is built from components, such as skills, agents, hooks, and MCP servers. Each component has a default folder in the plugin, an optional manifest key in `.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](/docs/en/plugins/manifest-reference#fields).
4016
4027Use this page to add a component to a plugin that already loads.
4038
4049After 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 .`](/docs/en/plugins/cli-reference#plugin-validate) in your shell from the plugin directory.
40510
406<Note>
407 These cases are covered on other pages:
408
409 * **Building your first plugin**: start with [Create a plugin](/docs/en/plugins/create)
410 * **Installing someone else's plugin**: see [Install plugins](/docs/en/plugins/install)
411 * **Your plugin's users are on claude.ai or in Cowork**: a different set of components loads there. See [Plugin structure and testing](https://claude.com/docs/plugins/build) and the [component support table](https://claude.com/docs/plugins/platform-support#compare-component-support-by-app)
412</Note>
11> [!NOTE]
12>
13> These cases are covered on other pages:
14>
15> * **Building your first plugin**: start with [Create a plugin](/docs/en/plugins/create)
16> * **Installing someone else's plugin**: see [Install plugins](/docs/en/plugins/install)
17> * **Your plugin's users are on claude.ai or in Cowork**: a different set of components loads there. See [Plugin structure and testing](https://claude.com/docs/plugins/build) and the [component support table](https://claude.com/docs/plugins/platform-support#compare-component-support-by-app)
41318
41419## Explore the plugin directory
41520
from line 32
42732
42833Each file is the smallest valid example of its format, there to show the shape rather than to be useful: a real skill or agent carries full instructions and often supporting files, and a real hook or monitor does real work. The sections after the explorer use the same files as their examples and link to fuller ones. Select a file or folder to read what it's for, see what goes in it, and find the section that covers it.
42934
430<PluginExplorer>
431 <Piece id="manifest">
432 The [manifest](/docs/en/plugins/manifest-reference) is the `plugin.json` file in a plugin's `.claude-plugin/` directory. It contains the plugin's metadata and the `userConfig` values that Claude Code prompts the user for. Claude Code loads a plugin without one. Inside the file, only `name` is required. In this one, `description` is the text users see for the plugin in `/plugin`, and `version` keeps users on that version until you change it:
433
434 ```json theme={null}
435 {
436 "name": "my-plugin",
437 "version": "1.0.0",
438 "description": "Review, formatting, and database tools for this team"
439 }
440 ```
441 </Piece>
442
443 <Piece id="skills">
444 A [skill](/docs/en/skills) is a `SKILL.md` file. Save each skill in its own directory under `skills/`. Claude reads every skill's `description`, and when what the user asks for matches it, such as asking Claude to review a pull request here, Claude loads the skill's instructions and follows them. The user can also run it directly as `/my-plugin:review`:
445
446 ```markdown theme={null}
447 ---
448 description: Reviews a pull request for style and test coverage. Use when asked to review code.
449 ---
450
451 Review the changed files. Report style problems first, then missing tests.
452 ```
453 </Piece>
454
455 <Piece id="commands">
456 A command is a single Markdown file the user runs by name. Commands are the older format: a skill runs by name the same way and can also carry supporting files in its own directory, so write new ones as skills and keep `commands/` for files you already have. This file becomes `/my-plugin:about` and takes the same frontmatter as a skill:
457
458 ```markdown theme={null}
459 ---
460 description: Summarize the repository
461 ---
462
463 Summarize what this repository does in three sentences.
464 ```
465 </Piece>
466
467 <Piece id="agents">
468 A [subagent](/docs/en/sub-agents) is a separate assistant, with its own instructions and its own context window, that Claude can delegate a task to and get a result back from. Each Markdown file under `agents/` defines one: the frontmatter names it and says when to use it, and the body is its system prompt. This one is named `my-plugin:security-reviewer`, and the user can invoke it with `@agent-my-plugin:security-reviewer`:
469
470 ```markdown theme={null}
471 ---
472 name: security-reviewer
473 description: Reviews code changes for security issues. Use after edits to authentication or input handling.
474 model: sonnet
475 ---
476
477 You are a security reviewer. Read the changed files and report injection, authentication, and secrets-handling risks.
478 ```
479 </Piece>
480
481 <Piece id="hooks">
482 A [hook](/docs/en/hooks-guide) 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 in `hooks/hooks.json` at the plugin root. This one runs the plugin's `scripts/format.sh` after Claude writes or edits a file:
483
484 ```json theme={null}
485 {
486 "hooks": {
487 "PostToolUse": [
488 {
489 "matcher": "Write|Edit",
490 "hooks": [
491 {
492 "type": "command",
493 "command": "\"${CLAUDE_PLUGIN_ROOT}/scripts/format.sh\""
494 }
495 ]
496 }
497 ]
498 }
499 }
500 ```
501 </Piece>
502
503 <Piece id="monitors">
504 A monitor is a shell command that Claude Code starts in the background when the session starts and keeps running until it ends. What it prints reaches Claude as notifications. A `when` field can instead start it the first time a named skill runs. This one tails an error log:
505
506 ```json theme={null}
507 [
508 {
509 "name": "error-log",
510 "command": "tail -F ./logs/error.log",
511 "description": "Application error log"
512 }
513 ]
514 ```
515 </Piece>
516
517 <Piece id="output-styles">
518 A plugin can include [output styles](/docs/en/output-styles), which change how Claude formats and phrases its replies. Save each output style as `output-styles/<name>.md`. This one appears in `/output-style` as `my-plugin:terse`:
519
520 ```markdown theme={null}
521 ---
522 name: terse
523 description: Answer in as few words as possible
524 keep-coding-instructions: true
525 ---
526
527 Keep every reply short. Skip preambles and summaries.
528 ```
529 </Piece>
530
531 <Piece id="themes">
532 A plugin can include [color themes](/docs/en/terminal-config#create-a-custom-theme) for the Claude Code interface. Save each theme as `themes/<slug>.json`. This one appears in `/theme` as `Dracula`, marked as from `my-plugin`:
533
534 ```json theme={null}
535 {
536 "name": "Dracula",
537 "base": "dark",
538 "overrides": {
539 "claude": "#bd93f9",
540 "error": "#ff5555"
541 }
542 }
543 ```
544 </Piece>
545
546 <Piece id="workflows">
547 The `workflows/` folder holds [workflow](/docs/en/workflows) `.js` files: a `meta` block, then a script body that orchestrates several subagents. This one runs as `/my-plugin:audit-routes`:
548
549 ```javascript theme={null}
550 export const meta = {
551 name: 'audit-routes',
552 description: 'Audit every route handler for missing auth checks',
553 }
554
555 const found = await agent('List every .ts file under src/routes/.', {
556 schema: { type: 'object', required: ['files'], properties: { files: { type: 'array', items: { type: 'string' } } } },
557 })
558
559 const audits = await pipeline(found.files, file =>
560 agent(`Audit ${file} for missing authentication checks.`, { label: file }),
561 )
562
563 return audits.filter(Boolean)
564 ```
565 </Piece>
566
567 <Piece id="bin">
568 `bin/` is how a plugin ships a command-line tool. While the plugin is enabled, Claude Code puts this folder on the `PATH` of the shell it runs commands in, so Claude, or a skill's instructions, can run the tool by name without the user installing anything. With this [executable](#executables) in place, `hello-plugin` is a command Claude can run:
569
570 ```bash theme={null}
571 #!/bin/bash
572 echo "hello from my-plugin"
573 ```
574 </Piece>
575
576 <Piece id="scripts">
577 The hook in `hooks/hooks.json` runs a script, and this folder is where the example keeps it. The name `scripts/` is a convention, not something Claude Code looks for: the hook points at the file by its path, `${CLAUDE_PLUGIN_ROOT}/scripts/format.sh`. A formatter script might look like this:
578
579 ```bash theme={null}
580 #!/bin/bash
581 npx prettier --write .
582 ```
583 </Piece>
584
585 <Piece id="settings">
586 A `settings.json` at the plugin root holds [settings](/docs/en/settings-reference) that apply while the plugin is enabled, so a plugin can change how the session behaves and not only add components. Only two keys take effect from a plugin, [`agent`](/docs/en/settings-reference#agent) and [`subagentStatusLine`](/docs/en/settings-reference#subagentstatusline); every other key is dropped. See [Default settings](#default-settings).
587
588 This one sets `agent`, which runs the session's main thread as the plugin's own `security-reviewer` agent, so that agent's system prompt, tool restrictions, and model apply to the whole session:
589
590 ```json theme={null}
591 {
592 "agent": "security-reviewer"
593 }
594 ```
595 </Piece>
596
597 <Piece id="mcp">
598 An [MCP server](/docs/en/mcp) gives Claude tools from an external system. Declare it in `.mcp.json` at the plugin root. This one starts a local server from a script inside the plugin, and appears in `/mcp` as `plugin:my-plugin:db`:
599
600 ```json theme={null}
601 {
602 "mcpServers": {
603 "db": {
604 "command": "node",
605 "args": ["${CLAUDE_PLUGIN_ROOT}/server.js"]
606 }
607 }
608 }
609 ```
610 </Piece>
611
612 <Piece id="lsp">
613 An LSP server gives Claude [diagnostics and code navigation](/docs/en/plugins/code-intelligence) for a language. Declare the server in `.lsp.json` at the plugin root. This one connects the Go language server for `.go` files:
614
615 ```json theme={null}
616 {
617 "gopls": {
618 "command": "gopls",
619 "args": ["serve"],
620 "extensionToLanguage": {
621 ".go": "go"
622 }
623 }
624 }
625 ```
626 </Piece>
627</PluginExplorer>
628
629## Add each kind of component
630
631Each 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.
632
633### Skills
634
635A [skill](/docs/en/skills) is a `SKILL.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/`:
636
637```text theme={null}
35What goes in a plugin
36
37This example plugin, `my-plugin`, has one of every kind of component, each in its default location. Select a file or folder to read what it’s for and see what goes in it.
38
39Plugin directory
40
63841my-plugin/
639├── .claude-plugin/
640│ └── plugin.json
641└── skills/
642 └── review/
643 └── SKILL.md
644```
645
646Give the `SKILL.md` a `description` so Claude knows when to use it:
647
648```markdown skills/review/SKILL.md theme={null}
42
43Selected piece
44
45The [manifest](/docs/en/plugins/manifest-reference) is the `plugin.json` file in a plugin's `.claude-plugin/` directory. It contains the plugin's metadata and the `userConfig` values that Claude Code prompts the user for. Claude Code loads a plugin without one. Inside the file, only `name` is required. In this one, `description` is the text users see for the plugin in `/plugin`, and `version` keeps users on that version until you change it:
46
47```json theme={null}
48{
49 "name": "my-plugin",
50 "version": "1.0.0",
51 "description": "Review, formatting, and database tools for this team"
52}
53```
54
55A [skill](/docs/en/skills) is a `SKILL.md` file. Save each skill in its own directory under `skills/`. Claude reads every skill's `description`, and when what the user asks for matches it, such as asking Claude to review a pull request here, Claude loads the skill's instructions and follows them. The user can also run it directly as `/my-plugin:review`:
56
57```markdown theme={null}
64958---
65059description: Reviews a pull request for style and test coverage. Use when asked to review code.
65160---
from line 62
65362Review the changed files. Report style problems first, then missing tests.
65463```
65564
656After you load the plugin, `/my-plugin:review` runs the skill. The command name and who can invoke it follow these rules:
657
658* **Command name**: `/<plugin>:<directory>`, so `skills/review/SKILL.md` in `my-plugin` is `/my-plugin:review`. If you set `name` in the frontmatter, it replaces the last segment and the plugin prefix stays. See [how a skill gets its command name](/docs/en/skills#how-a-skill-gets-its-command-name)
659* **Who invokes it**: Claude, the user, or both, controlled by frontmatter. See [Control who invokes a skill](/docs/en/skills#control-who-invokes-a-skill)
660
661You can also place skills outside the default `skills/` directory:
662
663* **Additional directories**: list them in the `skills` manifest key. They add to the default `skills/` scan rather than replacing it, unlike `commands` and `agents`
664* **A single skill at the plugin root**: with no `skills/` directory and no `skills` manifest key, a `SKILL.md` at the plugin root loads as one skill. Set `name` in its frontmatter, because otherwise a marketplace install names the skill after its [cache directory](/docs/en/plugins/loading#find-plugins-on-disk) rather than your plugin
665
666To include instructions in a plugin, write them as a skill. Claude Code doesn't load a `CLAUDE.md` at the plugin root, and `claude plugin validate` warns `CLAUDE.md at the plugin root is not loaded as project context`.
667
668If a rule must hold every time, such as [blocking edits to protected files](/docs/en/hooks-guide#block-edits-to-protected-files), add it to the plugin as a [hook](#hooks) rather than a skill. To choose between the two, see the Hook vs Skill tab under [Compare similar features](/docs/en/features-overview#compare-similar-features).
669
670For frontmatter fields and supporting files, see [Skills](/docs/en/skills).
671
672### Commands
673
674A command is a single Markdown file the user runs by name, such as `/my-plugin:about`.
675
676<Note>
677 Commands are the older format, and [skills](#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/`.
678</Note>
679
680Save a command at `commands/<file>.md` and it becomes `/<plugin>:<file>`. A subdirectory adds a segment, so `commands/db/migrate.md` is `/my-plugin:db:migrate`.
681
682Command files take the same frontmatter as skills.
683
684#### Define commands in the manifest
685
686You only need this if you want to keep command files somewhere other than `commands/`, 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`.
687
688This manifest defines `/my-plugin:about` inline, with no Markdown file:
689
690```json .claude-plugin/plugin.json theme={null}
691{
692 "name": "my-plugin",
693 "commands": {
694 "about": {
695 "content": "Summarize what this repository does in three sentences.",
696 "description": "Summarize the repository"
697 }
698 }
699}
700```
701
702Load the plugin and run `/my-plugin:about` in the session to confirm it loaded.
703
704For the full key syntax, see [`commands`](/docs/en/plugins/manifest-reference#commands).
705
706### Agents
707
708A [subagent](/docs/en/sub-agents) is a separate assistant, with its own instructions and context window, that Claude can delegate a task to. Each Markdown file under `agents/` defines one:
709
710```markdown agents/security-reviewer.md theme={null}
65A command is a single Markdown file the user runs by name. Commands are the older format: a skill runs by name the same way and can also carry supporting files in its own directory, so write new ones as skills and keep `commands/` for files you already have. This file becomes `/my-plugin:about` and takes the same frontmatter as a skill:
66
67```markdown theme={null}
68---
69description: Summarize the repository
70---
71
72Summarize what this repository does in three sentences.
73```
74
75A [subagent](/docs/en/sub-agents) is a separate assistant, with its own instructions and its own context window, that Claude can delegate a task to and get a result back from. Each Markdown file under `agents/` defines one: the frontmatter names it and says when to use it, and the body is its system prompt. This one is named `my-plugin:security-reviewer`, and the user can invoke it with `@agent-my-plugin:security-reviewer`:
76
77```markdown theme={null}
71178---
71279name: security-reviewer
71380description: Reviews code changes for security issues. Use after edits to authentication or input handling.
from line 84
71784You are a security reviewer. Read the changed files and report injection, authentication, and secrets-handling risks.
71885```
71986
720This agent is named `my-plugin:security-reviewer`, and the user can [invoke it explicitly](/docs/en/sub-agents#invoke-subagents-explicitly) with `@agent-my-plugin:security-reviewer`. The name form is `<plugin>:<name>`, where `<name>` comes from the frontmatter `name` field, or from the file name when that field is missing.
721
722The `agents` manifest key replaces the `agents/` scan.
723
724#### Organize agents in subfolders
725
726You can put plugin agent files in subfolders of `agents/`. Claude Code [loads them recursively](/docs/en/sub-agents#choose-the-subagent-scope) 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:
727
728* Frontmatter `name`: it replaces only the file name, so `name: audit` in `agents/review/security.md` loads as `my-plugin:review:audit`
729* Manifest [`agents`](/docs/en/plugins/manifest-reference#fields) field: a file you list there loads without subfolder names, so `"agents": "./custom/review/security.md"` loads as `my-plugin:security`
730
731#### Frontmatter fields in plugin agents
732
733A plugin agent's frontmatter follows these rules:
734
735* **Supported fields**: `name`, `description`, `model`, `effort`, `maxTurns`, `autoCompactWindow`, `tools`, `disallowedTools`, `skills`, `memory`, `background`, `omitClaudeMd`, `isolation`, `color`, and the `cacheTtl` key of `experimental`. The only valid `isolation` value is `"worktree"`. See [supported frontmatter fields](/docs/en/sub-agents#supported-frontmatter-fields) for what each one does
736* **Ignored fields**: `permissionMode`, `hooks`, `mcpServers`, and `initialPrompt`. An agent file can't add hooks or MCP servers on its own, so add those as plugin [hooks](#hooks) and [MCP servers](#mcp-servers) instead
737* **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`. Run [`claude plugin validate`](/docs/en/plugins/cli-reference#plugin-validate) in your shell to find these files
738
739For what each field does and the precedence rules, see [Subagents](/docs/en/sub-agents#supported-frontmatter-fields).
740
741### Hooks
742
743A [hook](/docs/en/hooks-guide) 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 in `hooks/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.
744
745This hook runs a bundled script after every `Write` or `Edit`:
746
747```json hooks/hooks.json theme={null}
87A [hook](/docs/en/hooks-guide) 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 in `hooks/hooks.json` at the plugin root. This one runs the plugin's `scripts/format.sh` after Claude writes or edits a file:
88
89```json theme={null}
74890{
74991 "hooks": {
75092 "PostToolUse": [
from line 104
762104}
763105```
764106
765Save the script at `scripts/format.sh` and make it executable.
766
767Load 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](/docs/en/hooks#debug-hooks) or by what the script itself changes.
768
769Hooks in `hooks/hooks.json` and in the `hooks` manifest key both load. For every event and its payload, see [Hook events](/docs/en/hooks#hook-events).
770
771When another enabled plugin has the same name, one of the two registers its `hooks/hooks.json` hooks and the other's are left out. See [Hooks when two enabled plugins share a name](/docs/en/plugins/loading#hooks-when-two-enabled-plugins-share-a-name) for which one, and for the note in `/plugin` that tells you.
772
773To write hooks as JavaScript functions that run inside Claude Code and can draw in its interface, list a module file under a `modules` key in the same `hooks/hooks.json`. A plugin with one is a mod. See [Create a mod](/docs/en/plugins/mods/create).
774
775#### When plugin hooks fire
776
777A 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 its `matcher`.
778
779If a hook never fires, see [hooks that don't fire](/docs/en/plugins/troubleshooting#failed-to-load-hooks-from-and-hooks-that-dont-fire).
780
781#### Environment, quoting, and matching MCP tools
782
783The hook's environment, the quoting of `${CLAUDE_PLUGIN_ROOT}`, and matchers for the plugin's own MCP tools work as follows:
784
785* **Environment**: every hook process receives `CLAUDE_PLUGIN_ROOT` and `CLAUDE_PLUGIN_DATA` in its environment, plus `CLAUDE_PLUGIN_OPTION_<KEY>` for each [user configuration](#user-configuration) value, so your script can read them from there
786* **Quoting**: when `command` has no `args`, it runs through a shell, so wrap the `${CLAUDE_PLUGIN_ROOT}` path in double quotes, as the `hooks/hooks.json` example under [Hooks](#hooks) does, to keep the expanded path one shell word. When you pass `args` instead, each element is passed as one argument with no shell and needs no quoting. See [exec form and shell form](/docs/en/hooks#exec-form-and-shell-form)
787* **Matching the plugin's own MCP tools**: a tool from an [MCP server this plugin declares](#mcp-servers) 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](/docs/en/hooks#match-mcp-tools)
788
789### MCP servers
790
791An 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`](/docs/en/mcp#project-scope). This `.mcp.json` declares one server named `db`:
792
793```json .mcp.json theme={null}
107A monitor is a shell command that Claude Code starts in the background when the session starts and keeps running until it ends. What it prints reaches Claude as notifications. A `when` field can instead start it the first time a named skill runs. This one tails an error log:
108
109```json theme={null}
110[
111 {
112 "name": "error-log",
113 "command": "tail -F ./logs/error.log",
114 "description": "Application error log"
115 }
116]
117```
118
119A plugin can include [output styles](/docs/en/output-styles), which change how Claude formats and phrases its replies. Save each output style as `output-styles/<name>.md`. This one appears in `/output-style` as `my-plugin:terse`:
120
121```markdown theme={null}
122---
123name: terse
124description: Answer in as few words as possible
125keep-coding-instructions: true
126---
127
128Keep every reply short. Skip preambles and summaries.
129```
130
131A plugin can include [color themes](/docs/en/terminal-config#create-a-custom-theme) for the Claude Code interface. Save each theme as `themes/<slug>.json`. This one appears in `/theme` as `Dracula`, marked as from `my-plugin`:
132
133```json theme={null}
134{
135 "name": "Dracula",
136 "base": "dark",
137 "overrides": {
138 "claude": "#bd93f9",
139 "error": "#ff5555"
140 }
141}
142```
143
144The `workflows/` folder holds [workflow](/docs/en/workflows) `.js` files: a `meta` block, then a script body that orchestrates several subagents. This one runs as `/my-plugin:audit-routes`:
145
146```javascript theme={null}
147export const meta = {
148 name: 'audit-routes',
149 description: 'Audit every route handler for missing auth checks',
150}
151
152const found = await agent('List every .ts file under src/routes/.', {
153 schema: { type: 'object', required: ['files'], properties: { files: { type: 'array', items: { type: 'string' } } } },
154})
155
156const audits = await pipeline(found.files, file =>
157 agent(`Audit ${file} for missing authentication checks.`, { label: file }),
158)
159
160return audits.filter(Boolean)
161```
162
163`bin/` is how a plugin ships a command-line tool. While the plugin is enabled, Claude Code puts this folder on the `PATH` of the shell it runs commands in, so Claude, or a skill's instructions, can run the tool by name without the user installing anything. With this [executable](#executables) in place, `hello-plugin` is a command Claude can run:
164
165```bash theme={null}
166#!/bin/bash
167echo "hello from my-plugin"
168```
169
170The hook in `hooks/hooks.json` runs a script, and this folder is where the example keeps it. The name `scripts/` is a convention, not something Claude Code looks for: the hook points at the file by its path, `${CLAUDE_PLUGIN_ROOT}/scripts/format.sh`. A formatter script might look like this:
171
172```bash theme={null}
173#!/bin/bash
174npx prettier --write .
175```
176
177A `settings.json` at the plugin root holds [settings](/docs/en/settings-reference) that apply while the plugin is enabled, so a plugin can change how the session behaves and not only add components. Only two keys take effect from a plugin, [`agent`](/docs/en/settings-reference#agent) and [`subagentStatusLine`](/docs/en/settings-reference#subagentstatusline); every other key is dropped. See [Default settings](#default-settings).
178
179This one sets `agent`, which runs the session's main thread as the plugin's own `security-reviewer` agent, so that agent's system prompt, tool restrictions, and model apply to the whole session:
180
181```json theme={null}
182{
183 "agent": "security-reviewer"
184}
185```
186
187An [MCP server](/docs/en/mcp) gives Claude tools from an external system. Declare it in `.mcp.json` at the plugin root. This one starts a local server from a script inside the plugin, and appears in `/mcp` as `plugin:my-plugin:db`:
188
189```json theme={null}
794190{
795191 "mcpServers": {
796192 "db": {
from line 197
801197}
802198```
803199
804You can also omit the `mcpServers` wrapper and put `db` at the top level of the file.
805
806Load the plugin and run `/mcp` to confirm the server appears as `plugin:my-plugin:db`.
807
808`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.
809
810For where a bad entry shows up at load time, see [MCP servers that don't start](/docs/en/plugins/troubleshooting#invalid-mcp-server-config-for-and-mcp-servers-that-dont-start).
811
812The `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.
813
814#### Reach users on claude.ai and Cowork
815
816A local stdio server, such as the `db` server under [MCP servers](#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, as [Bundle an MCP connector with its skill](https://claude.com/docs/plugins/build#bundle-an-mcp-connector-with-its-skill) shows.
817
818#### Server names, tool names, and reloads
819
820The server's names, variable substitution, and reload behavior follow these rules:
821
822* **Server name**: `plugin:<plugin>:<server>`, so the `db` server in `my-plugin` is `plugin:my-plugin:db` in `/mcp`. Use the same form to name the server in an [`mcp_tool` hook](/docs/en/hooks#mcp-tool-hook-fields)
823* **Tool names**: `mcp__plugin_<plugin>_<server>__<tool>`, so a `query` tool on that `db` server is `mcp__plugin_my-plugin_db__query`. That is the name to use in [permission rules](/docs/en/permissions) and [hook matchers](#hooks)
824* **Substitution**: `${CLAUDE_PLUGIN_ROOT}` and the other [path variables](#path-variables-and-persistent-data) are substituted in `command`, `args`, and `env`. No quoting is needed in `args`, because each element is passed as one argument
825* **Reload**: when the user runs `/reload-plugins` and [the reload applies](/docs/en/plugins/cli-reference#reloads-that-change-mcp-tools), a server whose configuration is unchanged keeps its connection. A server whose configuration changed reconnects, and one you removed disconnects
826
827#### Include a packaged MCPB server
828
829The `mcpServers` key also accepts a packaged server as an [MCPB file](https://github.com/modelcontextprotocol/mcpb), whose extension is `.mcpb` or the older `.dxt`. Point the key at the file, as a path inside the plugin or an `https://` URL:
830
831```json .claude-plugin/plugin.json theme={null}
832{
833 "name": "my-plugin",
834 "mcpServers": "./servers/db.mcpb"
835}
836```
837
838The server takes its name from the `name` in the bundle's manifest.
839
840A bundle's own manifest can declare settings the server needs from the user in a `user_config` block. A bundled server with a required setting that has no saved value doesn't start. The `/plugin` **Errors** tab shows `Bundled MCP server "<name>" was not started: it needs configuration`.
841
842Users supply the values in one of two ways:
843
844* **In `/plugin`**: select the plugin on the **Installed** tab and choose **Configure**
845* **At install, from the shell**: pass [`--config <server>.<key>=<value>`](/docs/en/plugins/cli-reference#plugin-install) to `claude plugin install`. Requires Claude Code v2.1.285 or later, and works only for a bundle packaged inside the plugin.
846
847For transports and authentication, see [MCP](/docs/en/mcp#plugin-provided-mcp-servers).
848
849### LSP servers
850
851An LSP server gives Claude diagnostics and code navigation for a language. If an [official code intelligence plugin](/docs/en/plugins/code-intelligence) already covers your language, install that instead of writing one. Otherwise declare the server in `.lsp.json` at the plugin root:
852
853```json .lsp.json theme={null}
200An LSP server gives Claude [diagnostics and code navigation](/docs/en/plugins/code-intelligence) for a language. Declare the server in `.lsp.json` at the plugin root. This one connects the Go language server for `.go` files:
201
202```json theme={null}
854203{
855204 "gopls": {
856205 "command": "gopls",
from line 211
862211}
863212```
864213
214## Add each kind of component
215
216Each 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.
217
218### Skills
219
220A [skill](/docs/en/skills) is a `SKILL.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/`:
221
222```text theme={null}
223my-plugin/
224├── .claude-plugin/
225│ └── plugin.json
226└── skills/
227 └── review/
228 └── SKILL.md
229```
230
231Give the `SKILL.md` a `description` so Claude knows when to use it:
232
233```markdown skills/review/SKILL.md theme={null}
234---
235description: Reviews a pull request for style and test coverage. Use when asked to review code.
236---
237
238Review the changed files. Report style problems first, then missing tests.
239```
240
241After you load the plugin, `/my-plugin:review` runs the skill. The command name and who can invoke it follow these rules:
242
243* **Command name**: `/<plugin>:<directory>`, so `skills/review/SKILL.md` in `my-plugin` is `/my-plugin:review`. If you set `name` in the frontmatter, it replaces the last segment and the plugin prefix stays. See [how a skill gets its command name](/docs/en/skills#how-a-skill-gets-its-command-name)
244* **Who invokes it**: Claude, the user, or both, controlled by frontmatter. See [Control who invokes a skill](/docs/en/skills#control-who-invokes-a-skill)
245
246You can also place skills outside the default `skills/` directory:
247
248* **Additional directories**: list them in the `skills` manifest key. They add to the default `skills/` scan rather than replacing it, unlike `commands` and `agents`
249* **A single skill at the plugin root**: with no `skills/` directory and no `skills` manifest key, a `SKILL.md` at the plugin root loads as one skill. Set `name` in its frontmatter, because otherwise a marketplace install names the skill after its [cache directory](/docs/en/plugins/loading#find-plugins-on-disk) rather than your plugin
250
251To include instructions in a plugin, write them as a skill. Claude Code doesn't load a `CLAUDE.md` at the plugin root, and `claude plugin validate` warns `CLAUDE.md at the plugin root is not loaded as project context`.
252
253If a rule must hold every time, such as [blocking edits to protected files](/docs/en/hooks-guide#block-edits-to-protected-files), add it to the plugin as a [hook](#hooks) rather than a skill. To choose between the two, see the Hook vs Skill tab under [Compare similar features](/docs/en/features-overview#compare-similar-features).
254
255For frontmatter fields and supporting files, see [Skills](/docs/en/skills).
256
257### Commands
258
259A command is a single Markdown file the user runs by name, such as `/my-plugin:about`.
260
261> [!NOTE]
262>
263> Commands are the older format, and [skills](#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/`.
264
265Save a command at `commands/<file>.md` and it becomes `/<plugin>:<file>`. A subdirectory adds a segment, so `commands/db/migrate.md` is `/my-plugin:db:migrate`.
266
267Command files take the same frontmatter as skills.
268
269#### Define commands in the manifest
270
271You only need this if you want to keep command files somewhere other than `commands/`, 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`.
272
273This manifest defines `/my-plugin:about` inline, with no Markdown file:
274
275```json .claude-plugin/plugin.json theme={null}
276{
277 "name": "my-plugin",
278 "commands": {
279 "about": {
280 "content": "Summarize what this repository does in three sentences.",
281 "description": "Summarize the repository"
282 }
283 }
284}
285```
286
287Load the plugin and run `/my-plugin:about` in the session to confirm it loaded.
288
289For the full key syntax, see [`commands`](/docs/en/plugins/manifest-reference#commands).
290
291### Agents
292
293A [subagent](/docs/en/sub-agents) is a separate assistant, with its own instructions and context window, that Claude can delegate a task to. Each Markdown file under `agents/` defines one:
294
295```markdown agents/security-reviewer.md theme={null}
296---
297name: security-reviewer
298description: Reviews code changes for security issues. Use after edits to authentication or input handling.
299model: sonnet
300---
301
302You are a security reviewer. Read the changed files and report injection, authentication, and secrets-handling risks.
303```
304
305This agent is named `my-plugin:security-reviewer`, and the user can [invoke it explicitly](/docs/en/sub-agents#invoke-subagents-explicitly) with `@agent-my-plugin:security-reviewer`. The name form is `<plugin>:<name>`, where `<name>` comes from the frontmatter `name` field, or from the file name when that field is missing.
306
307The `agents` manifest key replaces the `agents/` scan.
308
309#### Organize agents in subfolders
310
311You can put plugin agent files in subfolders of `agents/`. Claude Code [loads them recursively](/docs/en/sub-agents#choose-the-subagent-scope) 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:
312
313* Frontmatter `name`: it replaces only the file name, so `name: audit` in `agents/review/security.md` loads as `my-plugin:review:audit`
314* Manifest [`agents`](/docs/en/plugins/manifest-reference#fields) field: a file you list there loads without subfolder names, so `"agents": "./custom/review/security.md"` loads as `my-plugin:security`
315
316#### Frontmatter fields in plugin agents
317
318A plugin agent's frontmatter follows these rules:
319
320* **Supported fields**: `name`, `description`, `model`, `effort`, `maxTurns`, `autoCompactWindow`, `tools`, `disallowedTools`, `skills`, `memory`, `background`, `omitClaudeMd`, `isolation`, `color`, and the `cacheTtl` key of `experimental`. The only valid `isolation` value is `"worktree"`. See [supported frontmatter fields](/docs/en/sub-agents#supported-frontmatter-fields) for what each one does
321* **Ignored fields**: `permissionMode`, `hooks`, `mcpServers`, and `initialPrompt`. An agent file can't add hooks or MCP servers on its own, so add those as plugin [hooks](#hooks) and [MCP servers](#mcp-servers) instead
322* **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`. Run [`claude plugin validate`](/docs/en/plugins/cli-reference#plugin-validate) in your shell to find these files
323
324For what each field does and the precedence rules, see [Subagents](/docs/en/sub-agents#supported-frontmatter-fields).
325
326### Hooks
327
328A [hook](/docs/en/hooks-guide) 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 in `hooks/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.
329
330This hook runs a bundled script after every `Write` or `Edit`:
331
332```json hooks/hooks.json theme={null}
333{
334 "hooks": {
335 "PostToolUse": [
336 {
337 "matcher": "Write|Edit",
338 "hooks": [
339 {
340 "type": "command",
341 "command": "\"${CLAUDE_PLUGIN_ROOT}/scripts/format.sh\""
342 }
343 ]
344 }
345 ]
346 }
347}
348```
349
350Save the script at `scripts/format.sh` and make it executable.
351
352Load 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](/docs/en/hooks#debug-hooks) or by what the script itself changes.
353
354Hooks in `hooks/hooks.json` and in the `hooks` manifest key both load. For every event and its payload, see [Hook events](/docs/en/hooks#hook-events).
355
356When another enabled plugin has the same name, one of the two registers its `hooks/hooks.json` hooks and the other's are left out. See [Hooks when two enabled plugins share a name](/docs/en/plugins/loading#hooks-when-two-enabled-plugins-share-a-name) for which one, and for the note in `/plugin` that tells you.
357
358To write hooks as JavaScript functions that run inside Claude Code and can draw in its interface, list a module file under a `modules` key in the same `hooks/hooks.json`. A plugin with one is a mod. See [Create a mod](/docs/en/plugins/mods/create).
359
360#### When plugin hooks fire
361
362A 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 its `matcher`.
363
364If a hook never fires, see [hooks that don't fire](/docs/en/plugins/troubleshooting#failed-to-load-hooks-from-and-hooks-that-dont-fire).
365
366#### Environment, quoting, and matching MCP tools
367
368The hook's environment, the quoting of `${CLAUDE_PLUGIN_ROOT}`, and matchers for the plugin's own MCP tools work as follows:
369
370* **Environment**: every hook process receives `CLAUDE_PLUGIN_ROOT` and `CLAUDE_PLUGIN_DATA` in its environment, plus `CLAUDE_PLUGIN_OPTION_<KEY>` for each [user configuration](#user-configuration) value, so your script can read them from there
371* **Quoting**: when `command` has no `args`, it runs through a shell, so wrap the `${CLAUDE_PLUGIN_ROOT}` path in double quotes, as the `hooks/hooks.json` example under [Hooks](#hooks) does, to keep the expanded path one shell word. When you pass `args` instead, each element is passed as one argument with no shell and needs no quoting. See [exec form and shell form](/docs/en/hooks#exec-form-and-shell-form)
372* **Matching the plugin's own MCP tools**: a tool from an [MCP server this plugin declares](#mcp-servers) 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](/docs/en/hooks#match-mcp-tools)
373
374### MCP servers
375
376An 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`](/docs/en/mcp#project-scope). This `.mcp.json` declares one server named `db`:
377
378```json .mcp.json theme={null}
379{
380 "mcpServers": {
381 "db": {
382 "command": "node",
383 "args": ["${CLAUDE_PLUGIN_ROOT}/server.js"]
384 }
385 }
386}
387```
388
389You can also omit the `mcpServers` wrapper and put `db` at the top level of the file.
390
391Load the plugin and run `/mcp` to confirm the server appears as `plugin:my-plugin:db`.
392
393`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.
394
395For where a bad entry shows up at load time, see [MCP servers that don't start](/docs/en/plugins/troubleshooting#invalid-mcp-server-config-for-and-mcp-servers-that-dont-start).
396
397The `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.
398
399#### Reach users on claude.ai and Cowork
400
401A local stdio server, such as the `db` server under [MCP servers](#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, as [Bundle an MCP connector with its skill](https://claude.com/docs/plugins/build#bundle-an-mcp-connector-with-its-skill) shows.
402
403#### Server names, tool names, and reloads
404
405The server's names, variable substitution, and reload behavior follow these rules:
406
407* **Server name**: `plugin:<plugin>:<server>`, so the `db` server in `my-plugin` is `plugin:my-plugin:db` in `/mcp`. Use the same form to name the server in an [`mcp_tool` hook](/docs/en/hooks#mcp-tool-hook-fields)
408* **Tool names**: `mcp__plugin_<plugin>_<server>__<tool>`, so a `query` tool on that `db` server is `mcp__plugin_my-plugin_db__query`. That is the name to use in [permission rules](/docs/en/permissions) and [hook matchers](#hooks)
409* **Substitution**: `${CLAUDE_PLUGIN_ROOT}` and the other [path variables](#path-variables-and-persistent-data) are substituted in `command`, `args`, and `env`. No quoting is needed in `args`, because each element is passed as one argument
410* **Reload**: when the user runs `/reload-plugins` and [the reload applies](/docs/en/plugins/cli-reference#reloads-that-change-mcp-tools), a server whose configuration is unchanged keeps its connection. A server whose configuration changed reconnects, and one you removed disconnects
411
412#### Include a packaged MCPB server
413
414The `mcpServers` key also accepts a packaged server as an [MCPB file](https://github.com/modelcontextprotocol/mcpb), whose extension is `.mcpb` or the older `.dxt`. Point the key at the file, as a path inside the plugin or an `https://` URL:
415
416```json .claude-plugin/plugin.json theme={null}
417{
418 "name": "my-plugin",
419 "mcpServers": "./servers/db.mcpb"
420}
421```
422
423The server takes its name from the `name` in the bundle's manifest.
424
425A bundle's own manifest can declare settings the server needs from the user in a `user_config` block. A bundled server with a required setting that has no saved value doesn't start. The `/plugin` **Errors** tab shows `Bundled MCP server "<name>" was not started: it needs configuration`.
426
427Users supply the values in one of two ways:
428
429* **In `/plugin`**: select the plugin on the **Installed** tab and choose **Configure**
430* **At install, from the shell**: pass [`--config <server>.<key>=<value>`](/docs/en/plugins/cli-reference#plugin-install) to `claude plugin install`. Requires Claude Code v2.1.285 or later, and works only for a bundle packaged inside the plugin.
431
432For transports and authentication, see [MCP](/docs/en/mcp#plugin-provided-mcp-servers).
433
434### LSP servers
435
436An LSP server gives Claude diagnostics and code navigation for a language. If an [official code intelligence plugin](/docs/en/plugins/code-intelligence) already covers your language, install that instead of writing one. Otherwise declare the server in `.lsp.json` at the plugin root:
437
438```json .lsp.json theme={null}
439{
440 "gopls": {
441 "command": "gopls",
442 "args": ["serve"],
443 "extensionToLanguage": {
444 ".go": "go"
445 }
446 }
447}
448```
449
865450The file maps each server name directly to its configuration, with no wrapper object around the map. `command` is the binary's name, with its arguments in `args`. `extensionToLanguage` needs at least one extension, each starting with `.`.
866451
867452`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.
from line 466
881466
882467### Executables
883468
884Files in `bin/` 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:
469Files in `bin/` 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. A command that Claude runs with the [PowerShell tool](/docs/en/tools-reference#powershell-tool) outside the [sandbox](/docs/en/sandboxing) finds them on its `PATH` too, and a sandboxed PowerShell command doesn't. Add an executable script:
885470
886471```bash bin/hello-plugin theme={null}
887472#!/bin/bash
from line 587
1002587
1003588For the `when` trigger and the other fields, see [`monitors`](/docs/en/plugins/manifest-reference#monitors).
1004589
1005<h2 id="user-configuration">
1006 Ask the user for configuration values
1007</h2>
590## Ask the user for configuration values
1008591
1009592Declare the values your plugin needs from the user in the `userConfig` 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.
1010593
from line 632
1049632
1050633For the option fields, where each value is stored, how a component references a saved value, and which fields reject `${user_config.*}`, see [User configuration](/docs/en/plugins/manifest-reference#user-configuration).
1051634
1052<h2 id="path-variables-and-persistent-data">
1053 Reference plugin paths and store data
1054</h2>
635## Reference plugin paths and store data
1055636
1056637You 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:
1057638
1058639