A plugin is a portable set of components: skills, agents, hooks, MCP servers, LSP servers, monitors and some settings, packed together. The choice between a standalone .claude and a plugin is simple by the portability criterion: for one repository an ordinary .claude suffices, and a plugin is needed when a component must be versioned, installed and updated across several projects or teams at once. A plugin turns scattered customization into a distributable product with a version.
A plugin's structure has one important subtlety. The component directories - skills, agents, hooks, scripts, monitors, and also .mcp.json and .lsp.json - lie in the plugin root, not inside .claude-plugin: there is only the plugin.json manifest, setting the identity, metadata and optional component paths. It helps to see this layout once. For stable references inside the installed version you use the CLAUDE_PLUGIN_ROOT variable, not the current working directory.
Local plugin development is simple and predictable. The --plugin-dir flag attaches a directory (or a ZIP), and after editing components you call /reload-plugins; --plugin-url loads a trusted archive for the session. Errors are viewed on the Errors tab in /plugin and via claude --debug. A local --plugin-dir usually has priority over a marketplace copy - except a managed forced state, when the organization rigidly sets the version. This is handy for iterative development: you edit and reload at once.
A marketplace is an index of plugins, not a guarantee of each set's quality. The /plugin command gives discover, install, enable, disable and update, and the claude plugin CLI is handy for automation. An admin can restrict the known marketplaces and force-enable or disable plugins. The key reproducibility rule is to pin the version or commit where stability matters more than automatic updates. A plugin's presence in a marketplace does not mean someone checked its security.
LSP adds language intelligence. The .lsp.json file binds an executable language server to file extensions, but the binary itself (gopls, a language server and the like) the user installs themselves. After edits the LSP gives diagnostics, go-to-definition, reference search and type information. It helps to see such a binding once. For common languages the documentation advises ready marketplace plugins, and your own .lsp.json is needed only for a language not among the ready ones.
The plugin's other components - monitors, themes and channels - carry both benefit and risk. A monitor runs a background command and passes each of its stdout lines as a notification to Claude; but its output is untrusted input, so you limit the source, the rate and retention. Themes change the terminal's look but not permissions. Channels let an external source send messages into the session - this is a handy event ingress and at the same time a prompt-injection boundary that must not be forgotten.
A plugin's settings have a limit important to know. A plugin's root settings.json in the current documentation supports only a limited set of default keys; do not expect an arbitrary settings key from a plugin to be applied. This is protection: a plugin must not silently change your environment's arbitrary policy. Everything that really affects behavior - hooks, MCP, agents - stays visible and checkable rather than hidden in the shared settings.
Before releasing a plugin you go through a checklist, because a plugin is executable code that will travel to others. It helps to gather it once: correct manifest identity and version; all paths via CLAUDE_PLUGIN_ROOT; commands and scripts work without a user shell profile; hooks have a timeout and negative tests; MCP and LSP dependencies are documented; no secrets, absolute machine paths or unpinned remote code; /reload-plugins, the Errors tab and --debug are clean; the README describes permissions, data flow, installation, update and removal.
my-plugin/
├── .claude-plugin/
│ └── plugin.json # the manifest only
├── skills/ # components - in the plugin ROOT
│ └── review/SKILL.md
├── agents/
├── hooks/
│ └── hooks.json
├── .mcp.json
├── .lsp.json
├── monitors/
├── settings.json # a limited set of keys
└── README.md
# paths inside - via CLAUDE_PLUGIN_ROOT# Plugin release checklist
[ ] manifest identity and version are correct
[ ] all paths via CLAUDE_PLUGIN_ROOT
[ ] commands/scripts work without a user shell profile
[ ] hooks have a timeout and negative tests
[ ] MCP/LSP dependencies are documented
[ ] no secrets, absolute paths or unpinned remote code
[ ] /reload-plugins, /plugin Errors and --debug are clean
[ ] README: permissions, data flow, install, update, remove