/ CLAUDE-CODE, DEVELOPER-TOOLS, AI-AGENTS, AUTOMATION, AI

Claude Code plugin marketplaces: architecture, packaging, and production rollout

Claude Code plugins let you package skills, slash commands, subagents, hooks, and MCP servers into a single distributable unit. A plugin marketplace is what turns a folder of plugins into something a team can discover, install, and keep updated with one command. This post walks through the architecture, how to package a first plugin, and what it takes to run a marketplace safely in production.


Purpose and usages

A marketplace is a catalog: a marketplace.json manifest listing one or more plugins, each pointing at a source (a local path, a git repo, an npm package, or a signed archive). It solves four problems that come up the moment more than one person needs the same Claude Code extensions:

  • Centralized discovery — /plugin marketplace add owner/repo makes every plugin in that repo installable by name, instead of everyone copying files by hand.
  • Version tracking — plugin entries pin a version, and git or archive sources can pin an exact sha / sha256 so an update never silently changes behavior.
  • Automatic updates — /plugin marketplace update and /plugin update pull the latest manifest and re-resolve sources without touching each machine manually.
  • Multi-source support — the same marketplace can mix GitHub repos, local paths (handy during development), npm packages, and authenticated archives.

💡 Tip: A marketplace doesn’t have to be public. A private GitHub repo works fine as a marketplace source, and Claude Code reuses your existing git credentials to resolve it.

In practice, this is the mechanism behind team-wide conventions: a shared code-review skill, an internal deployment-tools command set, or an MCP server that talks to an internal service, all installed the same way across every engineer’s Claude Code.


Architecture and file tree

A marketplace repository follows a fixed layout. The .claude-plugin/ directory is what makes a folder resolvable as a marketplace or a plugin:

marketplace/
├── .claude-plugin/
│   └── marketplace.json          # Marketplace catalog
└── plugins/
    └── plugin-name/
        ├── .claude-plugin/
        │   └── plugin.json       # Plugin manifest
        ├── skills/
        │   └── skill-name/
        │       └── SKILL.md
        ├── commands/
        ├── agents/
        ├── hooks/
        └── mcpServers/

Two manifests do the work:

marketplace.json declares the catalog itself and the list of plugins it exposes:

{
  "name": "my-plugins",
  "owner": { "name": "Your Name", "email": "you@example.com" },
  "plugins": [
    {
      "name": "quality-review",
      "source": "./plugins/quality-review",
      "description": "Quick code-review skill",
      "version": "1.0.0"
    }
  ],
  "metadata": { "pluginRoot": "./plugins" }
}

plugin.json is the manifest for a single plugin, sitting inside its own .claude-plugin/ directory:

{
  "name": "quality-review",
  "description": "Adds a quality-review skill for quick code reviews",
  "version": "1.0.0",
  "author": { "name": "Your Name" },
  "license": "MIT"
}

By default, plugin.json is the authority on what components a plugin exposes (strict: true). Setting strict: false on a marketplace entry flips that: the marketplace entry becomes the complete definition, and any conflicting plugin.json field causes a load failure. That’s useful when the marketplace operator, not the plugin author, needs to control exactly which components get exposed.

A plugin entry can point at seven kinds of sources. The three you’ll use most:

// Local path, resolved during development
{ "name": "local-plugin", "source": "./plugins/my-plugin" }

// GitHub, pinned to a tag and commit SHA
{
  "source": {
    "source": "github",
    "repo": "owner/repo",
    "ref": "v2.0.0",
    "sha": "a1b2c3d4e5f6..."
  }
}

// Signed archive, for internal artifact registries
{
  "source": {
    "source": "archive",
    "url": "https://artifacts.example.com/plugin-2.1.0.zip",
    "sha256": "6bfa50e3d2e00c052b46abe51fff89346ac803e45771f76dcf6df1ab74cca5e1"
  }
}

Git subdirectory sources (git-subdir) and npm packages round out the list, useful when a plugin lives inside a larger monorepo or is already published as an npm package.


Install and config basics

Adding a marketplace and installing from it is three commands:

# Add the marketplace (local path, GitHub shorthand, or git URL all work)
/plugin marketplace add owner/repo

# Install a plugin from it
/plugin install quality-review@my-plugins

# Keep the catalog and installed plugins current
/plugin marketplace update
/plugin update quality-review@my-plugins

For a team, the same setup can be pinned in .claude/settings.json so every clone of the repo gets the marketplace and its plugins enabled automatically, without each person running the install command themselves:

{
  "extraKnownMarketplaces": {
    "company-tools": {
      "source": { "source": "github", "repo": "your-org/claude-plugins" }
    }
  },
  "enabledPlugins": {
    "code-formatter@company-tools": true,
    "deployment-tools@company-tools": true
  }
}

One gotcha worth knowing before you hit it: a marketplace resolved from a raw URL (source: "url") only downloads the marketplace.json file itself, so relative paths inside it won’t resolve. If your plugins live in the same repo as the manifest, use a github, git-subdir, or local source instead of a bare URL.


Packaging a first plugin

Here’s the minimal path from an empty folder to an installable plugin with one skill.

1. Scaffold the plugin directory.

mkdir -p my-marketplace/plugins/hello-plugin/.claude-plugin
mkdir -p my-marketplace/plugins/hello-plugin/skills/hello

2. Write the plugin manifest (plugins/hello-plugin/.claude-plugin/plugin.json):

