Add components to a plugin changedplugins/components
Nearest release: v2.1.282, published 7 hours before upstream edited the page. Shown because the two are within 24 hours of each other. Nothing here says the release caused the edit.
Upstream edited this page at 24 Sep 2026 23:46 UTC, give or take a minute or two: the time comes from Anthropic’s own sitemap rather than from a commit. This site recorded the change at 25 Sep 2026 00:07 UTC.
# Add components to a plugin ## Explore the plugin directory ## Add each kind of component ### Skills ### Commands #### Define commands in the manifest ### Agents #### Organize agents in subfolders #### Frontmatter fields in plugin agents ### Hooks #### When plugin hooks fire #### Environment, quoting, and matching MCP tools ### MCP servers #### Reach users on claude.ai and Cowork #### Server names, tool names, and reloads #### Include a packaged MCPB server ### LSP servers ### Executables ### Default settings ### Themes and output styles ### Channels ### Monitors ### When the configuration dialog appears ### Install dependencies into the data directory ## Next steps
The whole hunk
1077 lines, new pageA whole new page. There's nothing to diff it against, so here is what it says.
# Add components to a plugin
> Add skills, hooks, MCP servers, and every other component type to a Claude Code plugin, with an example that validates for each.
export const Piece = ({id, children}) => <div className="pe-piece" data-piece={id}>{children}</div>;
export const PluginExplorer = ({children}) => {
const PIECES = [{
id: 'manifest',
name: 'Manifest',
path: '.claude-plugin/plugin.json',
lines: [{
depth: 0,
kind: 'folder',
text: '.claude-plugin/'
}, {
depth: 1,
kind: 'file',
text: 'plugin.json'
}],
href: '/en/plugins/manifest-reference#manifest-file',
linkText: 'Go to the manifest reference'
}, {
id: 'skills',
name: 'Skills',
path: 'skills/review/SKILL.md',
lines: [{
depth: 0,
kind: 'folder',
text: 'skills/'
}, {
depth: 1,
kind: 'folder',
text: 'review/'
}, {
depth: 2,
kind: 'file',
text: 'SKILL.md'
}],
href: '/en/plugins/components#skills',
linkText: 'Go to the Skills section'
}, {
id: 'commands',
name: 'Commands',
path: 'commands/about.md',
lines: [{
depth: 0,
kind: 'folder',
text: 'commands/'
}, {
depth: 1,
kind: 'file',
text: 'about.md'
}],
href: '/en/plugins/components#commands',
linkText: 'Go to the Commands section'
}, {
id: 'agents',
name: 'Agents',
path: 'agents/security-reviewer.md',
lines: [{
depth: 0,
kind: 'folder',
text: 'agents/'
}, {
depth: 1,
kind: 'file',
text: 'security-reviewer.md'
}],
href: '/en/plugins/components#agents',
linkText: 'Go to the Agents section'
}, {
id: 'hooks',
name: 'Hooks',
path: 'hooks/hooks.json',
lines: [{
depth: 0,
kind: 'folder',
text: 'hooks/'
}, {
depth: 1,
kind: 'file',
text: 'hooks.json'
}],
href: '/en/plugins/components#hooks',
linkText: 'Go to the Hooks section'
}, {
id: 'monitors',
name: 'Monitors',
path: 'monitors/monitors.json',
lines: [{
depth: 0,
kind: 'folder',
text: 'monitors/'
}, {
depth: 1,
kind: 'file',
text: 'monitors.json'
}],
href: '/en/plugins/components#monitors',
linkText: 'Go to the Monitors section'
}, {
id: 'output-styles',
name: 'Output styles',
path: 'output-styles/terse.md',
lines: [{
depth: 0,
kind: 'folder',
text: 'output-styles/'
}, {
depth: 1,
kind: 'file',
text: 'terse.md'
}],
href: '/en/plugins/components#themes-and-output-styles',
linkText: 'Go to the Themes and output styles section'
}, {
id: 'themes',
name: 'Themes',
path: 'themes/dracula.json',
lines: [{
depth: 0,
kind: 'folder',
text: 'themes/'
}, {
depth: 1,
kind: 'file',
text: 'dracula.json'
}],
href: '/en/plugins/components#themes-and-output-styles',
linkText: 'Go to the Themes and output styles section'
}, {
id: 'workflows',
name: 'Workflows',
path: 'workflows/audit-routes.js',
lines: [{
depth: 0,
kind: 'folder',
text: 'workflows/'
}, {
depth: 1,
kind: 'file',
text: 'audit-routes.js'
}],
href: '/en/workflows#distribute-a-workflow-in-a-plugin',
linkText: 'Go to Distribute a workflow in a plugin'
}, {
id: 'bin',
name: 'Executables',
path: 'bin/hello-plugin',
lines: [{
depth: 0,
kind: 'folder',
text: 'bin/'
}, {
depth: 1,
kind: 'file',
text: 'hello-plugin'
}],
href: '/en/plugins/components#executables',
linkText: 'Go to the Executables section'
}, {
id: 'scripts',
name: 'Scripts',
path: 'scripts/format.sh',
lines: [{
depth: 0,
kind: 'folder',
text: 'scripts/'
}, {
depth: 1,
kind: 'file',
text: 'format.sh'
}],
href: '/en/plugins/components#hooks',
linkText: 'Go to the Hooks section'
}, {
id: 'settings',
name: 'Default settings',
path: 'settings.json',
lines: [{
depth: 0,
kind: 'file',
text: 'settings.json'
}],
href: '/en/plugins/components#default-settings',
linkText: 'Go to the Default settings section'
}, {
id: 'mcp',
name: 'MCP servers',
path: '.mcp.json',
lines: [{
depth: 0,
kind: 'file',
text: '.mcp.json'
}],
href: '/en/plugins/components#mcp-servers',
linkText: 'Go to the MCP servers section'
}, {
id: 'lsp',
name: 'LSP servers',
path: '.lsp.json',
lines: [{
depth: 0,
kind: 'file',
text: '.lsp.json'
}],
href: '/en/plugins/components#lsp-servers',
linkText: 'Go to the LSP servers section'
}];
const [selectedId, setSelectedId] = useState('manifest');
const [isFullscreen, setIsFullscreen] = useState(false);
const rootRef = useRef(null);
useEffect(() => {
const onFsChange = () => setIsFullscreen(!!document.fullscreenElement);
document.addEventListener('fullscreenchange', onFsChange);
return () => document.removeEventListener('fullscreenchange', onFsChange);
}, []);
const toggleFullscreen = () => {
if (!rootRef.current) return;
if (document.fullscreenElement) document.exitFullscreen(); else rootRef.current.requestFullscreen().catch(() => {});
};
const selected = PIECES.find(p => p.id === selectedId) || PIECES[0];
const onTreeKeyDown = e => {
const keys = ['ArrowDown', 'ArrowUp', 'Home', 'End'];
if (keys.indexOf(e.key) === -1) return;
const i = PIECES.findIndex(p => p.id === selectedId);
let next = i;
if (e.key === 'ArrowDown') next = Math.min(PIECES.length - 1, i + 1);
if (e.key === 'ArrowUp') next = Math.max(0, i - 1);
if (e.key === 'Home') next = 0;
if (e.key === 'End') next = PIECES.length - 1;
e.preventDefault();
if (next === i) return;
const id = PIECES[next].id;
setSelectedId(id);
const el = document.getElementById('pe-node-' + id);
if (el) el.focus();
};
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">
<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" />
</svg>;
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">
<path d="M4 1.5h5.5L13 5v9.5H4z" />
<path d="M9.5 1.5V5H13" />
</svg>;
return <div ref={rootRef} className={isFullscreen ? 'pe-root pe-fullscreen not-prose' : 'pe-root not-prose'} data-selected={selected.id}>
<style>{`
.pe-root {
--pe-mono: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
--pe-accent: #D97757;
--pe-accent-text: #A8502F;
--pe-accent-bg: rgba(217,119,87,0.10);
--pe-bg: #FFFFFF;
--pe-surface: #FAFAF7;
--pe-hover: #F0EEE6;
--pe-border: #E8E6DC;
--pe-text: #141413;
--pe-text-2: #3D3D3A;
--pe-text-3: #5E5D59;
font-family: inherit;
background: var(--pe-bg);
color: var(--pe-text);
border: 1px solid var(--pe-border);
border-radius: 12px;
margin: 1.5rem 0;
overflow: hidden;
box-sizing: border-box;
}
.dark .pe-root {
--pe-accent-text: #EBA98F;
--pe-accent-bg: rgba(217,119,87,0.18);
--pe-bg: #1A1918;
--pe-surface: #232221;
--pe-hover: #2E2D2B;
--pe-border: #3A3936;
--pe-text: #F1EFE9;
--pe-text-2: #D6D4CA;
--pe-text-3: #B8B5AD;
}
.pe-root *, .pe-root *::before, .pe-root *::after { box-sizing: border-box; }
.pe-head { display: flex; align-items: flex-start; gap: 12px; padding: 18px 24px 16px; border-bottom: 1px solid var(--pe-border); }
.pe-head-text { flex: 1; min-width: 0; }
.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; }
.pe-fs-btn:hover { background: var(--pe-hover); }
.pe-fs-btn:focus-visible { outline: 2px solid var(--pe-accent); outline-offset: 2px; }
.pe-fullscreen { border-radius: 0; height: 100vh; display: flex; flex-direction: column; overflow: auto; }
.pe-fullscreen .pe-body { flex: 1; }
.pe-title { font-size: 19px; font-weight: 600; line-height: 1.3; color: var(--pe-text); margin: 0; }
.pe-sub { font-size: 15px; line-height: 1.5; color: var(--pe-text-3); margin: 4px 0 0; }
.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); }
.pe-body { display: flex; align-items: stretch; }
.pe-tree-pane { width: 270px; flex-shrink: 0; background: var(--pe-surface); border-right: 1px solid var(--pe-border); padding: 16px 0 12px; }
.pe-panel { flex: 1; min-width: 0; padding: 16px 24px 24px; }
.pe-caption { font-size: 13px; font-weight: 600; color: var(--pe-text-3); margin: 0 0 10px; }
.pe-tree-pane .pe-caption { padding: 0 16px; }
.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); }
.pe-node {
display: block; width: 100%; margin: 0; padding: 3px 16px 3px 30px; text-align: left; cursor: pointer;
background: transparent; color: var(--pe-text-2);
Cut at 300 lines. The page has the rest.
No line in this hunk matches that.