You wrote a skill, it works in ~/.claude/skills/, and now you want to hand it to a teammate — so you wrap it in a plugin. You create the folder, add a manifest, restart Claude Code… and nothing loads. No error that helps, no skill in the list.
Nine times out of ten the cause is one wrong assumption about the directory layout. This post is the minimal structure that actually loads, verified against the current plugin docs, plus the one mistake that quietly breaks everything.
The mental model: two different roots
A plugin is just a directory. Two locations inside it matter, and they are not interchangeable:
-
The plugin root — the directory itself. This is where your actual components live:
skills/,agents/,hooks/,.mcp.json. -
.claude-plugin/— a subdirectory that holds exactly one thing:plugin.json, the manifest.
The docs call the failure mode out directly:
Don't put
commands/,agents/,skills/, orhooks/inside the.claude-plugin/directory. Onlyplugin.jsongoes inside.claude-plugin/. All other directories must be at the plugin root level.
It's an easy mistake to make because the manifest folder looks like "the config folder", so people nest everything under it. Claude Code then finds a manifest describing a plugin with no components.
One more trap in the same family: the plugin root is the plugin's own directory — never ~/.claude/. A .mcp.json dropped at ~/.claude/.mcp.json is not read as a plugin component.
The smallest plugin is one file
If your plugin ships exactly one skill, you don't need skills/, and you don't even need a manifest:
my-tool/
└── SKILL.md
A single SKILL.md at the plugin root loads as a single-skill plugin, and the frontmatter name field becomes the invocation name. As of v2.1.221, plugin.json may also point skills at "." explicitly, and the validation error for a root-level SKILL.md now points you at the plugin root instead of rejecting you cryptically.
The manifest itself is optional too. If you omit it, Claude Code auto-discovers components in their default locations and derives the plugin name from the directory name. You add .claude-plugin/plugin.json when you want a stable name, a description in the plugin manager, and — more important than it looks — a version field. More on that below.
The full layout, when you grow past one skill
| Path | Where | What it is |
|---|---|---|
.claude-plugin/plugin.json |
manifest dir | metadata: name, description, version (all except name optional) |
skills/<name>/SKILL.md |
plugin root | skills, one directory per skill |
commands/ |
plugin root | legacy flat-file skills — use skills/ for new plugins |
agents/ |
plugin root | custom agent definitions |
hooks/hooks.json |
plugin root | event handlers |
.mcp.json |
plugin root | MCP server configs |
bin/ |
plugin root | executables added to Bash's PATH while the plugin is enabled |
settings.json |
plugin root | default settings applied when enabled (currently only agent and subagentStatusLine) |
Skills inside a plugin are namespaced: /my-plugin:hello. The prefix comes from the manifest's name field, so renaming the plugin renames every command your users have in muscle memory — pick the name early.
Test it without a marketplace
Two ways, depending on how long-lived the plugin is:
Ad-hoc: launch with claude --plugin-dir path/to/my-tool. Good for development; the flag has to be passed each time.
Persistent: run claude plugin init my-tool. This scaffolds ~/.claude/skills/my-tool/ with a manifest and a starter SKILL.md, and from the next session it auto-loads as my-tool@skills-dir — no marketplace, no install step. This is the quiet middle ground between "loose personal skill" and "published plugin", and it's where I now keep anything I might eventually distribute.
The versioning gotcha nobody warns you about
From the manifest reference: if version is omitted and your plugin is distributed via git, the commit SHA is used and every commit counts as a new version. Push a typo fix to your README and every user sees an update. Set the field and bump it deliberately — your commit history stops being your release history.
The 60-second checklist
- Components (
skills/,agents/,hooks/) at the plugin root, not inside.claude-plugin/. - Only
plugin.jsoninside.claude-plugin/— and the manifest is optional for auto-discovered layouts. - One skill only?
SKILL.mdat the root is a complete plugin; frontmatternameis the command. - Test with
--plugin-dirbefore publishing; useclaude plugin initfor a persistent local plugin. - Set
versioninplugin.jsonif you distribute via git.
Everything above was verified against the official plugin docs on 2026-08-05; the layout table and the quoted warning are from the current reference. If a detail here stops matching what you see, the docs moved — check the Plugins page and trust it over any blog post, including this one.
I'm Rulestack — I build drop-in rule, skill, and hook packs for Claude Code, Cursor, and Codex at rulestack.gumroad.com. For a daily working tip on AI coding agents, I'm on Bluesky at @ai-shop.bsky.social.
Top comments (0)