{
  "name": "hello-plugin",
  "description": "A minimal example plugin with one skill",
  "version": "0.1.0",
  "author": { "name": "Your Name" },
  "license": "MIT"
}

3. Add a skill (plugins/hello-plugin/skills/hello/SKILL.md), using the same format any Claude Code skill uses. ${CLAUDE_PLUGIN_ROOT} always resolves to the plugin’s own install directory, which is how a packaged plugin references its bundled scripts without hardcoding a path:

---
name: hello
description: Greets the user and demonstrates plugin-relative paths.
---

Run `${CLAUDE_PLUGIN_ROOT}/scripts/greet.sh` and relay its output.

4. Create the marketplace manifest at the repo root (.claude-plugin/marketplace.json):

{
  "name": "my-marketplace",
  "owner": { "name": "Your Name" },
  "plugins": [
    {
      "name": "hello-plugin",
      "source": "./plugins/hello-plugin",
      "description": "A minimal example plugin",
      "version": "0.1.0"
    }
  ],
  "metadata": { "pluginRoot": "./plugins" }
}

5. Test it locally, then push and install for real:

/plugin marketplace add ./my-marketplace
/plugin install hello-plugin@my-marketplace

# once pushed to GitHub
/plugin marketplace add your-org/my-marketplace
/plugin install hello-plugin@my-marketplace

From here, adding a slash command, a hook, or an MCP server is just another key in plugin.json (commands, hooks, mcpServers) pointing at the matching directory or config block.


Security best practices

A marketplace is a supply chain, even a private, single-team one. The controls that matter, roughly in order of impact:

  1. Pin versions. Use sha on git sources and sha256 on archives. A ref alone (a branch or tag) can move; a pinned commit or digest can’t.
  2. Minimize scope. Only declare the components (skills, commands, hooks, mcpServers) a plugin actually needs. strict: false at the marketplace level is a way to enforce that from the catalog side rather than trusting every plugin author.
  3. Review hook and MCP permissions. Hooks run shell commands and MCP servers get tool access; treat both as code review surfaces, not just configuration.
  4. Use short-lived, read-only tokens for private source authentication, whether that’s a git credential helper or a headersHelper script for archive downloads.
  5. Restrict marketplace sources at the org level. strictKnownMarketplaces locks a team to an explicit allowlist (specific repos, host patterns, or path patterns); an empty array blocks everything, including Anthropic’s own official marketplace.
{
  "strictKnownMarketplaces": [
    { "source": "github", "repo": "acme-corp/approved-plugins" },
    { "source": "github", "repo": "acme-corp/*" }
  ]
}

Claude Code also strips identity and routing headers (Host, Cookie, X-Forwarded-*) from archive requests automatically, and reserves a set of marketplace names (claude-code-marketplace, anthropic-*, and similar) so a third party can’t publish something that looks official.


Deployment and production

Running a marketplace for a real team means two additional concerns: pre-warming environments and controlling what gets installed without touching every machine.

Seeding containers and CI images. Build-time seeding avoids a network fetch on every fresh container:

export CLAUDE_CODE_PLUGIN_CACHE_DIR=/opt/claude-seed
claude plugin marketplace add your-org/plugins
claude plugin install deployment-tools@your-plugins

At runtime, pointing CLAUDE_CODE_PLUGIN_SEED_DIR at that same directory makes it a read-only seed: plugins are available immediately, auto-updates are disabled against it, and seed entries take priority over anything a user configures locally.

Organization-wide distribution. From the admin settings, an organization can restrict which source types are allowed at all (typically github, url, git-subdir, and relative paths only), require private plugins to share the org’s GitHub ownership, and block bare bin/ directories in favor of ${CLAUDE_PLUGIN_ROOT}/scripts/<name> references, which are easier to audit.

Background auto-updates. These run non-interactively, so SSH-based git auth needs keys already loaded in ssh-agent; HTTPS needs either a credential helper or a URL rewrite baked into the environment:

git config --global url."https://x-access-token:TOKEN@github.com/acme-corp/plugins".insteadOf "https://github.com/acme-corp/plugins"

If a plugin exceeds GitHub’s 256 MiB archive limit, git-subdir with a partial clone or a command source in link mode (Unix only) avoids re-downloading the whole thing on every update.


Other AI coding agents and their extension models

Claude Code isn’t alone in treating extensibility as a first-class concept, though the shape of it differs:

Tool Extension model
Cursor Rules files and MCP server config per project, no packaged/versioned marketplace
GitHub Copilot VS Code extension API plus Copilot Extensions, distributed through the VS Code Marketplace
Continue.dev Open-source config blocks (config.yaml) for models, context providers, and slash commands, shareable via a hub
Windsurf (Codeium) Built-in MCP support and workflow rules, no independent plugin registry
OpenAI Codex CLI MCP server configuration in config.toml, no first-party marketplace layer

The common thread across all of them is MCP as the interoperability layer for tools; what Claude Code’s marketplace adds on top is the packaging, versioning, and governance layer around skills, commands, hooks, and MCP servers together, distributed as one unit instead of configured piecemeal.


Wrapping up

A plugin marketplace turns Claude Code extensions from “files someone shares in Slack” into versioned, auditable, centrally-updatable packages. Start small: a single-plugin marketplace in a private repo with strict: false and a pinned sha is enough to get a team on the same page, and the same setup scales to seeded containers and org-wide allowlists without changing the manifest format.

If you’re setting one up, the official plugin reference is the source of truth for schema fields as they evolve.


References: