Skip to content

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.json and says where to fetch that one plugin.
  • A marketplace source says where to fetch the marketplace.json file itself. You write one in settings (extraKnownMarketplaces, strictKnownMarketplaces, blockedMarketplaces), or Claude Code builds one when you run claude 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.

FieldTypeMeaning
namestringMarketplace 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
ownerobjectMaintainer. name required; email and url optional
pluginsarrayPlugin entries. Validated individually, so one bad entry does not sink the rest
$schemastringEditor schema URL; ignored at load
descriptionstringShown to users. The validator warns if missing
versionstringVersion of the catalogue itself
metadata.description, metadata.versionstringAlternative home for the two fields above
metadata.pluginRootstringFolder that bare plugin names resolve under (see relative paths). v2.1.239 or later
forceRemoveDeletedPluginsbooleantrue uninstalls plugins from users' machines when you delete their entries
allowCrossMarketplaceDependenciesOnstring arrayOther 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
renamesobjectOld 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:

CategoryNamesRule
Official marketplacesclaude-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-pluginsReserved unless the marketplace comes from a github or git source under github.com/anthropics/
Communityclaude-community, claude-plugins-community, healthcareSame rule
Plugin directoryanthropic-plugin-directory, claude-plugin-directorySame rule
ImpersonationLookalikes such as official-claude-plugins or claude-plugins-v2, and any name containing non-ASCII charactersError Marketplace name impersonates an official Anthropic/Claude marketplace. Already-registered marketplaces with such names stop loading
Alternative spellingsA 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 originsinline, builtin, skills-dir, synced, claude-plugin-testUsed by Claude Code for non-marketplace plugins
Package toolsnpm, pip, uv, cargo, github, gh (any casing)v2.1.275 or later
claude.ai prefixAnything 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:

FieldTypeMeaning
namestringPlugin id, same character rules as the marketplace name. What users type before @, even if plugin.json says something else
sourcestring or objectWhere to fetch. See plugin sources
descriptionstringShown in /plugin listings and detail views
versionstringIf plugin.json also has one, plugin.json wins and the validator warns
categorystringFree-form grouping
tagsstring arrayFree-form search tags
strictbooleanDefault true. Whether plugin.json is authoritative for components. See strict mode
relevanceobjectWhen to suggest the plugin. See Suggest plugins by relevance
dependenciesarrayRequired plugins: "name", "name@marketplace" or an object
defaultEnabledbooleanDefault true. Overrides the plugin.json value
displayNamestringUI label. Falls back to plugin.json, then to name
metadataobjectYour own data; never read. v2.1.222 or later
headersobjectHTTP headers for this entry's archive download. Overrides same-named headers from the marketplace source. v2.1.238 or later
headersHelperstringCommand 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 of strict, and every manifest field it carries applies, including mcpServers, lspServers, userConfig and channels.
  • The plugin has a plugin.json: that file is the manifest. strict decides what happens to the entry's six component fields (commands, agents, skills, hooks, outputStyles, themes). Entry mcpServers, lspServers, userConfig and channels are ignored; declare those in plugin.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

strictplugin.jsonEntry declares components?Result
anyabsentanyEntry is the manifest
true (default)presentanyplugin.json is authoritative; entry components are appended, except hooks, whose matchers replace the manifest's for the same event
falsepresentnoSame as true
falsepresentyesFails 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.

