Claude Code Learning Hub
中文 Mingyu's Library

Hub / Course / D14

D14 · Plugins: packaging & distribution

Pack the Skills, Hooks, MCP, and Subagents you've learned into an installable, versionable plugin, and distribute it to your team and the community through a marketplace — the finale of the 14-day course.

The first 13 lessons taught you to add capabilities to your own Claude Code; this one teaches you to package those capabilities as a plugin, so a new machine, a new project, or a new colleague never means re-copying config by hand. By the end you'll have closed the loop from "I can use it" to "the whole team uses it".

Why this lesson comes today

Look back at the syllabus: D7 taught Skills, D8 taught Hooks, D9 taught MCP, D10 taught Subagents. These four capabilities share one trait: their configuration is scattered across your local .claude/ directory or settings.json. To share them with a colleague, you'd have to copy files by hand — no versions, no update mechanism, no install command.

OfficialPlugins are the official packaging answer: a self-contained directory that bundles skills, agents, hooks, MCP servers, LSP servers and other components, and — with a manifest — can be installed, updated, and distributed through a marketplace. Our takeIt sits in the final lesson because a plugin introduces no new capability of its own — it's the shipping container for everything that came before; understand the contents first, then learn the packing, and the order makes sense. D13's CI scenario also gets its closing piece here: the official seed-directory mechanism lets container images pre-install plugins at build time.

Core concepts, explained

One box for everything you've learned

OfficialPhysically, a plugin is just a directory: metadata lives in .claude-plugin/plugin.json (the manifest), and every other component sits in its default location under the plugin root. The docs warn specifically: .claude-plugin/ holds only plugin.json — stuffing skills/, agents/, hooks/ and the like into it is the most common structural mistake.

