From aida-core
Comprehensive guide to creating, structuring, and distributing Claude Code plugins
npx claudepluginhub joshuarweaver/cascade-ai-ml-agents-misc-2 --plugin oakensoul-aida-core-pluginPlugins are the unit of distribution for Claude Code extensions. They package skills, agents, hooks, MCP servers, LSP servers, output styles, and default settings into installable bundles that can be shared across projects and teams. A plugin is a **distributable package** that can contain: - Skills (process definitions + automation capabilities) - Agents (expert personas / subagent definitions) ...
Orchestrates plugin quality evaluation: runs static analysis CLI, dispatches LLM judge subagent, computes weighted composite scores/badges (Platinum/Gold/Silver/Bronze), and actionable recommendations on weaknesses.
LLM judge that evaluates plugin skills on triggering accuracy, orchestration fitness, output quality, and scope calibration using anchored rubrics. Restricted to read-only file tools.
Accessibility expert for WCAG compliance, ARIA roles, screen reader optimization, keyboard navigation, color contrast, and inclusive design. Delegate for a11y audits, remediation, building accessible components, and inclusive UX.
Plugins are the unit of distribution for Claude Code extensions. They package skills, agents, hooks, MCP servers, LSP servers, output styles, and default settings into installable bundles that can be shared across projects and teams.
A plugin is a distributable package that can contain:
Plugins solve the distribution problem: "How do I share my extensions with others?"
| Approach | Skill Names | Best For |
|---|---|---|
Standalone (.claude/ directory) | /hello | Personal workflows, project-specific customizations, quick experiments |
Plugins (directories with .claude-plugin/plugin.json) | /plugin-name:hello | Sharing with teammates, distributing to community, versioned releases |
Use standalone configuration when:
/helloUse plugins when:
/my-plugin:helloStart with standalone configuration for quick iteration, then convert to a plugin when ready to share.
The only required file is .claude-plugin/plugin.json:
my-plugin/
├── .claude-plugin/
│ └── plugin.json # Plugin manifest (name is the only required field)
└── (any components)
The manifest is technically optional -- Claude Code can auto-discover components in default locations and derive the plugin name from the directory name. Use a manifest when you need metadata or custom component paths.
my-plugin/
├── .claude-plugin/
│ └── plugin.json # Plugin manifest
├── commands/ # Legacy (use skills/ for new development)
│ └── quick-action.md
├── skills/ # Agent Skills with SKILL.md files
│ └── code-review/
│ ├── SKILL.md
│ ├── scripts/
│ │ └── validate.sh
│ └── references/
│ └── checklist.md
├── agents/ # Subagent definitions
│ ├── security-reviewer.md
│ └── performance-tester.md
├── hooks/ # Hook configurations
│ └── hooks.json
├── settings.json # Default settings (e.g., agent key)
├── .mcp.json # MCP server definitions
├── .lsp.json # LSP server configurations
├── outputStyles/ # Output style customizations
│ └── concise.md
├── scripts/ # Utility scripts for hooks/skills
│ ├── format-code.sh
│ └── security-scan.py
├── README.md # Documentation
├── CHANGELOG.md # Version history
├── LICENSE # License file
└── .gitignore
All component directories must be at the plugin root, not inside
.claude-plugin/. Only plugin.json goes inside .claude-plugin/.
| Component | Default Location | Purpose |
|---|---|---|
| Manifest | .claude-plugin/plugin.json | Plugin metadata and configuration |
| Skills | skills/ | Agent Skills with <name>/SKILL.md structure |
| Commands | commands/ | Legacy skill markdown files (still supported) |
| Agents | agents/ | Subagent markdown files |
| Hooks | hooks/hooks.json | Hook configuration |
| MCP servers | .mcp.json | MCP server definitions |
| LSP servers | .lsp.json | Language server configurations |
| Settings | settings.json | Default configuration applied when plugin is enabled |
| Output styles | outputStyles/ | Response formatting customizations |
Both commands/ and skills/ directories are supported:
commands/ contains simple markdown files (legacy format)skills/ contains directories with SKILL.md files (recommended)If a command and skill share the same name, the skill takes precedence.
Use skills/ for new development; commands/ is maintained for backward
compatibility.
If you include a manifest, name is the only required field:
{
"name": "my-plugin"
}
The name is used for namespacing components. For example, a skill hello
in plugin my-plugin becomes /my-plugin:hello.
{
"name": "my-plugin",
"version": "2.1.0",
"description": "Brief description of what this plugin provides",
"author": {
"name": "Your Name or Organization",
"email": "author@example.com",
"url": "https://github.com/username"
},
"license": "MIT",
"repository": "https://github.com/username/my-plugin",
"homepage": "https://your-plugin-docs.example.com",
"keywords": ["keyword1", "keyword2"],
"commands": ["./custom/commands/special.md"],
"agents": "./custom/agents/",
"skills": "./custom/skills/",
"hooks": "./config/hooks.json",
"mcpServers": "./mcp-config.json",
"lspServers": "./.lsp.json",
"outputStyles": "./styles/"
}
| Field | Required | Description |
|---|---|---|
name | Yes | Unique identifier (kebab-case, no spaces) |
version | No | Semantic version (X.Y.Z). If also set in marketplace entry, plugin.json takes priority. |
description | No | One-line description shown in plugin manager |
author | No | Creator info ({name, email, url} object) |
license | No | SPDX license identifier |
repository | No | Source code URL |
homepage | No | Documentation URL |
keywords | No | Discovery tags |
Custom paths supplement default directories -- they do not replace them.
All paths must be relative to plugin root and start with ./.
| Field | Type | Description |
|---|---|---|
commands | string or array | Additional command files/directories |
agents | string or array | Additional agent files |
skills | string or array | Additional skill directories |
hooks | string, array, or object | Hook config paths or inline config |
mcpServers | string, array, or object | MCP config paths or inline config |
lspServers | string, array, or object | LSP config paths or inline config |
outputStyles | string or array | Additional output style files/directories |
Multiple paths can be specified as arrays:
{
"commands": [
"./specialized/deploy.md",
"./utilities/batch-process.md"
],
"agents": [
"./custom-agents/reviewer.md",
"./custom-agents/tester.md"
]
}
Skills live in the skills/ directory. Each skill is a folder containing
a SKILL.md file. The folder name becomes the skill name, prefixed with
the plugin namespace.
skills/
├── code-review/
│ ├── SKILL.md
│ ├── references/
│ └── scripts/
└── deploy/
└── SKILL.md
Plugin skills are automatically discovered when installed. Claude can invoke
them based on task context, and users invoke them via /plugin-name:skill-name.
For complete skill authoring guidance, see knowledge/skills.md.
Agents live in the agents/ directory as markdown files with YAML frontmatter:
---
name: security-reviewer
description: Reviews code for security vulnerabilities
---
You are a security expert. When reviewing code, check for...
Plugin agents appear in the /agents interface and can be invoked
automatically by Claude or manually by users.
Plugin hooks are configured in hooks/hooks.json at the plugin root. The
format is the same as hooks in settings.json:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/format-code.sh"
}
]
}
]
}
}
Hooks can also be defined inline in plugin.json under the hooks field.
All three hook types (command, prompt, agent) are supported in
plugins. For complete hook type documentation, see knowledge/hooks.md.
Use ${CLAUDE_PLUGIN_ROOT} in hook commands to reference files within the
plugin directory (see Environment Variables section below).
MCP server configurations go in .mcp.json at the plugin root, or inline
in plugin.json under mcpServers:
{
"mcpServers": {
"plugin-database": {
"command": "${CLAUDE_PLUGIN_ROOT}/servers/db-server",
"args": ["--config", "${CLAUDE_PLUGIN_ROOT}/config.json"],
"env": {
"DB_PATH": "${CLAUDE_PLUGIN_ROOT}/data"
}
},
"plugin-api-client": {
"command": "npx",
"args": ["@company/mcp-server", "--plugin-mode"],
"cwd": "${CLAUDE_PLUGIN_ROOT}"
}
}
}
Plugin MCP servers start automatically when the plugin is enabled and appear as standard MCP tools in Claude's toolkit.
LSP (Language Server Protocol) plugins give Claude real-time code intelligence: diagnostics after edits, go-to-definition, find references, and hover info.
Configuration goes in .lsp.json at the plugin root, or inline in
plugin.json under lspServers:
{
"go": {
"command": "gopls",
"args": ["serve"],
"extensionToLanguage": {
".go": "go"
}
}
}
Required LSP fields:
| Field | Description |
|---|---|
command | The LSP binary to execute (must be in PATH) |
extensionToLanguage | Maps file extensions to language identifiers |
Optional LSP fields:
| Field | Description |
|---|---|
args | Command-line arguments for the LSP server |
transport | Communication transport: stdio (default) or socket |
env | Environment variables for the server |
initializationOptions | Options passed during server initialization |
settings | Settings via workspace/didChangeConfiguration |
workspaceFolder | Workspace folder path |
startupTimeout | Max time to wait for startup (ms) |
shutdownTimeout | Max time for graceful shutdown (ms) |
restartOnCrash | Auto-restart if server crashes |
maxRestarts | Maximum restart attempts |
Users installing LSP plugins must have the language server binary installed on their machine separately.
Pre-built LSP plugins from the official marketplace:
| Plugin | Language Server | Binary Required |
|---|---|---|
pyright-lsp | Pyright (Python) | pyright-langserver |
typescript-lsp | TypeScript Language Server | typescript-language-server |
rust-analyzer-lsp | rust-analyzer | rust-analyzer |
gopls-lsp | gopls (Go) | gopls |
Plugins can include a settings.json file at the plugin root to apply
default configuration when enabled. Currently, only the agent key is
supported:
{
"agent": "security-reviewer"
}
This activates the named agent from the plugin's agents/ directory as
the main thread, applying its system prompt, tool restrictions, and model.
Settings from settings.json take priority over settings declared in
plugin.json. Unknown keys are silently ignored.
Plugins can include an outputStyles/ directory with markdown files that
customize how Claude responds. Reference them via the outputStyles field
in plugin.json.
The ${CLAUDE_PLUGIN_ROOT} variable contains the absolute path to the
plugin's installation directory. Use it in hooks, MCP servers, and scripts
to ensure correct paths regardless of installation location:
{
"hooks": {
"PostToolUse": [
{
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/process.sh"
}
]
}
]
}
}
This is essential because plugins are copied to a cache location when installed from a marketplace (see Plugin Caching below).
When installing a plugin, the scope determines where it is available:
| Scope | Settings File | Use Case |
|---|---|---|
user | ~/.claude/settings.json | Personal plugins across all projects (default) |
project | .claude/settings.json | Team plugins shared via version control |
local | .claude/settings.local.json | Project-specific plugins, gitignored |
managed | managed-settings.json | Enterprise-managed plugins (read-only) |
Install with a specific scope:
claude plugin install formatter@my-marketplace --scope project
Claude Code provides CLI commands for non-interactive plugin management:
claude plugin install <plugin> [--scope <user|project|local>]
Install a plugin from available marketplaces. Default scope is user.
claude plugin uninstall <plugin> [--scope <user|project|local>]
Remove an installed plugin. Aliases: remove, rm.
claude plugin enable <plugin> [--scope <user|project|local>]
Re-enable a disabled plugin.
claude plugin disable <plugin> [--scope <user|project|local>]
Disable a plugin without uninstalling it.
claude plugin update <plugin> [--scope <user|project|local|managed>]
Update a plugin to the latest version.
claude plugin validate .
Validate plugin or marketplace JSON syntax and structure. Also available
interactively as /plugin validate.
Within Claude Code, use /plugin to open the plugin manager with tabs:
Use the --plugin-dir flag to load plugins during development without
installing them:
claude --plugin-dir ./my-plugin
Load multiple plugins simultaneously:
claude --plugin-dir ./plugin-one --plugin-dir ./plugin-two
Unlike --add-dir which supports live skill reloading, --plugin-dir
requires a restart to detect changes because plugin components (hooks,
MCP servers, settings) are loaded at startup.
Use claude --debug (or /debug in the TUI) to see:
| Issue | Cause | Solution |
|---|---|---|
| Plugin not loading | Invalid plugin.json | Validate with claude plugin validate |
| Commands not appearing | Wrong directory structure | Ensure commands/ at root, not in .claude-plugin/ |
| Hooks not firing | Script not executable | Run chmod +x script.sh |
| MCP server fails | Missing ${CLAUDE_PLUGIN_ROOT} | Use variable for all plugin paths |
| Path errors | Absolute paths used | All paths must be relative, starting with ./ |
| LSP binary not found | Server not installed | Install the language server binary |
Marketplace plugins are copied to ~/.claude/plugins/cache rather than
used in place. This has important implications:
Installed plugins cannot reference files outside their directory. Paths
like ../shared-utils will not work after installation because external
files are not copied to the cache.
Use symbolic links within your plugin directory to reference external files. Symlinks are followed during the copy process:
# Inside your plugin directory
ln -s /path/to/shared-utils ./shared-utils
If plugins are not appearing or behaving unexpectedly:
rm -rf ~/.claude/plugins/cache
Then restart Claude Code and reinstall plugins.
Create .claude-plugin/marketplace.json in your repository root:
{
"name": "company-tools",
"owner": {
"name": "DevTools Team",
"email": "devtools@example.com"
},
"metadata": {
"description": "Internal development tools",
"version": "1.0.0",
"pluginRoot": "./plugins"
},
"plugins": [
{
"name": "code-formatter",
"source": "./plugins/formatter",
"description": "Automatic code formatting",
"version": "2.1.0"
}
]
}
Required marketplace fields:
| Field | Description |
|---|---|
name | Marketplace identifier (kebab-case). Users see it in install commands. |
owner | Maintainer info. name is required, email is optional. |
plugins | Array of plugin entries |
Optional metadata:
| Field | Description |
|---|---|
metadata.description | Brief marketplace description |
metadata.version | Marketplace version |
metadata.pluginRoot | Base directory prepended to relative source paths |
| Source | Type | Fields | Notes |
|---|---|---|---|
| Relative path | string | -- | Local directory within marketplace repo. Must start with ./ |
| GitHub | object | repo, ref?, sha? | "source": "github" |
| Git URL | object | url (must end .git), ref?, sha? | "source": "url" |
| NPM | object | package, version?, registry? | "source": "npm" |
| pip | object | package, version?, registry? | "source": "pip" |
Examples:
{
"name": "local-plugin",
"source": "./plugins/my-plugin"
}
{
"name": "github-plugin",
"source": {
"source": "github",
"repo": "owner/plugin-repo",
"ref": "v2.0.0"
}
}
{
"name": "git-plugin",
"source": {
"source": "url",
"url": "https://gitlab.com/team/plugin.git"
}
}
# GitHub repository
/plugin marketplace add owner/repo
# Git URL (GitLab, Bitbucket, self-hosted)
/plugin marketplace add https://gitlab.com/company/plugins.git
# Specific branch or tag
/plugin marketplace add https://gitlab.com/company/plugins.git#v1.0.0
# Local directory
/plugin marketplace add ./my-marketplace
# Remote URL
/plugin marketplace add https://example.com/marketplace.json
/plugin marketplace list # List all configured marketplaces
/plugin marketplace update X # Refresh plugin listings
/plugin marketplace remove X # Remove a marketplace (uninstalls its plugins)
Configure automatic marketplace installation for projects via
.claude/settings.json:
{
"extraKnownMarketplaces": {
"company-tools": {
"source": {
"source": "github",
"repo": "your-org/claude-plugins"
}
}
},
"enabledPlugins": {
"code-formatter@company-tools": true
}
}
Administrators can restrict allowed marketplaces via strictKnownMarketplaces
in managed settings:
{
"strictKnownMarketplaces": [
{
"source": "github",
"repo": "acme-corp/approved-plugins"
},
{
"source": "hostPattern",
"hostPattern": "^github\\.example\\.com$"
}
]
}
An empty array [] locks down all marketplace additions. Omitting the
field allows unrestricted access.
The strict field in plugin entries controls authority for component
definitions:
| Value | Behavior |
|---|---|
true (default) | plugin.json is authoritative. Marketplace entry can supplement with additional components. |
false | Marketplace entry is the entire definition. Plugin must not have its own component declarations in plugin.json. |
AIDA-specific. This file is part of the AIDA framework, not the Claude Code plugin standard. Only include
aida-config.jsonwhen building plugins for the AIDA ecosystem.
AIDA-specific fields (config and recommendedPermissions) live in a
separate aida-config.json inside .claude-plugin/. This keeps the
standard plugin.json clean for the Claude Code plugin validator.
Plugins can declare user-configurable preferences:
{
"config": {
"label": "My Plugin Settings",
"description": "Configure preferences for My Plugin",
"preferences": [
{
"key": "feature.enabled",
"type": "boolean",
"label": "Enable feature X",
"default": true
},
{
"key": "output.format",
"type": "choice",
"label": "Output format",
"options": ["JSON", "YAML", "Text"],
"default": "JSON"
},
{
"key": "custom.path",
"type": "string",
"label": "Custom output path",
"default": "./output"
}
]
}
}
Supported preference types:
boolean -- on/off togglechoice -- select from a list (options array required)string -- free-text inputPlugins can declare permission recommendations:
{
"recommendedPermissions": {
"git-operations": {
"label": "Git Operations",
"description": "Commit, push, branch, and other git commands",
"rules": [
"Bash(git add:*)",
"Bash(git commit:*)",
"Bash(git push:*)"
],
"suggested": "allow"
}
}
}
Rule format: Tool(command:args). Use * for wildcard arguments.
Follow Semantic Versioning (SemVer):
Breaking changes:
New features (backwards compatible):
Bug fixes (backwards compatible):
Claude Code uses the version to determine whether to update a plugin. If you change code but do not bump the version, existing users will not see changes due to caching.
When version is set in both plugin.json and marketplace.json, the
plugin.json version takes priority silently.
Note: Plugin dependency resolution may not yet be enforced by Claude Code. The version operators below describe the intended behavior. Verify against the current Claude Code release before relying on automatic dependency resolution.
{
"dependencies": {
"base-plugin": "^1.0.0",
"optional-plugin": "~2.3.0"
}
}
| Operator | Meaning | Example |
|---|---|---|
^ | Compatible | ^1.2.3 = >=1.2.3 <2.0.0 |
~ | Patch updates | ~1.2.3 = >=1.2.3 <1.3.0 |
>= | Minimum | >=1.0.0 |
= | Exact | =1.2.3 |
kebab-casereact-testing-tools not testingawesome-plugin is not useful@myorg/plugin-nameInclude in README.md:
Before publishing:
${CLAUDE_PLUGIN_ROOT})claude plugin validate .${CLAUDE_PLUGIN_ROOT} for all file paths in hooks and MCP configsInstalling a plugin grants it significant access:
Review plugin source code before installation. Only install plugins from trusted marketplaces and authors.
linting-plugin/
├── .claude-plugin/
│ └── plugin.json
└── skills/
└── lint/
└── SKILL.md
testing-toolkit/
├── .claude-plugin/
│ └── plugin.json
├── agents/
│ └── test-advisor.md
└── skills/
├── test/
│ └── SKILL.md
├── coverage/
│ └── SKILL.md
└── test-runner/
└── SKILL.md
enterprise-plugin/
├── .claude-plugin/
│ └── plugin.json
├── agents/
│ ├── security-reviewer.md
│ └── compliance-checker.md
├── skills/
│ └── code-review/
│ └── SKILL.md
├── hooks/
│ └── hooks.json
├── settings.json
├── .mcp.json
├── .lsp.json
├── outputStyles/
│ └── detailed.md
├── scripts/
│ ├── security-scan.sh
│ └── format-code.py
├── README.md
├── CHANGELOG.md
└── LICENSE
.claude-plugin/plugin.jsonskills/, agents/, commands/ from .claude/settings.json to hooks/hooks.json (same format)${CLAUDE_PLUGIN_ROOT} referencesclaude --plugin-dir ./my-pluginStandalone (.claude/) | Plugin |
|---|---|
| Only available in one project | Can be shared via marketplaces |
Files in .claude/commands/ | Files in plugin-name/commands/ |
Hooks in settings.json | Hooks in hooks/hooks.json |
Short skill names (/review) | Namespaced names (/plugin:review) |
| Must manually copy to share | Install with claude plugin install |
Problem: Paths break when installed in different locations.
Solution: Use ${CLAUDE_PLUGIN_ROOT} for all paths in hooks, MCP
servers, and scripts:
{
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/process.sh"
}
For Python scripts, use relative path resolution:
plugin_dir = Path(__file__).parent.parent.parent
template_path = plugin_dir / "templates" / "my-template.jinja2"
Problem: Two plugins require incompatible versions of a dependency.
Solution:
Problem: Skills or commands do not appear after installation.
Solution:
.claude-plugin/claude plugin validate .rm -rf ~/.claude/plugins/cache--plugin-dir