The Navigator · 11 min mission

Plugins & Marketplaces: Package and Ship Your Whole Setup

Bundle skills, agents, hooks, and MCP into one installable plugin — and ship it to your team.

pluginsdistributioncustomizationFact-checked 2026-06-13
On this page

Your Claude Code setup is never just one file. After a few months it is a code-review skill, two subagents, a PostToolUse hook that runs your formatter, three slash commands, and a .mcp.json that wires up Sentry and your internal API. It works beautifully — on your machine. A new engineer joins and you spend an afternoon copy-pasting fragments of your .claude/ directory into theirs over Slack, hoping you didn't miss the hook script.

A plugin deletes that afternoon. It bundles skills, agents, hooks, slash commands, MCP servers, LSP servers, and more into one self-contained, versioned directory. A marketplace is the catalog that distributes it. The payoff is a single line: your teammate runs /plugin install team-standard@your-org, runs /reload-plugins, and now has your entire setup — same skills, same hooks, same MCP wiring — pinned to the same version. [V]

What a plugin is

A plugin is a self-contained directory of components that extends Claude Code. The components are the same building blocks you already use standalone — skills, agents, hooks, MCP servers, LSP servers, background monitors — just packaged so they travel as a unit. [V]

The official docs frame the choice as standalone vs. plugin. Standalone configuration lives loose in your .claude/ directory: skill names are short (/deploy), it is perfect for a single project or a quick experiment, and nothing is shared. A plugin is what you reach for when you want to share with a team or community, reuse the same setup across many projects, and ship versioned releases. The one trade-off: plugin components are namespaced by the plugin name — a hello skill in a plugin named my-first-plugin invokes as /my-first-plugin:hello. That namespacing is deliberate: it prevents two plugins that both ship a commit skill from colliding. [V]

The honest guidance from Anthropic is to start standalone in .claude/ for fast iteration, then convert to a plugin when you're ready to share. [V] You do not design a plugin up front; you grow one out of the setup you already trust.

Anatomy: the one rule that trips everyone up

There is exactly one structural rule, and it is the single most common mistake the docs call out: only plugin.json goes inside .claude-plugin/. Every component directory lives at the plugin root, not inside .claude-plugin/. [V] Put your skills/ folder inside .claude-plugin/ and the plugin loads but its skills silently vanish.

Here is the standard layout. The manifest is tucked into .claude-plugin/; everything else sits at the root:

text
team-standard/
├── .claude-plugin/
│   └── plugin.json        ← the ONLY thing in here
├── skills/                ← <name>/SKILL.md directories
│   └── code-review/
│       └── SKILL.md
├── agents/                ← subagent .md files
│   └── security-reviewer.md
├── commands/              ← flat .md slash commands (use skills/ for new work)
│   └── deploy.md
├── hooks/
│   └── hooks.json         ← event handlers
├── .mcp.json              ← MCP server definitions
├── .lsp.json              ← LSP server configs
├── bin/                   ← executables added to the Bash PATH while enabled
├── settings.json          ← default settings applied when enabled
└── monitors/
    └── monitors.json      ← background monitors

Each default location is auto-discovered — you do not have to register skills/ or agents/ anywhere; Claude Code scans them. The manifest itself is optional: drop a plugin directory with a skills/ folder and no plugin.json, and Claude Code derives the name from the directory name and loads the components from their default locations. You add a manifest when you need real metadata (a version, an author) or custom component paths. [V]

scaffold, test, iterate
… scroll to run this session
Build a plugin from your existing config, test it instantly with --plugin-dir (no install), and reload changes mid-session with /reload-plugins. The local copy wins over any installed plugin of the same name for that session.

The plugin.json manifest

The manifest at .claude-plugin/plugin.json is the plugin's identity card. name is the only required field — it must be kebab-case with no spaces, and it is what namespaces every component (name's value becomes the name:component prefix). Everything else is optional metadata: description (shown in the plugin picker), version, author, homepage, repository, license, and keywords. [V]

json
{
  "name": "team-standard",
  "displayName": "Team Standard Setup",
  "version": "1.4.0",
  "description": "Our house skills, review agent, formatter hook, and Sentry MCP",
  "author": { "name": "Platform Team", "email": "platform@acme.com" },
  "homepage": "https://docs.acme.com/claude",
  "license": "MIT",
  "keywords": ["review", "formatting", "internal"],
  "defaultEnabled": true
}