my-plugin/ — plugin root .claude-plugin/plugin.json manifest: name / version / description This subdirectory holds only the manifest; all components below live in the plugin root skills/<name>/SKILL.md Skills · from D7 hooks/hooks.json Event hooks · from D8 .mcp.json MCP servers · from D9 agents/*.md Subagents · from D10 .lsp.json · bin/ LSP code intelligence · executables added to PATH settings.json · monitors/ etc. Defaults, background monitors, themes, workflows
What's inside a plugin: the manifest lives alone in .claude-plugin/; components all sit at the root. The first four components are exactly the capabilities you learned in D7–D10.

OfficialThe manifest itself is optional: without one, Claude Code auto-discovers components in their default locations and the plugin takes its directory name; with one, only name is required. Skills inside a plugin carry a namespace prefix — hello inside plugin my-plugin becomes /my-plugin:hello — so same-named skills in different plugins don't collide.

Standalone config or a plugin?

OfficialThe docs give explicit selection criteria:

ApproachSkill nameBest for
Standalone config (.claude/ directory)/helloPersonal workflows, single-project customization, quick experiments
Plugin (self-contained directory + manifest)/my-plugin:helloSharing with a team/community, cross-project reuse, versioned releases, marketplace distribution

OfficialThe recommended path: iterate quickly in .claude/ first, then convert to a plugin to share once it matures. Migration is mostly copying .claude/skills/ and agents/ into the plugin root, and moving hooks from settings into hooks/hooks.json (same format). Our takeRemember to delete the originals from .claude/ after migrating: a same-named agent gets overridden by the project-level definition, so the plugin version would never take effect. For choosing among the five extension mechanisms, see the tip Skill / Hook / MCP / Subagent / Plugin: which to use.

Analogy: a marketplace is an app store, a plugin is an app

OfficialThe docs use this analogy themselves: adding a marketplace is like adding an app store — you've only registered the catalog and can browse it, nothing is installed yet; from it you then pick individual plugins to install. So distribution is always two steps: /plugin marketplace add registers the catalog, /plugin install plugin-name@marketplace-name installs.

A marketplace's physical form is .claude-plugin/marketplace.json at the repo root, with three required fields: name, owner, plugins; each entry in the plugins array needs at least name and source. source supports five kinds: an in-repo relative path (./plugins/xx), github (owner/repo), url (any git address), git-subdir (a monorepo subdirectory, sparse clone), and npm (an npm package). OfficialKeep the two layers straight: the marketplace source is "where the catalog itself lives"; a plugin source is "where each plugin in the catalog is fetched from" — the two can point at different repos and pin versions independently.

Dev directory --plugin-dir testing marketplace marketplace.json User adds market marketplace add Install to cache copy into versioned cache list in catalog & push share owner/repo /plugin install New release: bump version (or push a new commit if version is unset) → users pull via /plugin update
The distribution chain from development to a user's machine. Installation is not an in-place reference — the plugin is copied into ~/.claude/plugins/cache.

Versioning and the cache: the two easiest traps to hit

Trap one: "I pushed a new commit, so users will get the update." OfficialVersions resolve in this order: 1. version in plugin.json → 2. version on the marketplace entry → 3. the git commit SHA of the plugin source. Once you've written "version": "1.0.0", no number of extra commits matters — until you bump that field, users running /plugin update just get "already up to date": the version string is the cache key. Official advice: fast-iterating internal plugins should simply omit version, so every commit counts as a new release; explicit semantic versions are for stable public releases. Also don't set it in both plugin.json and the marketplace entry: the former silently wins, so a stale manifest version will mask the number you changed in the catalog.

Trap two: "a plugin can reference files elsewhere in the repo." OfficialWhen installed from a marketplace, the plugin directory is copied into the local cache at ~/.claude/plugins/cache, not used in place. Files reached through out-of-bounds paths like ../shared-utils don't get copied, so they are guaranteed broken after install. To reference the plugin's own files, use the ${CLAUDE_PLUGIN_ROOT} variable (it points at the install directory and changes on every update); for data that must persist across versions and updates (such as node_modules or caches), use ${CLAUDE_PLUGIN_DATA}. If files truly must be shared within one marketplace, the official escape hatch is symlinks.

Three routes for team distribution

Our takeOrdered from smallest audience to largest:

  1. Bundled with the project repo (the team default). OfficialWrite extraKnownMarketplaces (declaring the marketplace) plus enabledPlugins (declaring which plugins are enabled by default) into the project's .claude/settings.json; teammates get an install prompt once they trust the directory. The install scopes confirm this design: user (you, all projects), project (written into .claude/settings.json, shared with the repo), local (you, this project), managed (admin-controlled, read-only).
  2. A private marketplace repo (company-internal). OfficialInstalling from private git repos is supported — git permissions are your access control; GitHub, GitLab, and self-hosted git all work. For CI/container scenarios, CLAUDE_CODE_PLUGIN_SEED_DIR pre-seeds the marketplace and plugin cache at image build time, for zero-clone startup at runtime — the companion piece to D13's headless mode.
  3. Public release (community). OfficialAnthropic maintains two public marketplaces: claude-plugins-official is curated by Anthropic, auto-registered on first interactive startup, with no submission channel; claude-community accepts third-party submissions, included after automated validation and security screening, and added manually by users with /plugin marketplace add anthropics/claude-plugins-community. Submissions go through the form on claude.ai or the Console; run claude plugin validate ./your-plugin locally before submitting.
Security noteOfficialPlugins and marketplaces are high-trust components that can execute arbitrary code with your user permissions; install only from sources you trust. Before installing, claude plugin details plugin-name shows its component inventory and estimated token overhead; organizations can control which marketplaces may be added with strictKnownMarketplaces.

Hands-on: doable today

Walk the official quickstart end to end — create the plugin → test locally → create a marketplace → install — about 30 minutes.

  1. Create the plugin directory and manifest. Expected result: my-first-plugin/.claude-plugin/plugin.json:
    mkdir -p my-first-plugin/.claude-plugin
    cat > my-first-plugin/.claude-plugin/plugin.json <<'EOF'
    {
      "name": "my-first-plugin",
      "description": "A greeting plugin to learn the basics",
      "version": "1.0.0"
    }
    EOF
  2. Add a skill. Create my-first-plugin/skills/hello/SKILL.md:
    ---
    description: Greet the user with a friendly message
    disable-model-invocation: true
    ---
    
    Greet the user warmly and ask how you can help them today.
  3. Load it locally for testing: start claude --plugin-dir ./my-first-plugin, then type /my-first-plugin:hello. Expected: Claude replies with a greeting; the Custom commands tab of /help lists the skill under its namespace. After edits, run /reload-plugins to hot-reload — no restart needed.
  4. Validate the structure: claude plugin validate ./my-first-plugin. Expected: it prints Validation passed with a check mark; add --strict to treat warnings as errors too — good for CI.
  5. Create a local marketplace and install from it. First build the catalog:
    mkdir -p my-marketplace/.claude-plugin
    mv my-first-plugin my-marketplace/plugins/
    cat > my-marketplace/.claude-plugin/marketplace.json <<'EOF'
    {
      "name": "my-plugins",
      "owner": { "name": "Your Name" },
      "plugins": [
        {
          "name": "my-first-plugin",
          "source": "./plugins/my-first-plugin",
          "description": "A greeting plugin to learn the basics"
        }
      ]
    }
    EOF
    Then, inside Claude Code, run /plugin marketplace add ./my-marketplace, then /plugin install my-first-plugin@my-plugins, and pick a scope on the details page that appears. Expected: the install summary shows the plugin is active, or prompts Run /reload-plugins to activate. — do so. To distribute publicly, push my-marketplace to GitHub, and others can add it with /plugin marketplace add owner/repo.
  6. Package your own existing config. Let Claude Code do the work with the prompt below:
Migrate the skills, agents, and the hooks in settings from this project's .claude/ directory into a Claude Code plugin named team-toolkit:
1. Create team-toolkit/.claude-plugin/plugin.json, leaving out the version field for now (use commit SHAs as versions to make iteration easy);
2. Copy .claude/skills/ and .claude/agents/ into the plugin root, move the hooks config into hooks/hooks.json, and switch script paths to ${CLAUDE_PLUGIN_ROOT};
3. Run claude plugin validate ./team-toolkit and make it pass, fixing the warnings too;
4. List which originals I should delete from .claude/ after the migration to avoid same-name overrides — don't delete anything yet.

How to know you've learned it

  • You can articulate the difference and trade-offs between standalone config (.claude/) and a plugin: namespacing (/hello vs /plugin:hello), distributability, versioning.
  • You can sketch the plugin directory layout and point out the most common mistake — putting component directories inside .claude-plugin/.
  • You've run your first plugin with --plugin-dir, passed claude plugin validate, and completed one install from a local marketplace.
  • You can explain what /plugin marketplace add and /plugin install each do, and the difference between a marketplace source and a plugin source.
  • Self-test: you shipped with "version": "1.0.0" in plugin.json, then pushed three bug-fix commits. What do users see when they run /plugin update, and how do you fix it? (Answer: they see "already up to date", because the version string is the cache key and it hasn't changed; either bump version on every release, or delete the field and let the commit SHA serve as the version.)

And that wraps the 14-day course. The through-line, looking back: D1–D6 laid the foundations (the loop, context, permissions, memory, planning, sessions), D7–D10 the four extension mechanisms, D11–D13 parallelism and automation, D14 packaging and distribution. From "using it" to "using it well" to "getting the whole team on it" — the loop is closed.