How the Claude Code Plugin Discovers Manifests Through marketplace.json
The Claude Code plugin discovers skill manifests by parsing a catalog file at .claude-plugin/marketplace.json, validating local relative paths, and deriving absolute directories to scan for SKILL.md files.
The marketplace.json file serves as a central registry for Claude Code plugins, allowing the system to locate and load skill definitions across multiple plugin directories. This discovery mechanism is implemented in the vercel-labs/skills repository, where the getPluginSkillPaths function orchestrates the entire process.
The Discovery Pipeline in plugin-manifest.ts
The core discovery logic lives in src/plugin-manifest.ts. The exported function getPluginSkillPaths follows a strict seven-step pipeline to transform the marketplace.json catalog into a list of searchable skill directories.
Step 1: Read and Parse the Catalog File
The function first attempts to load /.claude-plugin/marketplace.json from the project root:
const content = await readFile(join(basePath, '.claude-plugin/marketplace.json'), 'utf-8');
const manifest: MarketplaceManifest = JSON.parse(content);
This parsing happens at lines 77-81. If the file is missing or contains invalid JSON, the system falls back to single-plugin discovery via plugin.json (discussed in Step 6).
Step 2: Validate Root and Source Paths
The Claude Code convention requires all paths to be relative and start with ./. The function validates the optional pluginRoot and each plugin's source property:
const validPluginRoot = pluginRoot === undefined || isValidRelativePath(pluginRoot);
if (plugin.source !== undefined && !isValidRelativePath(plugin.source)) continue;
Invalid paths are silently skipped at lines 83-93. This prevents absolute path injection and maintains sandbox boundaries.
Step 3: Ignore Remote Plugin Entries
The marketplace.json format supports remote plugins via object-based source properties (e.g., {source: "...", repo: "…"}). However, the discovery mechanism only processes local filesystem plugins:
if (typeof plugin.source !== 'string' && plugin.source !== undefined) continue;
This check at line 89 filters out remote entries, as they cannot be scanned for SKILL.md files on the local filesystem.
Step 4: Build Absolute Plugin Base Paths
For each valid plugin, the function constructs the absolute directory path by joining components:
const pluginBase = join(basePath, pluginRoot ?? '', plugin.source ?? '');
This composition at line 94 yields the concrete directory where the plugin's skill definitions reside.
Step 5: Collect Skill Directories via addPluginSkillPaths
The helper function addPluginSkillPaths (lines 54-75) performs the final directory collection:
addPluginSkillPaths(pluginBase, plugin.skills);
This helper:
- Always adds the conventional
skills/subdirectory - Adds parent directories of explicitly declared
skillsentries (after validating they are relative and contained within the project) - Performs path traversal checks to prevent directory escape attacks
The discovered directories are pushed onto the searchDirs array for return.
Step 6: Fallback to plugin.json
When marketplace.json is absent or invalid, the same discovery logic applies to a single-plugin manifest at .claude-plugin/plugin.json:
// Lines 102-107: fallback handling
This ensures backward compatibility and simpler single-plugin projects.
Step 7: Return Search Directories
The function returns an array of absolute directories. The consumer in src/skills.ts walks these directories to locate individual SKILL.md files and load complete skill definitions.
Practical Example: Discovering Skill Roots
Here's how to use the discovery API in your own tooling:
import { getPluginSkillPaths } from '@/plugin-manifest';
async function listSkillRoots() {
const roots = await getPluginSkillPaths(process.cwd());
console.log('Skill search roots discovered from marketplace.json:');
roots.forEach(r => console.log(' -', r));
}
listSkillRoots();
Sample output for a project with two plugins:
Skill search roots discovered from marketplace.json:
- /my/project/.claude-plugin/plugins/plugin-a/skills
- /my/project/.claude-plugin/plugins/plugin-b/custom-skills
Key Files in the Discovery System
| File | Purpose |
|---|---|
src/plugin-manifest.ts |
Core implementation of getPluginSkillPaths and validation helpers |
src/skills.ts |
Consumes discovered directories to locate SKILL.md files |
tests/plugin-manifest-discovery.test.ts |
Test coverage for valid, invalid, and attack scenarios |
README.md (Skill discovery section) |
Documentation of the fallback mechanism |
Summary
- The Claude Code plugin discovers manifests through
marketplace.jsonby reading, parsing, and validating a catalog file at.claude-plugin/marketplace.json - The
getPluginSkillPathsfunction insrc/plugin-manifest.tsenforces the./relative path convention and ignores remote plugin entries - Valid plugins are resolved to absolute directories, with the
addPluginSkillPathshelper collecting both conventionalskills/subdirectories and explicitly declared skill paths - The system falls back to
plugin.jsonfor single-plugin projects when no marketplace catalog exists - Discovered directories are returned to
src/skills.tsfor finalSKILL.mdfile location
Frequently Asked Questions
What happens if marketplace.json contains invalid JSON?
The discovery mechanism catches parsing errors and falls back to attempting discovery via .claude-plugin/plugin.json. If that also fails, the function returns an empty array of search directories, causing no skills to be loaded for that project.
Can marketplace.json reference plugins outside the project directory?
No. The path validation in getPluginSkillPaths requires all source paths to pass isValidRelativePath, which ensures they start with ./ and contain no directory traversal sequences that would escape the project root. Absolute paths are rejected and ignored.
How does the plugin handle remote plugin definitions?
Remote plugins defined with object-style source properties (containing repo or other remote identifiers) are filtered out at line 89 of src/plugin-manifest.ts. The discovery system only processes local filesystem plugins that can be scanned for SKILL.md files.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →