A Claude Code plugin bundles agents, skills, hooks, and MCP servers into one installable package built around a .claude-plugin/plugin.json manifest. This lesson packages an existing Claude Code setup into a plugin, fixes the three most common breakages (manifest location, hardcoded hook paths, skill namespacing), and covers the three ways to install a plugin.
What you will be able to do
- Package an existing Claude Code setup (agents, skills, hooks, .mcp.json) into a plugin folder with a valid .claude-plugin/plugin.json manifest
- Write portable hook commands using ${CLAUDE_PLUGIN_ROOT} instead of a hardcoded absolute path so hooks work on any machine
- Test a plugin locally with claude --plugin-dir and iterate using /reload-plugins before publishing it
- Install a plugin from the official marketplace, a GitHub repo, or a local directory using the correct plugin marketplace add / plugin install commands
- Namespace skill references correctly (for example /content-ops:research) so they do not collide with another plugin's same-named skill
Before you start
- A working Claude Code setup with agents, skills, and hooks already built (the lesson uses the Lesson 5 content-pipeline-scaffold as its example)
- Claude Code CLI installed and able to run claude --plugin-dir and the /plugin commands
- Comfort editing JSON, for plugin.json and hooks.json
- A GitHub repository (or self-hosted git) if you plan to distribute the plugin via that path rather than a local directory
Reference
| Item | Detail |
|---|---|
| Manifest path | .claude-plugin/plugin.json, only this file goes in the hidden folder; only name is required |
| Hook config | hooks/hooks.json needs an outer "hooks" wrapper; plain .claude/settings.json hook configs do not use this wrapper |
| Portable path variable | ${CLAUDE_PLUGIN_ROOT} resolves to the plugin's install directory; contents written here are wiped on the next version update |
| Persistent data variable | ${CLAUDE_PLUGIN_DATA} resolves to ~/.claude/plugins/data/{plugin-id}/, and survives version updates |
| Local test command | claude --plugin-dir ./content-ops loads the plugin in-place without a marketplace |
| Reload during dev | /reload-plugins picks up edited agent, skill, or hook files without restarting Claude Code |
| Marketplace install | /plugin marketplace add owner/repo-name registers the marketplace, then /plugin install <plugin-name>@<marketplace-name> installs it; pin a version with @v1.2.0 |
| Skill auto-invoke control | Add disable-model-invocation: true to a skill's frontmatter so it only runs when the user types it explicitly |
Common errors and fixes
| What goes wrong | The fix |
|---|---|
Manifest placed outside .claude-plugin/, or other component folders placed inside it |
Only plugin.json belongs in .claude-plugin/; agents/, skills/, hooks/, and .mcp.json live at the plugin root and are auto-discovered |
Hook command uses a hardcoded absolute path like /Users/you/.claude/hooks/format-on-save.sh |
Replace it with ${CLAUDE_PLUGIN_ROOT}/hooks/format-on-save.sh so it resolves on any machine the plugin installs on |
hooks.json is written without the outer "hooks" wrapper |
Plugin hook configs require the wrapper; omitting it makes hooks silently fail to register |
version field is left out of plugin.json while distributing via git |
Every commit becomes a new version and triggers an update prompt for installed users; set an explicit version and bump it intentionally |
Skill referenced as /research inside plugin docs or in another skill body |
Use the namespaced form /content-ops:research, since the un-namespaced name collides with other plugins' skills of the same name |
Read the full walkthrough
The complete lesson, with screenshots and any downloads, is published on Substack as part of Claude Code Masterclass: Build AI Systems Without Writing Code.
More in this section
Continue the course
Browse all lessons in the Claude Code Masterclass: Build AI Systems Without Writing Code course, or subscribe to the GenAI Unplugged newsletter to get new lessons in your inbox.