TypeFieldsUse for
Relative paththe stringA folder inside the marketplace. Must start ./ (or be "." for the root, or a bare name under metadata.pluginRoot)
githubrepo, ref, shaA GitHub repo in owner/repo form
urlurl, ref, shaAny git repo by URL
git-subdirurl, path, ref, shaOne folder of a git repo, via sparse checkout
npmpackage, version, registryAn npm package or tarball, fetched without running install scripts
archiveurl, sha256An HTTPS zip. v2.1.224 or later
commandcommand, timeout, modeA 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, sha is checked out. On GitHub, GitLab, Bitbucket and most hosts that means installs still work after the ref is deleted, provided the commit is reachable. Hosts that cannot fetch by SHA (AWS CodeCommit, for example) still need the ref to 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 an https tarball URL.
  • version: version, range or dist-tag, used when package has no version. Defaults to latest.
  • registry: registry URL if not the default. Must be https unless 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) or link.
{
  "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:

copylink
What happensFolder copied into the cacheCache entry filled with links to each top-level item; files load in place
VersionHash of the copied files; identical re-runs count as up to dateReal path of the printed folder plus its top-level entries; print a new path to signal new content
Size limitsApplyDo not apply
AfterwardsYour tool may delete or rewrite the folderFolder must stay put for as long as the plugin is installed
Node dependenciesInstalled automatically if a lockfile is presentNever installed; ship node_modules
Other rulesTop-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.

NameAs a marketplace sourceAs a plugin source
urlDirect link to marketplace.json; fields url, headers, headersHelperGit repo to clone; fields url, ref, sha
gitGit repo to clone; fields url, ref, path, sparsePathsDoes not exist
githubGitHub repo; fields repo, ref, path, sparsePathsGitHub repo; fields repo, ref, sha (no path)

Every type and where it is valid

TypeFieldsProduced by marketplace add fromIn extraKnownMarketplacesIn strictKnownMarketplacesIn blockedMarketplaces
urlurl, headers, headersHelperAn http(s):// URL that does not look like gitLoadsAllows that URLBlocks that URL
githubrepo, ref, path, sparsePathsowner/repo, owner/repo@ref, owner/repo#refLoadsAllows matching repo, ref, path; repo may be owner/*Blocks the same plus git URLs for that repo
giturl, ref, path, sparsePathsuser@host:path, or an http(s):// URL ending .git, containing /_git/, or naming a github.com or gitlab.com repo. #ref pinsLoadsAllows matching URL, ref, pathBlocks the same plus other spellings of the same github.com repo
npmpackageNeverFails: NPM marketplace sources not yet implementedParses, matches nothingParses, matches nothing
filepathA path to a .json fileLoadsAllows that pathBlocks that path
directorypathA path to a folderLoadsAllows that pathBlocks that path
settingsname, plugins, ownerNeverLoadsAllows same name with identical pluginsBlocks same name
skills-dirnoneNeverFails: Unsupported marketplace source typeKeeps skills-directory plugins loadingStops them loading
hostPatternhostPatternNeverFails: Unsupported marketplace source typeAllows github, git, url sources with a matching hostBlocks them
pathPatternpathPatternNeverFails: Unsupported marketplace source typeAllows file/directory sources with a matching pathBlocks them

Field details

FieldType(s)Notes
urlurlLink to marketplace.json. Only that file is fetched, so entries cannot use relative paths
urlgitRepository to clone
headersurlHeaders sent with the fetch
headersHelperurlCommand printing short-lived headers. v2.1.238 or later
repogithubMust be a single repository in marketplace add and extraKnownMarketplaces. marketplace add rejects owner/*; in extraKnownMarketplaces it is taken literally and the clone fails
refgithub, gitBranch or tag; defaults to the default branch
pathgithub, gitLocation of the catalogue inside the repo; default .claude-plugin/marketplace.json
pathfileThe catalogue file, read in place. The root is taken as two folders up, so keep it at <root>/.claude-plugin/marketplace.json
pathdirectoryThe marketplace root
sparsePathsgithub, gitFolders for sparse checkout, e.g. [".claude-plugin", "plugins"]. Set by claude plugin marketplace add --sparse
skipLfsgithub, gitAccepted, does nothing
namesettingsMust equal the extraKnownMarketplaces key and not be reserved
pluginssettingsInline 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

MessageWhat to fix
Marketplace must have a namename 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 marketplaceChoose a name that does not imitate a reserved one
Marketplace name cannot contain control or bidirectional-formatting charactersInvisible 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 emptyowner.name
Plugin name cannot contain spaces. Use kebab-case (e.g., "my-plugin")An entry name
Plugin name cannot contain control or bidirectional-formatting charactersAn entry name
Plugin name "x" is reserved: it passes as one of Anthropic's ownAn 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 marketplaceTwo entries share a name
plugins.i.source: Invalid inputsource 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 authoredDo not write "unsupported" as a type
Plugin "x" sets headersHelper but is not "strict": falseAdd "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 nullFix the renames chain
target "x" is not a valid plugin name (PluginIdSchema)A renames target is not a valid id

Warnings

MessageWhat 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 definedEmpty 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 pinAdd 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 readA symlinked local source
No marketplace description provided. ...Add description
Entry declares version "x" but <path>/plugin.json says "y". At install time, plugin.json winsSet 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 Desktopname 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.