Marketplace reference
Every marketplace.json field, plugin entry field, plugin source type and marketplace source type, plus what each validation message means.
Two kinds of source object turn up around marketplaces, and mixing them up causes most of the confusion I see:
- A plugin source sits inside each entry of
marketplace.jsonand says where to fetch that one plugin. - A marketplace source says where to fetch the
marketplace.jsonfile itself. You write one in settings (extraKnownMarketplaces,strictKnownMarketplaces,blockedMarketplaces), or Claude Code builds one when you runclaude plugin marketplace add.
This page is the exact reference for both, plus the catalogue file. For a walkthrough see Create a marketplace and Host a marketplace; for allowlist recipes see Manage plugins for your organisation.
The catalogue file
Put it at .claude-plugin/marketplace.json. The folder containing .claude-plugin/ is the marketplace root, and every relative plugin path resolves from there, not from .claude-plugin/. If you store the file elsewhere in a repo, users must declare the marketplace through extraKnownMarketplaces with a path on the source, because claude plugin marketplace add has no option for a custom location.
A user can only have one marketplace registered per name.
Unknown keys at the top level or inside entries are ignored, which means a typo silently does nothing. The validator warns about each one, so run it.
Top-level fields
name, owner and plugins are required.
| Field | Type | Meaning |
|---|---|---|
name | string | Marketplace id. Letters, digits, ., _ and -, starting with a letter or digit, no ... Anything else fails validation because plugins could not be installed from it. Users type it after the @. See reserved names |
owner | object | Maintainer. name required; email and url optional |
plugins | array | Plugin entries. Validated individually, so one bad entry does not sink the rest |
$schema | string | Editor schema URL; ignored at load |
description | string | Shown to users. The validator warns if missing |
version | string | Version of the catalogue itself |
metadata.description, metadata.version | string | Alternative home for the two fields above |
metadata.pluginRoot | string | Folder that bare plugin names resolve under (see relative paths). v2.1.239 or later |
forceRemoveDeletedPlugins | boolean | true uninstalls plugins from users' machines when you delete their entries |
allowCrossMarketplaceDependenciesOn | string array | Other marketplaces whose plugins may be pulled in as dependencies. Only the list in the installed plugin's own marketplace counts, for its entire dependency chain. See Plugin dependencies |
renames | object | Old plugin name to new name, or to null for removed plugins |
The last three are explained with examples on Host a marketplace.
Reserved names
Marketplaces may not use these names:
| Category | Names | Rule |
|---|---|---|
| Official marketplaces | claude-code-marketplace, claude-code-plugins, claude-plugins-official, anthropic-marketplace, anthropic-plugins, agent-skills, anthropic-agent-skills, life-sciences, knowledge-work-plugins, claude-for-legal, claude-for-financial-services, financial-services-plugins, first-party-plugins, claude-tag-plugins | Reserved unless the marketplace comes from a github or git source under github.com/anthropics/ |
| Community | claude-community, claude-plugins-community, healthcare | Same rule |
| Plugin directory | anthropic-plugin-directory, claude-plugin-directory | Same rule |
| Impersonation | Lookalikes such as official-claude-plugins or claude-plugins-v2, and any name containing non-ASCII characters | Error Marketplace name impersonates an official Anthropic/Claude marketplace. Already-registered marketplaces with such names stop loading |
| Alternative spellings | A reserved name with a trailing dot, or with a symbol other than _ replacing a hyphen (so claude.code.plugins counts as claude-code-plugins) | Adding fails with is another spelling of "<reserved>", a reserved marketplace name; registered ones stop loading. v2.1.280 or later |
| Internal origins | inline, builtin, skills-dir, synced, claude-plugin-test | Used by Claude Code for non-marketplace plugins |
| Package tools | npm, pip, uv, cargo, github, gh (any casing) | v2.1.275 or later |
| claude.ai prefix | Anything starting claudeai- | claude plugin marketplace add refuses with a message that the prefix is reserved for claude.ai-hosted marketplaces |
Control or bidirectional formatting characters in a name produce Marketplace name cannot contain control or bidirectional-formatting characters. When a registered marketplace stops loading because of an impersonating name, claude plugin list and /plugin say Claude Code refuses the marketplace name "<name>" and tell you to remove it (v2.1.282 or later). Removing it also uninstalls its plugins and deletes their data.
Plugin entries
Each entry in plugins needs name and source. Besides its own fields, an entry accepts every plugin.json field apart from the directory listing fields. The table lists the entry-specific fields and those whose meaning shifts in an entry:
| Field | Type | Meaning |
|---|---|---|
name | string | Plugin id, same character rules as the marketplace name. What users type before @, even if plugin.json says something else |
source | string or object | Where to fetch. See plugin sources |
description | string | Shown in /plugin listings and detail views |
version | string | If plugin.json also has one, plugin.json wins and the validator warns |
category | string | Free-form grouping |
tags | string array | Free-form search tags |
strict | boolean | Default true. Whether plugin.json is authoritative for components. See strict mode |
relevance | object | When to suggest the plugin. See Suggest plugins by relevance |
dependencies | array | Required plugins: "name", "name@marketplace" or an object |
defaultEnabled | boolean | Default true. Overrides the plugin.json value |
displayName | string | UI label. Falls back to plugin.json, then to name |
metadata | object | Your own data; never read. v2.1.222 or later |
headers | object | HTTP headers for this entry's archive download. Overrides same-named headers from the marketplace source. v2.1.238 or later |
headersHelper | string | Command printing download headers as JSON for expiring credentials. Requires "strict": false. v2.1.238 or later |
How an entry and plugin.json combine
- The fetched plugin has no
plugin.json: the entry is the manifest regardless ofstrict, and every manifest field it carries applies, includingmcpServers,lspServers,userConfigandchannels. - The plugin has a
plugin.json: that file is the manifest.strictdecides what happens to the entry's six component fields (commands,agents,skills,hooks,outputStyles,themes). EntrymcpServers,lspServers,userConfigandchannelsare ignored; declare those inplugin.json.
Hooks on an entry
Write them as an inline object mapping event names to matcher arrays. A file path or array passes validation but never runs, and the plugin reports not yet supported in a marketplace entry. Keep file-based hooks in the plugin's own hooks/hooks.json or plugin.json.
Display fields
displayName, description, author, homepage, repository, license and keywords can be set on either side. If the entry sets one, users see the entry's value; otherwise they see plugin.json's. Before installation Claude Code can only read plugin.json for relative-path entries (the files are already in the marketplace), so for any other source users see only entry fields until they install.
Strict mode
strict | plugin.json | Entry declares components? | Result |
|---|---|---|---|
| any | absent | any | Entry is the manifest |
true (default) | present | any | plugin.json is authoritative; entry components are appended, except hooks, whose matchers replace the manifest's for the same event |
false | present | no | Same as true |
false | present | yes | Fails with Plugin <name> has conflicting manifests: both plugin.json and marketplace entry specify components |
Plugin sources
A plugin source is either a relative path string or an object whose own source key names the type.
| Type | Fields | Use for |
|---|---|---|
| Relative path | the string | A folder inside the marketplace. Must start ./ (or be "." for the root, or a bare name under metadata.pluginRoot) |
github | repo, ref, sha | A GitHub repo in owner/repo form |
url | url, ref, sha | Any git repo by URL |
git-subdir | url, path, ref, sha | One folder of a git repo, via sparse checkout |
npm | package, version, registry | An npm package or tarball, fetched without running install scripts |
archive | url, sha256 | An HTTPS zip. v2.1.224 or later |
command | command, timeout, mode | A folder printed by a command run on the user's machine. v2.1.229 or later |
Note the overlapping names: url and github are also marketplace source types (where url means a direct link to marketplace.json), git exists only as a marketplace source, npm exists as both, and git-subdir, archive and command exist only as plugin sources.
Shared git fields (github, url, git-subdir):
ref: branch or tag; defaults to the repo's default branch.sha: full 40-character lowercase commit. If both are set,shais checked out. On GitHub, GitLab, Bitbucket and most hosts that means installs still work after therefis deleted, provided the commit is reachable. Hosts that cannot fetch by SHA (AWS CodeCommit, for example) still need therefto exist and contain the commit.
Relative path
Resolves from the marketplace root. .. fails validation, and on macOS and Linux any backslash after the leading ./ is refused, so use forward slashes.
{ "name": "pdf-tools", "source": "./plugins/pdf-tools" }
Relative paths only work when Claude Code has the marketplace's files. That is true for github, git, file and directory marketplace sources. It is not true for url (only marketplace.json is downloaded) and relative paths are rejected outright for settings sources.
Bare names. With "metadata": { "pluginRoot": "./plugins" }, an entry can say "source": "pdf-tools" and resolve to ./plugins/pdf-tools (v2.1.239 or later). pluginRoot must be a relative path inside the marketplace, has no effect on sources already starting ./, and anything containing a / (such as team-a/pdf-tools) is not a bare name and still needs ./.
github
{
"name": "pdf-tools",
"source": {
"source": "github",
"repo": "cshields/pdf-tools",
"ref": "release",
"sha": "4f1c0a9e7b2d3c5e6f8091a2b3c4d5e6f7a8b9c0"
}
}
url
A full git URL: https://, http://, file:// or git@. No .git suffix is needed, so Azure DevOps and CodeCommit URLs work as copied. No owner/repo shorthand.
{
"name": "pdf-tools",
"source": { "source": "url", "url": "https://dev.azure.com/acme/tools/_git/pdf-tools", "ref": "main" }
}
git-subdir
url accepts a full git URL or owner/repo shorthand; path is the plugin's folder. Only that folder is checked out, and over HTTPS or SSH a partial clone is requested, so a plugin in a huge monorepo installs without pulling the whole repo (where the host supports it).
{
"name": "pdf-tools",
"source": { "source": "git-subdir", "url": "acme/platform-monorepo", "path": "claude/pdf-tools" }
}
npm
package: a name (@acme/pdf-tools), a name with version (@acme/pdf-tools@3.1.0), or anhttpstarball URL.version: version, range or dist-tag, used whenpackagehas no version. Defaults tolatest.registry: registry URL if not the default. Must behttpsunless it is the user's own default registry.
The package is fetched with the user's npm client; its install scripts never run and its dependencies are not installed during the fetch. If it ships a supported lockfile, dependencies are installed in a separate locked-down step (see How plugins load).
package values that are refused before anything is fetched: git addresses, folder or file: paths, npm: aliases, tarball links on github.com, gist.github.com, gitlab.com, bitbucket.org or git.sr.ht (GitLab registry links under gitlab.com/api/v4/ are allowed), and http tarballs unless on the user's default registry.
{
"name": "pdf-tools",
"source": { "source": "npm", "package": "@acme/pdf-tools", "version": "~3.1.0", "registry": "https://npm.acme.internal" }
}
archive
url must be https:// and may not target loopback, link-local or cloud metadata hosts. Size and timeout limits are on Host a marketplace. The plugin can sit at the top of the zip or one folder down. sha256 is 64 hex characters (any case); if set, mismatching downloads are refused.
{
"name": "pdf-tools",
"source": {
"source": "archive",
"url": "https://releases.acme.dev/pdf-tools/3.1.0.zip",
"sha256": "0d6e4079e36703ebd37c00722f5891d28b0e2811dc114b129215123adcce3605"
}
}
command
For when a tool already on the user's machine generates the plugin, such as an IDE rendering a plugin for the currently selected toolchain. The command runs on install and update and again once per session, so users get fresh output without reinstalling.
command: shell command printing the plugin folder's absolute path on one line and exiting 0. Shown to the user for approval in full. Printable ASCII, at most 500 characters, no run of four or more spaces.timeout: 1 to 600 seconds; default 60.mode:copy(default) orlink.
{
"name": "pdf-tools",
"source": { "source": "command", "command": "acme-sdk export-claude-plugin --print-path", "timeout": 90 }
}
The command runs via sh (or cmd.exe on Windows) from the user's home directory, so use absolute paths or commands on PATH. The printed folder must contain the complete plugin when the command exits; the path may change between runs.
It fails if the command exits non-zero, overruns, or prints anything other than one absolute path, and also if the printed folder has no plugin content at its top level (no .claude-plugin/, skills/, commands/, agents/ or hooks/), is the session's start directory or one of its parents, is a UNC path on Windows, or (in copy mode) exceeds 256 MiB or 20,000 entries.
Copy versus link mode:
copy | link | |
|---|---|---|
| What happens | Folder copied into the cache | Cache entry filled with links to each top-level item; files load in place |
| Version | Hash of the copied files; identical re-runs count as up to date | Real path of the printed folder plus its top-level entries; print a new path to signal new content |
| Size limits | Apply | Do not apply |
| Afterwards | Your tool may delete or rewrite the folder | Folder must stay put for as long as the plugin is installed |
| Node dependencies | Installed automatically if a lockfile is present | Never installed; ship node_modules |
| Other rules | Top-level symlinks must stay inside the folder; sessions started inside the folder do not load it; refused on Windows |
How users approve the command is covered on Install plugins. Admins can disable these sources with disableCommandPluginSources (see the settings reference).
Marketplace sources
These say where marketplace.json comes from.
| Name | As a marketplace source | As a plugin source |
|---|---|---|
url | Direct link to marketplace.json; fields url, headers, headersHelper | Git repo to clone; fields url, ref, sha |
git | Git repo to clone; fields url, ref, path, sparsePaths | Does not exist |
github | GitHub repo; fields repo, ref, path, sparsePaths | GitHub repo; fields repo, ref, sha (no path) |
Every type and where it is valid
| Type | Fields | Produced by marketplace add from | In extraKnownMarketplaces | In strictKnownMarketplaces | In blockedMarketplaces |
|---|---|---|---|---|---|
url | url, headers, headersHelper | An http(s):// URL that does not look like git | Loads | Allows that URL | Blocks that URL |
github | repo, ref, path, sparsePaths | owner/repo, owner/repo@ref, owner/repo#ref | Loads | Allows matching repo, ref, path; repo may be owner/* | Blocks the same plus git URLs for that repo |
git | url, ref, path, sparsePaths | user@host:path, or an http(s):// URL ending .git, containing /_git/, or naming a github.com or gitlab.com repo. #ref pins | Loads | Allows matching URL, ref, path | Blocks the same plus other spellings of the same github.com repo |
npm | package | Never | Fails: NPM marketplace sources not yet implemented | Parses, matches nothing | Parses, matches nothing |
file | path | A path to a .json file | Loads | Allows that path | Blocks that path |
directory | path | A path to a folder | Loads | Allows that path | Blocks that path |
settings | name, plugins, owner | Never | Loads | Allows same name with identical plugins | Blocks same name |
skills-dir | none | Never | Fails: Unsupported marketplace source type | Keeps skills-directory plugins loading | Stops them loading |
hostPattern | hostPattern | Never | Fails: Unsupported marketplace source type | Allows github, git, url sources with a matching host | Blocks them |
pathPattern | pathPattern | Never | Fails: Unsupported marketplace source type | Allows file/directory sources with a matching path | Blocks them |
Field details
| Field | Type(s) | Notes |
|---|---|---|
url | url | Link to marketplace.json. Only that file is fetched, so entries cannot use relative paths |
url | git | Repository to clone |
headers | url | Headers sent with the fetch |
headersHelper | url | Command printing short-lived headers. v2.1.238 or later |
repo | github | Must be a single repository in marketplace add and extraKnownMarketplaces. marketplace add rejects owner/*; in extraKnownMarketplaces it is taken literally and the clone fails |
ref | github, git | Branch or tag; defaults to the default branch |
path | github, git | Location of the catalogue inside the repo; default .claude-plugin/marketplace.json |
path | file | The catalogue file, read in place. The root is taken as two folders up, so keep it at <root>/.claude-plugin/marketplace.json |
path | directory | The marketplace root |
sparsePaths | github, git | Folders for sparse checkout, e.g. [".claude-plugin", "plugins"]. Set by claude plugin marketplace add --sparse |
skipLfs | github, git | Accepted, does nothing |
name | settings | Must equal the extraKnownMarketplaces key and not be reserved |
plugins | settings | Inline catalogue. Items take name, source, description, version, strict, headers, headersHelper. Sources must be objects, since there is no repo for a relative path |
Policy-only values
hostPattern, pathPattern, skills-dir and the owner/* form of repo are only meaningful in strictKnownMarketplaces and blockedMarketplaces. The two patterns are regular expressions tested before any fetch. skills-dir is not really a source: once any allowlist exists, skills-directory plugins stop loading until you add {"source": "skills-dir"}. owner/* matches every repo under exactly that owner (v2.1.223 or later).
Writing sources in settings
extraKnownMarketplaces maps a name to an object containing source:
{
"extraKnownMarketplaces": {
"studio-plugins": {
"source": {
"source": "github",
"repo": "cshields/studio-plugins",
"sparsePaths": [".claude-plugin", "plugins"]
}
}
}
}
An inline settings marketplace needs no hosted file at all:
{
"extraKnownMarketplaces": {
"local-experiments": {
"source": {
"source": "settings",
"name": "local-experiments",
"plugins": [
{ "name": "pdf-tools", "source": { "source": "github", "repo": "cshields/pdf-tools" } }
]
}
}
}
}
The policy lists are arrays of source objects:
{
"blockedMarketplaces": [
{ "source": "hostPattern", "hostPattern": "(^|\\.)pastebin\\.com$" },
{ "source": "pathPattern", "pathPattern": "^/tmp/" }
]
}
Validation messages
claude plugin validate <path> accepts the marketplace root or the catalogue file. Entries are referred to by index, as plugins.1.source or plugins[1].source. Messages prefixed with an index and plugin.json → concern that plugin's own files; those are covered on Plugin troubleshooting. Warnings that mention Claude Desktop flag names that the desktop app would reject. Exit codes and --strict are on the plugin CLI reference.
Errors
| Message | What to fix |
|---|---|
Marketplace must have a name | name is empty |
Marketplace name cannot contain spaces. Use kebab-case (e.g., "my-marketplace") | Spaces in name |
Marketplace name cannot contain path separators (/ or \), ".." sequences, or be "." | Path-like name |
Marketplace name impersonates an official Anthropic/Claude marketplace | Choose a name that does not imitate a reserved one |
Marketplace name cannot contain control or bidirectional-formatting characters | Invisible or control characters in name |
Marketplace name "inline" is reserved for --plugin-dir session plugins (and equivalents for builtin, skills-dir, synced, claude-plugin-test, npm, pip, uv, cargo, github, gh) | Rename the marketplace |
Author name cannot be empty | owner.name |
Plugin name cannot contain spaces. Use kebab-case (e.g., "my-plugin") | An entry name |
Plugin name cannot contain control or bidirectional-formatting characters | An entry name |
Plugin name "x" is reserved: it passes as one of Anthropic's own | An entry name; rules on the manifest reference |
Claude Code cannot install plugins from marketplace "x". ... | Marketplace name uses disallowed characters |
Claude Code cannot install plugin "x". ... | Entry name uses disallowed characters |
Duplicate plugin name "x" found in marketplace | Two entries share a name |
plugins.i.source: Invalid input | source matches no type; see below |
plugins.i.source: Invalid string: must start with "./" | Relative path missing ./ (printed Invalid input before v2.1.285) |
plugins[i].source: Path contains "..": <path> | Relative source escapes the root |
source.source: 'unsupported' is a parse-time placeholder and cannot be authored | Do not write "unsupported" as a type |
Plugin "x" sets headersHelper but is not "strict": false | Add "strict": false to that archive entry |
chain does not resolve (<reason>), followed by a note that the target must be a listed plugin, a renames key, or null | Fix the renames chain |
target "x" is not a valid plugin name (PluginIdSchema) | A renames target is not a valid id |
Warnings
| Message | What it concerns |
|---|---|
Unknown field 'x'. Claude Code ignores it at load time. | A stray key at top level, under metadata, in an entry, or under relevance |
Marketplace has no plugins defined | Empty plugins |
Plugin "x" sets headers/headersHelper, which only apply to "archive" sources; they have no effect on this entry. | Headers on a non-archive entry |
Plugin "x" fetches its archive with a headersHelper but sets no sha256 pin | Add sha256 |
Header "x" is a request-routing/identity header that catalog entries may not set; Claude Code drops it at download time. | Remove that header |
Local source "x" is or traverses a symlink, so <path> was not read | A symlinked local source |
No marketplace description provided. ... | Add description |
Entry declares version "x" but <path>/plugin.json says "y". At install time, plugin.json wins | Set version in one place only |
'relevance' must be an object containing topic and signals; got <type>. ... | Malformed relevance |
'metadata' must be a free-form object; got <type>. ... | Malformed metadata |
'experimental' must be an object containing component declarations; got <type>. ... | Malformed experimental |
Marketplace name "x" is reserved in Claude Desktop | name is org, org-provisioned or unknown |
Marketplace name "x" is not accepted by Claude Desktop (...) | Desktop allows letters, digits, ., _, -, alphanumeric start, max 128 characters |
Plugin name "x" is not accepted by Claude Desktop (...) | Same rules; Desktop drops the entry |
"Invalid input" on a source
The source object matched no known type. Typical causes: an npm package containing .., a misspelt source type, or a known type missing a required field (a github source with no repo, say).
What validation cannot see
Entry hooks written as a path or array pass validation and only fail at load. Fetch errors for remote sources only appear at install. For load-time failures, claude plugin list shows the error beside the plugin, and Plugin troubleshooting explains each message.