Two fields carry real behavioral weight. version is the cache key Claude Code uses to decide whether an update is available. Set it, and users only receive changes when you bump the string — pushing new commits without bumping it does nothing, and /plugin update reports "already at the latest version." Omit it, and for git-hosted sources the commit SHA becomes the version, so every commit ships. The practical rule [P]: pin an explicit semver version for published, stable plugins; leave it unset for internal plugins you iterate on daily. [V]

defaultEnabled (Claude Code v2.1.154+) controls whether the plugin starts enabled. It defaults to true. Set "defaultEnabled": false to ship a plugin that installs disabled until the user opts in with claude plugin enable <plugin> — the right move for a plugin that adds cost or connects to an external service the user should choose to turn on. One subtlety: a user's own enable/disable setting always wins over defaultEnabled, and it persists across updates, so flipping the default in a later release never overrides someone's existing choice. [V]

userConfig: prompt for values instead of making users edit JSON

When your plugin needs an API endpoint or a token, do not tell users to hand-edit settings.json. Declare a userConfig block and Claude Code prompts them for the values when the plugin is enabled. [V]

json
{
  "name": "team-standard",
  "userConfig": {
    "api_endpoint": {
      "type": "string",
      "title": "API endpoint",
      "description": "Your team's internal API base URL"
    },
    "api_token": {
      "type": "string",
      "title": "API token",
      "description": "Token for the internal API",
      "sensitive": true
    }
  }
}

Each option needs type (string, number, boolean, directory, or file), title, and description. The load-bearing flag is "sensitive": true: it masks the input and stores the value in the system keychain (falling back to ~/.claude/.credentials.json where no keychain exists) instead of writing it to settings.json in plaintext. [V]

The values flow everywhere they're useful: substitute them as ${user_config.KEY} inside MCP and LSP configs, hook commands, and monitor commands; non-sensitive ones can also appear in skill and agent content; and all of them are exported to plugin subprocesses as CLAUDE_PLUGIN_OPTION_<KEY> environment variables. One real constraint to respect: keychain storage is shared with OAuth tokens and capped around 2 KB total, so keep sensitive values small — store a token, not a giant JSON blob. [V]

Build a plugin skeleton

Plugin structure builder

Pick what your plugin bundles and name it — the directory tree, the plugin.json manifest, and the resolved namespaced invocation names all rewrite themselves as you click. Components live at the plugin root; only the manifest sits in .claude-plugin/.

Plugin identity

The name is kebab-case and namespaces every component. Pinning a semantic version means users only update when you bump it.

Bundled components

Toggle what ships inside the plugin. Each enabled component adds a folder or file at the plugin root.

deploy-kit/ · directory tree
deploy-kit/
├── .claude-plugin/
│ └── plugin.json
├── commands/
│ └── deploy.md
├── agents/
│ └── security-reviewer.md
├── skills/
│ └── review-pr/
│ └── SKILL.md
└── hooks/
└── hooks.json
.claude-plugin/plugin.json
{
"name": "deploy-kit",
"version": "1.0.0",
"description": "Ship our deploy commands, review agent, and CI hooks as one install"
}
resolved invocations

Slash command

run in the prompt/deploy-kit:deploy

Subagent

mention@agent-deploy-kit:security-reviewer
or launchclaude --agent deploy-kit:security-reviewer

Skill

auto-discovered by taskdeploy-kit:review-pr
Toggle the components you want to bundle and see the exact directory tree and plugin.json it produces — with everything in the right place (and nothing wrongly nested under .claude-plugin/).

Installing plugins

Once a plugin lives in a marketplace, installing it is one command. The canonical form names both the plugin and its marketplace:

shell
/plugin install team-standard@your-org

That installs to user scope by default (available to you across all projects). To install for the whole team via version control, use project scope so it's written to .claude/settings.json; for a private, gitignored install use local scope. From the CLI the flag is --scope: claude plugin install team-standard@your-org --scope project. [V]

The interactive front door is /plugin, a tabbed manager you cycle with Tab: Discover (browse plugins from all your marketplaces), Installed (enable, disable, uninstall, favorite), Marketplaces (add, update, remove), and Errors (load failures). The Discover detail pane is genuinely useful before you commit: it shows a Context cost estimate (how many tokens the plugin adds every turn), a Last updated date, and a Will install section listing exactly which commands, agents, skills, hooks, and MCP/LSP servers it adds — so you review the blast radius before installing. [V]

For development and CI you skip marketplaces entirely. --plugin-dir ./my-plugin loads a local plugin directory (or a .zip) for the session only, with no install step — repeat the flag to load several at once. --plugin-url https://…/plugin.zip fetches a hosted archive (a CI build artifact, say) at startup for that session only. After any change, /reload-plugins picks up edits without a restart. [V]

