plugin.json file in a plugin’s .claude-plugin/ directory. It carries the plugin’s metadata and the userConfig values that Claude Code prompts the user for. It also declares any component that you define inline or keep outside its default location.
This reference is for plugin creators, and for marketplace owners who put component fields in a marketplace entry.
These cases are covered on other pages:
- Learning to build a plugin: start with Create a plugin
- What each component does at runtime: see Plugin components
- A field: the Fields table gives each field’s type, whether it’s required, its default, and what it accepts. Path rules covers the
./prefix and containment for every component path - A
userConfigoption or achannelsentry: the User configuration and Channels schemas ${CLAUDE_PLUGIN_ROOT}or another variable a plugin can reference: Environment variables- Where each component’s files go: Standard layout
- A message from
claude plugin validate: the troubleshooting page lists each message with its fix and links to the relevant sections on this page
Manifest file
The manifest is optional. Without it, Claude Code loads the components it finds in the standard layout. The plugin name then comes from the marketplace entry, or from the directory name when you load the plugin with--plugin-dir.
Write a manifest when you want metadata, a component outside its default directory, userConfig, or an inline component definition.
Save the manifest at .claude-plugin/plugin.json under the plugin root. Put every other plugin file at the plugin root, not inside .claude-plugin/. That includes skills/, commands/, and hooks/.
The following example sets most of the keys in the Fields table. It passes validation in a plugin directory that contains each referenced path.
Unrecognized fields
An unrecognized top-level key is stripped, and an unrecognized key inside auserConfig option, channels entry, lspServers config, or monitors entry is rejected:
- Top-level fields: the field is stripped and the plugin loads.
claude plugin validatereports each unrecognized top-level field as a warning - Strict objects:
userConfigoptions,channelsentries,lspServersconfigs, andmonitorsentries are strict. An unknown key inside one is an error, and the plugin doesn’t load
Validate the manifest
claude plugin validate is the authoritative check for a manifest. Run it from your shell against the plugin directory:
Validation passed: the manifest loadsValidation passed with warnings: the manifest loads, but the validator found something to fix, such as an unknown top-level field that Claude Code strips, anamethat isn’t kebab-case, or a missingversion,description, orauthor. Pass--strictto turn warnings into failures in CIValidation failed: the manifest has a type mismatch, a path that is missing or escapes the plugin root, or an unknown key inside auserConfigoption,channelsentry,lspServersconfig, ormonitorsentry. Claude Code reports the same problem when it loads the plugin
Fields
The table lists the top-level keys inplugin.json. name is the only required key. Where a field name is a link, the linked section has its full rules.
For component keys such as commands and hooks, Component path forms shows each accepted shape with an example, and every path follows the path rules for the ./ prefix, extensions, and containment.
In the Type column, a path is a string relative to the plugin root, such as
"./custom/commands".
name
The plugin identifier. It must be non-empty, with no spaces, @, :, path separators, control characters, or bidirectional-formatting characters; use kebab-case.
Claude Code namespaces every component under it, so an agent reviewer in plugin deploy-tools appears as deploy-tools:reviewer.
displayName
The name shown in UI in place of name. It may contain spaces and any casing, and it isn’t used for namespacing or lookup.
For a marketplace-installed plugin, a displayName on the marketplace entry takes precedence over this value.
version
A version string, not checked against semver. Setting it pins the plugin to that version until you change it; see Versions and updates. A plugin with a command source, a plugin from a marketplace hosted on claude.ai, and a plugin loaded in place from a marketplace added as a local directory aren’t pinned by this field.
metadata
A free-form object for your own data, such as catalog or entitlement fields. Claude Code doesn’t read it. Requires Claude Code v2.1.222 or later.
defaultEnabled
Whether the plugin starts enabled when the user hasn’t set it in enabledPlugins. Defaults to true. A plugin that an enabled plugin depends on starts enabled regardless. The same field in the marketplace entry overrides this one.
Once a user’s enabledPlugins entry is written, it persists across plugin updates, so changing defaultEnabled in a later release doesn’t change the setting for an existing user.
dependencies
Plugins that must be enabled for this one to work. Each entry is "name", "name@marketplace", or { "name": "...", "marketplace": "...", "version": "..." }. Bare names resolve against this plugin’s own marketplace. See dependency constraints.
settings
Settings Claude Code applies while the plugin is enabled. Only agent and subagentStatusLine take effect; other keys are dropped at load. A settings.json at the plugin root takes precedence over this key. See Default settings.
Component path forms
Every component key accepts a path relative to the plugin root.hooks, mcpServers, lspServers, and experimental.monitors also accept inline configuration, commands also accepts an object map, and mcpServers also accepts MCP bundle paths and URLs. The examples that follow show each accepted shape once. For what each component does at runtime, see Plugin components.
Path-only fields
agents, skills, outputStyles, workflows, and experimental.themes take one path or an array of paths. agents entries must be .md files, and skills entries must be directories. The other three accept a directory or a file.
commands
commands takes a path, an array of paths, or an object map. A path names a flat .md command file or a directory. In the object map, each key becomes the command name after the plugin prefix. For example, "about" in plugin deploy-tools runs as /deploy-tools:about.
Each value sets exactly one of source or content, and an entry that sets both or neither fails validation. The other fields in this table are optional:
This map declares one command from a file and one from inline content:
hooks
hooks takes a .json file path, an inline hooks object in the same shape as hooks in settings.json, or an array mixing both. For hook events and handler fields, see the hooks reference.
Claude Code merges whatever you declare with hooks/hooks.json when that file exists.
mcpServers
mcpServers takes a .json file path, an MCP bundle path or URL, an inline map, or an array mixing them. For server config fields, see plugin-provided MCP servers.
Claude Code loads .mcp.json at the plugin root first, then each declared shape in order. A server name declared later replaces an earlier one.
An mcpServers value takes one of these shapes:
A bundle path or URL must end in
.mcpb or .dxt. Any other extension fails validation.
lspServers
lspServers takes a .json file path, an inline map of server name to config, or an array of either.
Claude Code loads .lsp.json at the plugin root first, then each declared config in order. A server name declared later replaces an earlier one.
Each server config is a strict object with these fields. An unknown key fails validation.
This inline config runs
gopls for .go files:
monitors
experimental.monitors takes a .json file path or the inline array. When you omit the key, Claude Code loads monitors/monitors.json if it exists.
Each entry is a strict object with these fields.
This inline array declares one monitor that starts the first time the
deploy skill runs:
command can’t reference ${user_config.*}. See Fields that run through a shell.
Path rules
Every component path in a manifest is relative to the plugin root and must start with./. A path such as commands/foo.md fails validation. skills and mcpServers each accept one form outside that rule:
skills: also accepts".". Both"."and"./"denote the plugin root. Before v2.1.221,"."failed manifest validation, so use"./"when the plugin must load on earlier versionsmcpServers: also accepts anhttps://bundle URL
Containment and existence
Every component path must resolve inside the plugin root and must exist.claude plugin validate doesn’t check the outputStyles, lspServers, monitors, or themes paths, so a bad path in those fields fails only when the plugin loads:
- Containment: a path that resolves outside the plugin root doesn’t load, and the
/pluginErrors tab shows<component> path escapes plugin directory: <path>. A path containing..is the usual case, andclaude plugin validatereports it asPath contains ".." which could be a path traversal attempt - Existence: a path that doesn’t exist doesn’t load, and the
/pluginErrors tab shows<component> path not found: <path>.claude plugin validatereports it asPath not found
How each key combines with its default location
Each component key either replaces its default location, adds to it, or merges with it:- Replaces the default:
commands,agents,outputStyles,workflows,experimental.themes,experimental.monitors. When you setcommands, the defaultcommands/directory isn’t scanned. To keep the default and add more, list it explicitly:"commands": ["./commands/", "./extras/"] - Adds to the default:
skills. Theskills/directory is still scanned, and the listed directories load alongside it - Merges:
hooks,mcpServers,lspServers. The default file loads first, and what the manifest declares merges into it, as described under Component path forms
commands/ and also sets the manifest key that replaces it, Claude Code loads the manifest paths and not the folder. claude plugin list and the /plugin interface then show the warning Default <folder>/ folder is ignored because the manifest sets "<key>".
To avoid the warning, set the key to a path inside that folder: "commands": ["./commands/deploy.md"] names a file in the default folder and produces no warning.
User configuration
userConfig declares values Claude Code prompts the user for when the plugin is enabled, so users don’t edit settings.json themselves.
Keys are identifiers made of letters, digits, and underscores, and can’t start with a digit.
Each value is a strict object with these fields. An unknown key fails validation.
Each option of each enabled plugin also appears as a row in the
/config panel, except sensitive options and multiple lists. The /config rows require Claude Code v2.1.269 or later.
This userConfig declares an endpoint and a masked token:
Limit a field to fixed options
Setoptions on a userConfig field to make users pick its value from a fixed list.
To limit a tone field to three options, list them in options and set default to one of them:
options on any field, users on Claude Code versions before v2.1.271 can’t load the plugin.
options applies to a string field that isn’t multiple or sensitive. Set default to one of the listed values, or set required: true so the user must pick one. Each option is a plain label of 1 to 64 characters, and claude plugin validate, which you run in your shell, reports anything else it rejects. A plugin whose options break these rules fails to load.
Where values are stored
Non-sensitive values are saved underpluginConfigs in the user’s settings.json. Sensitive values go to the platform’s secure credential store instead. The settings page lists which settings files pluginConfigs is read from.
Reference a saved value
Reference a saved value where the plugin needs it, in one of two forms:${user_config.KEY}: substituted in MCP server config, LSP server config, exec-form hookargs, and skill and agent content. In skill and agent content, only non-sensitive values are substituted, and a sensitive value there becomes a placeholderCLAUDE_PLUGIN_OPTION_<KEY>: exported to hook processes for every option, with<KEY>uppercased. A shell-form hook reads$CLAUDE_PLUGIN_OPTION_API_TOKENforapi_token
Fields that run through a shell
Shell-form hook commands, monitor commands, and MCPheadersHelper reject ${user_config.*}. A component that references it in one of these fields fails with an error instead of running, because the field’s value is passed to a shell that would re-parse the substituted value.
The table shows how the value can reach each of these fields instead.
Channels
channels declares the message channels a plugin provides, such as a bridge to a chat app. When you declare one, Claude Code can prompt for the channel’s configuration when the plugin is enabled. For how the server injects messages, see the channels reference.
Each entry is a strict object bound to one of the plugin’s MCP servers, with these fields:
This manifest binds a channel to the plugin’s
telegram MCP server and prompts for a bot token that substitutes into the server’s env:
Environment variables
Claude Code provides three path variables to plugin components. Reference them as${NAME} in the fields listed under Where each variable resolves, and read them as environment variables in the processes that receive them.
${CLAUDE_PLUGIN_ROOT} changes when the plugin updates, so don’t write state there. For where the root moves and when the old directory is cleaned up, see the loading page.
When you uninstall the plugin from the last place it’s installed, the ${CLAUDE_PLUGIN_DATA} directory is deleted unless you pass --keep-data.
Where each variable resolves
In each plugin component,${...} references resolve inline in specific fields, and some components also receive the variables in their process environment:
The variables aren’t present in the environment of commands Claude runs through the Bash tool, in the main session or in a subagent. In skill, command, and agent content, write the
${...} reference in the Markdown body instead, and Claude Code substitutes the path inline when it loads the content.
Quoting and path separators
Keep each substituted path a single argument:- Hook commands: use exec form with
argsso each path is one argument with no quoting - Shell-form hooks and monitor commands: wrap the variable in double quotes so a path with spaces stays one word
Standard layout
Each component type has a default location under the plugin root, used when the manifest doesn’t point elsewhere.
A plugin that uses every default location, plus a
scripts/ folder that its hooks call, is laid out like this:
CLAUDE.md at the plugin root isn’t loaded as context, and claude plugin validate warns when it finds one. To include instructions that load into Claude’s context, put them in a skill.
Marketplace entries and the manifest
A marketplace entry accepts every field on this page alongside its own fields, includingstrict.
The strict field decides whether the entry may add components to a plugin that has its own plugin.json. It defaults to true.
How entry fields combine with plugin.json
The entry either serves as the manifest, adds components to it, or conflicts with it:
- No
plugin.json: the entry is the manifest, regardless ofstrict. Entryhooksloads only in the inline object form. For a file path or array there, the/pluginErrors tab shows anot yet supported in a marketplace entryerror plugin.jsonpresent,strictunset ortrue: Claude Code loads the manifest and appends the entry’scommands,agents,skills,outputStyles, andthemesto it. Forhooks, the entry’s matchers for an event replace the manifest’s matchers for that same event, and events only the manifest declares keep theirsplugin.jsonpresent,strict: false: an entry that declares any ofcommands,agents,skills,hooks,outputStyles, orthemesis a conflict, and the plugin fails to load withPlugin <name> has conflicting manifests
source is the marketplace root lists specific skills subdirectories, only those subdirectories load, and the plugin’s default skills/ directory isn’t scanned. A skills key in the manifest instead adds to the default.
Metadata precedence
Some metadata fields have a fixed precedence regardless ofstrict:
defaultEnabledand display fields: the entry’sdefaultEnabledand its display fields such asdisplayNameoverride the manifest’sversion: the manifest’sversionoverrides the entry’sname: when the entry lists the plugin under a differentnamethan the manifest,enabledPluginsuses the entry name, and components are namespaced under the manifest name
Next steps
- Add components to a plugin: what each component does at runtime, with an example that validates
- Marketplace reference: the entry fields a marketplace can set for your plugin
- Plugin commands reference:
claude plugin validateflags and output - Troubleshoot plugins: each validation message with its fix