A marketplace is a .claude-plugin/marketplace.json catalog hosted in a git repo, and users add it once with /plugin marketplace add owner/repo. The hard parts are not the schema, they are versioning discipline, private-repo auth, and the strictKnownMarketplaces managed-setting that decides whether your team can install anything else.
The minimal marketplace
A marketplace repo with one plugin has three files. Save this layout under any directory you control (say, acme-plugins/):
acme-plugins/
├── .claude-plugin/
│ └── marketplace.json
└── plugins/
└── code-formatter/
├── .claude-plugin/
│ └── plugin.json
└── skills/
└── format/
└── SKILL.md
acme-plugins/.claude-plugin/marketplace.json:
{
"name": "acme-tools",
"owner": {
"name": "Acme DevTools",
"email": "[email protected]"
},
"plugins": [
{
"name": "code-formatter",
"source": "./plugins/code-formatter",
"description": "Auto-format files on save"
}
]
}
acme-plugins/plugins/code-formatter/.claude-plugin/plugin.json:
{
"name": "code-formatter",
"description": "Auto-format files on save",
"version": "1.0.0"
}
Push to GitHub at acme-corp/acme-plugins and users add it with one command:
/plugin marketplace add acme-corp/acme-plugins
/plugin install code-formatter@acme-tools
Note the install syntax: <plugin-name>@<marketplace-name>, where acme-tools is the name field inside marketplace.json, not the GitHub repo path.
The five source types
The source field on each plugin entry tells Claude Code where to fetch that plugin. Pick based on where the plugin actually lives.
| Source type | Use when | Example |
|---|---|---|
Relative path ("./plugins/x") | Plugin lives inside the marketplace repo | "source": "./plugins/formatter" |
github | Plugin lives in its own GitHub repo | { "source": "github", "repo": "acme/formatter", "ref": "v2.1" } |
url | Non-GitHub git host (GitLab, Bitbucket, self-hosted) | { "source": "url", "url": "https://gitlab.acme.com/team/plugin.git" } |
git-subdir | Plugin lives in a subdirectory of a monorepo | { "source": "git-subdir", "url": "acme/monorepo", "path": "tools/claude-plugin" } |
npm | Plugin distributed as an npm package | { "source": "npm", "package": "@acme/claude-plugin", "version": "^2.0.0" } |
For github, url, and git-subdir you can pin to ref (branch or tag) and sha (40-char commit) for reproducible installs.
The git-subdir source uses a sparse partial clone, so a 5GB monorepo with a 50KB plugin only pulls the 50KB plus minimal git metadata. Use this instead of “we’ll just publish from the monorepo” guesswork.
Hosting on GitHub vs. anywhere else
GitHub gets the shortest user command:
/plugin marketplace add acme-corp/acme-plugins
/plugin marketplace add acme-corp/[email protected] # pin to a tag
Other git hosts work, but the user types the full URL:
/plugin marketplace add https://gitlab.acme.com/devtools/plugins.git
You can also publish a plain marketplace.json over HTTP and have users add it by URL. Do not do this if any plugin uses a relative path source: URL-based marketplaces fetch the JSON only, not the plugin files, so "source": "./plugins/x" will fail with path-not-found at install time. Use git hosting or switch every plugin entry to github / url / npm sources.
Private repos: credentials in two places
Manual install and /plugin marketplace update use your existing git credential helpers. If gh auth status works, plugin install works. If you push and pull from a private GitLab over SSH, plugin install works as long as the host is in known_hosts and the key is loaded in ssh-agent.
Background auto-updates run at startup, before you can be prompted for a password. Set the appropriate env var so they can authenticate non-interactively:
| Provider | Env var | Notes |
|---|---|---|
| GitHub | GITHUB_TOKEN or GH_TOKEN | PAT or GitHub App token with repo scope for private |
| GitLab | GITLAB_TOKEN or GL_TOKEN | PAT or project token with at least read_repository |
| Bitbucket | BITBUCKET_TOKEN | App password or repo access token |
Stick this in ~/.zshrc once and forget about it. In CI, set it as a secret. Without it, the marketplace shows up but auto-update silently fails.
Versioning: the bump-or-omit rule
Claude Code resolves a plugin’s version from the first of these that is set:
versionin the plugin’splugin.jsonversionin the plugin’s marketplace entry- The git commit SHA of the source
Pick one strategy and stick to it:
- Internal/actively-developed plugins: omit
versioneverywhere. Every commit becomes a new version automatically. Users get updates whenever they/plugin marketplace updateor auto-update fires. - Public/SemVer plugins: set
versioninplugin.jsonand bump it on every release. If you forget to bump, users keep the cached old copy even though the source has changed.
Avoid setting version in both plugin.json and the marketplace entry. The plugin.json value silently wins, so an outdated manifest can mask the version you wrote in marketplace.json.
Gating: extraKnownMarketplaces and strictKnownMarketplaces
Two managed settings control what your team can install.
extraKnownMarketplaces declares marketplaces team members get prompted to add automatically when they trust the project. Drop this in .claude/settings.json (project-shared) or in managed settings (org-wide):
{
"extraKnownMarketplaces": {
"acme-tools": {
"source": {
"source": "github",
"repo": "acme-corp/acme-plugins"
}
}
},
"enabledPlugins": {
"code-formatter@acme-tools": true
}
}
enabledPlugins pre-flags which plugins should be on by default once the marketplace is added.
strictKnownMarketplaces in managed settings (set by IT, cannot be overridden by users or projects) restricts which marketplaces users are allowed to add at all. Four useful shapes:
Complete lockdown (no marketplaces, period):
{ "strictKnownMarketplaces": [] }
Allowlist specific marketplaces:
{
"strictKnownMarketplaces": [
{ "source": "github", "repo": "acme-corp/approved-plugins" },
{ "source": "github", "repo": "acme-corp/security-tools", "ref": "v2.0" }
]
}
Allow anything from your internal git host (regex on host):
{
"strictKnownMarketplaces": [
{ "source": "hostPattern", "hostPattern": "^github\\.acme-corp\\.com$" }
]
}
Allow filesystem-based marketplaces from a specific directory (regex on path):
{
"strictKnownMarketplaces": [
{ "source": "pathPattern", "pathPattern": "^/opt/approved/" }
]
}
Pair extraKnownMarketplaces with strictKnownMarketplaces so users do not have to add anything by hand: extraKnownMarketplaces auto-registers the approved marketplaces and strictKnownMarketplaces blocks anything else. The order in the JSON file does not matter; what matters is that both live in managed settings together.
Footguns
Relative paths fail in URL-based marketplaces. A marketplace added via /plugin marketplace add https://example.com/marketplace.json only fetches the JSON; it does not download the plugin files. Any plugin with "source": "./plugins/x" fails with “path not found”. Why this matters: you can ship a marketplace that works perfectly when you test it from a local checkout and breaks the moment a user adds it by URL. Either host on git, or use github / url / npm source for every plugin entry.
Pinning version without bumping silently freezes users. If plugin.json says "version": "1.0.0" and you push three releases without changing the field, every existing user keeps the cached 1.0.0 forever. The cache key is the resolved version, not the commit SHA. Why this matters: you fix a security bug, push to main, run /plugin marketplace update, and nothing changes for anyone. Bump version on every release, or omit it and let the commit SHA do the work.
Background auto-update needs a token, not a credential helper. Manual /plugin marketplace update uses gh auth login credentials. Auto-update runs at startup, when interactive prompts would block Claude Code from launching, so it skips credential helpers and reads GITHUB_TOKEN / GITLAB_TOKEN / BITBUCKET_TOKEN from the environment. Why this matters: a private marketplace that works perfectly for manual updates silently stops auto-updating in CI or on a fresh laptop where the env var is not set. Verify echo $GITHUB_TOKEN returns a value, in the shell that launches Claude Code.
Reserved marketplace names. claude-code-marketplace, claude-code-plugins, claude-plugins-official, anthropic-marketplace, anthropic-plugins, agent-skills, knowledge-work-plugins, and life-sciences are reserved for Anthropic. Names that impersonate official ones (like official-claude-plugins or anthropic-tools-v2) are also blocked. Why this matters: pick a name like acme-tools early; renaming a published marketplace forces every user to remove and re-add it, which uninstalls every plugin they had installed from it.
/plugin marketplace remove uninstalls every plugin from that marketplace. Removing the marketplace and re-adding it is not a refresh, it is a wipe-and-reinstall. Why this matters: do not “fix” a stale marketplace by removing and re-adding; use /plugin marketplace update instead. The remove path is documented as such, but it is the kind of thing you only learn the hard way after losing the team’s installed plugin set on a Friday afternoon.
plugin.json version always wins over marketplace-entry version. If you set both, the manifest version masks the marketplace one with no warning. Why this matters: you bump marketplace.json from 2.1.0 to 2.2.0 and nothing changes for users because plugin.json still says 2.1.0. Pick one location to declare version and leave the other unset.
When NOT to run a marketplace
- You have one plugin. Users can install directly from a plugin repo with
/plugin install owner/repowithout going through a marketplace. The marketplace shape pays off at three or more plugins, when the catalog itself becomes useful. - The plugin is for one project, not a team. A
.claude/directory checked into the project repo plusextraKnownMarketplacespointing at someone else’s marketplace covers most “ship our setup with the repo” cases. You do not need to be a marketplace operator to share a plugin. - You want to publish to the public. The community marketplaces (
anthropics/claude-plugins, third-party catalogs) accept submissions; running your own competing marketplace fragments discovery. Reach for your own marketplace when you need control over the catalog (private plugins, gated access, internal-only) or when scale demands it. - You are not ready for the versioning commitment. Every plugin you list is a thing teammates will rely on. A marketplace is a deployment surface; if you cannot commit to bumping versions or watching auto-update telemetry, keep plugins as one-off repos until you are.