MethodWhat it doesPersists?Best for
/plugin install name@mktInstalls from an added marketplaceYesReal use — team and personal setups
/plugin UIBrowse, see token cost + Will install, pick scopeYesReviewing a plugin before committing to it
--plugin-dir ./pLoads a local dir or .zip, no installNo (session only)Developing and testing your own plugin
--plugin-url https://…Fetches a hosted archive at startupNo (session only)CI build artifacts; trusted hosted zips
Four ways to get a plugin into a session. Marketplace installs persist; the two flags are session-only and ideal for development and CI.

Marketplaces: the catalog that distributes plugins

A marketplace is a catalog of plugins, defined by a single file: .claude-plugin/marketplace.json at the repo root. Using one is a deliberate two-step act — add the marketplace (register the catalog; nothing installs yet), then install individual plugins from it. It is exactly like adding an app store, then choosing which apps to download. [V]

The file needs three things: a name (kebab-case, public-facing — users type it after @), an owner, and a plugins array. Each plugin entry needs at minimum a name and a source that says where to fetch it:

json
{
  "name": "your-org",
  "owner": { "name": "Platform Team", "email": "platform@acme.com" },
  "plugins": [
    {
      "name": "team-standard",
      "source": "./plugins/team-standard",
      "description": "House skills, review agent, formatter hook, Sentry MCP",
      "version": "1.4.0"
    },
    {
      "name": "deploy-tools",
      "source": { "source": "github", "repo": "acme/deploy-plugin", "sha": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0" }
    }
  ]
}

Users add this catalog with /plugin marketplace add, which accepts a GitHub owner/repo shorthand, any git URL (GitLab, Bitbucket, self-hosted — include .git), a local path, or a direct URL to a hosted marketplace.json. To pin the catalog to a branch or tag, append @ref to the shorthand or #ref to a git URL. [V]

`source`Required fieldsPin fieldsUse it for
Relative path "./p"string starting with ./commit SHA of the marketplace repoPlugins living in the same repo as the marketplace
githubrepo (owner/repo)ref, shaPlugins in a separate GitHub repo
urlurl (any git URL)ref, shaGitLab, Bitbucket, self-hosted git
git-subdirurl, pathref, shaA plugin inside a monorepo (sparse clone)
npmpackageversion, registryPlugins published to npm (public or private)
Plugin source types in a marketplace entry. The git-based sources (github, url, git-subdir) accept ref (branch/tag) and sha (exact commit); when both are set, sha is the effective pin.

Validate and inspect before you ship

Two commands turn "I think this works" into "I verified it works." claude plugin validate checks JSON syntax and schema. Pointed at a marketplace directory it validates marketplace.json — schema, duplicate plugin names, source path traversal (..), and version mismatches against each referenced plugin.json. Pointed at a plugin directory it validates that plugin's plugin.json plus its skill, agent, command, and hooks/hooks.json frontmatter. Add --strict to treat warnings — like a misspelled or leftover field — as errors, which is exactly what you want in CI before publishing. [V]

shell
claude plugin validate ./plugins/team-standard --strict

The second command answers the question every reviewer should ask: what does this cost me every turn? claude plugin details <name> prints the full component inventory grouped as Skills, Agents, Hooks, MCP servers, and LSP servers, plus a projected token cost split into always-on (tokens added to every session just by the plugin's listing text — skill descriptions, agent descriptions, command names) and on-invoke (tokens a component costs only when it fires). The always-on total is computed via the count_tokens API for your active model, so it reflects reality, not a guess. [V] A plugin with twelve verbose skill descriptions is not free — plugin details is how you see the bill before you pay it every turn.

validate, then read the token bill
… scroll to run this session
Validate strictly in CI, then inspect what the plugin actually costs per session before shipping it to the whole team.

Senior scenario: ship the team standard as one versioned plugin

You lead platform for a fifteen-engineer team. Today, "set up Claude Code" is a wiki page nobody fully follows: people miss the formatter hook, run stale review prompts, and half of them never wired up the Sentry MCP. You want one source of truth that everyone installs in one command and that you can version like any other internal package.

The build. You create an internal repo acme/claude-plugins. Inside it, plugins/team-standard/ holds a .claude-plugin/plugin.json ("version": "1.4.0", defaultEnabled: true), a skills/ directory with your code-review and scaffold skills, an agents/security-reviewer.md, a hooks/hooks.json that runs your formatter on Write|Edit, and a .mcp.json for Sentry whose token you declare in userConfig with "sensitive": true so it lands in the keychain, never in git. At the repo root, .claude-plugin/marketplace.json lists the plugin with "source": "./plugins/team-standard". You run claude plugin validate . --strict in CI on every PR.

The distribution. You commit a project-level .claude/settings.json to your application repos that declares the marketplace and the enabled plugin, so a teammate who clones and trusts the repo is prompted to install it automatically:

json
{
  "extraKnownMarketplaces": {
    "your-org": { "source": { "source": "github", "repo": "acme/claude-plugins" } }
  },
  "enabledPlugins": { "team-standard@your-org": true }
}

The result. A new hire clones the repo, trusts the folder, accepts the prompt — and has your entire standard. When you fix a review prompt, you bump version to 1.4.1 and push; everyone's next /plugin update (or startup auto-update, which is on by default for official marketplaces and opt-in per-entry for yours) pulls it. The wiki page is gone. The setup is now a versioned artifact. [V]

Two versioning strategies — pick deliberately

Explicit version (pinned)

Set "version": "1.4.0" in plugin.json. Users get an update only when you bump the string. Pushing commits without bumping does nothing — /plugin update says "already at the latest version." Best for published plugins with real release cycles. The catch: you must remember to bump on every release, or your fix never reaches anyone. [V]

Commit-SHA version (rolling)

Omit version from both plugin.json and the marketplace entry. For git-hosted sources the commit SHA becomes the version, so every new commit ships to users automatically. Best for internal or actively-developed plugins where you want changes out the moment you push. No bump to forget. [V]

Enterprise control: lock down where plugins come from

A plugin runs arbitrary code on the machine with the user's privileges — that is the whole point, and also the whole risk. Anthropic's guidance is blunt: only install plugins and add marketplaces from sources you trust, because Anthropic does not control what's inside third-party plugins and cannot verify they work as intended. [V] For organizations that need more than guidance, managed settings provide hard controls.

extraKnownMarketplaces pre-registers marketplaces so users don't have to run /plugin marketplace add — pair it with enabledPlugins to declare which plugins are on by default. Set in a project's .claude/settings.json it prompts teammates on folder-trust; set in org-level managed settings it applies fleet-wide. [V]

strictKnownMarketplaces is the allowlist. Its three states are the whole policy: undefined means no restriction (the default); an empty array [] is a complete lockdown — users cannot add any new marketplace; a list of sources means users may add only marketplaces that match the allowlist. Entries are matched exactly for github/url sources, or by regex via hostPattern (for a self-hosted git host) and pathPattern (for filesystem sources). [V]

json
{
  "strictKnownMarketplaces": [
    { "source": "github", "repo": "acme-corp/approved-plugins" },
    { "source": "hostPattern", "hostPattern": "^github\\.acme\\.com$" }
  ]
}

Because it lives in managed settings, individual users and project configs cannot override it — the check runs before any network or filesystem operation, on add and on every install, update, and auto-update. A related blockedMarketplaces denies specific sources. Together they let an org say: "Claude Code, yes — but only plugins from our catalog." [V]

From loose config to shipped plugin

  1. Grow it standalone first

    Build and refine your skills, agents, and hooks in .claude/ until you trust them. Iteration is faster when there is no namespacing and no install step. [V]

  2. Convert to a plugin directory

    Make a plugin folder, add .claude-plugin/plugin.json with a name, and copy your skills/, agents/, and hooks/ to the root (not into .claude-plugin/). Move secrets into a userConfig block with "sensitive": true. [V]

  3. Test locally with --plugin-dir

    Run claude --plugin-dir ./team-standard, exercise each component (/name:skill, check /agents, trigger the hook), and /reload-plugins after edits. No install needed. [V]

  4. Validate strictly

    Run claude plugin validate ./team-standard --strict and inspect claude plugin details to see the per-session token cost. Wire the validate step into CI. [V]

  5. Publish via a marketplace

    Create .claude-plugin/marketplace.json listing the plugin with a source, host it on git, and have teammates run /plugin marketplace add owner/repo then /plugin install team-standard@your-org. Decide your version strategy: pinned version or rolling commit-SHA. [V]

Knowledge check

You publish a team plugin with "version": "1.3.0" in plugin.json. You fix a bug, commit, and push to the marketplace repo. Teammates run /plugin update but report they still have the old behavior. What happened?

Reach the end and this star joins your charted sky.