# How the Claude Code Plugin Discovers Manifests Through marketplace.json

> Learn how the Claude Code plugin finds skill manifests by parsing marketplace.json, validating paths, and scanning for SKILL.md files to discover plugins.

- Repository: [Vercel Labs/skills](https://github.com/vercel-labs/skills)
- Tags: how-to-guide
- Published: 2026-04-23

---

**The Claude Code plugin discovers skill manifests by parsing a catalog file at [`.claude-plugin/marketplace.json`](https://github.com/vercel-labs/skills/blob/main/.claude-plugin/marketplace.json), validating local relative paths, and deriving absolute directories to scan for [`SKILL.md`](https://github.com/vercel-labs/skills/blob/main/SKILL.md) files.**

The [`marketplace.json`](https://github.com/vercel-labs/skills/blob/main/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`](https://github.com/vercel-labs/skills/blob/main/src/plugin-manifest.ts). The exported function `getPluginSkillPaths` follows a strict seven-step pipeline to transform the [`marketplace.json`](https://github.com/vercel-labs/skills/blob/main/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`](https://github.com/vercel-labs/skills/blob/main//.claude-plugin/marketplace.json) from the project root:

```typescript
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`](https://github.com/vercel-labs/skills/blob/main/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:

```typescript
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`](https://github.com/vercel-labs/skills/blob/main/marketplace.json) format supports remote plugins via object-based `source` properties (e.g., `{source: "...", repo: "…"}`). However, the discovery mechanism only processes local filesystem plugins:

```typescript
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`](https://github.com/vercel-labs/skills/blob/main/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:

```typescript
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:

```typescript
addPluginSkillPaths(pluginBase, plugin.skills);

```

This helper:
- Always adds the conventional `skills/` subdirectory
- Adds parent directories of explicitly declared `skills` entries (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`](https://github.com/vercel-labs/skills/blob/main/marketplace.json) is absent or invalid, the same discovery logic applies to a single-plugin manifest at [`.claude-plugin/plugin.json`](https://github.com/vercel-labs/skills/blob/main/.claude-plugin/plugin.json):

```typescript
// 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`](https://github.com/vercel-labs/skills/blob/main/src/skills.ts) walks these directories to locate individual [`SKILL.md`](https://github.com/vercel-labs/skills/blob/main/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:

```typescript
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`](https://github.com/vercel-labs/skills/blob/main/src/plugin-manifest.ts) | Core implementation of `getPluginSkillPaths` and validation helpers |
| [`src/skills.ts`](https://github.com/vercel-labs/skills/blob/main/src/skills.ts) | Consumes discovered directories to locate [`SKILL.md`](https://github.com/vercel-labs/skills/blob/main/SKILL.md) files |
| [`tests/plugin-manifest-discovery.test.ts`](https://github.com/vercel-labs/skills/blob/main/tests/plugin-manifest-discovery.test.ts) | Test coverage for valid, invalid, and attack scenarios |
| [`README.md`](https://github.com/vercel-labs/skills/blob/main/README.md) (Skill discovery section) | Documentation of the fallback mechanism |

## Summary

- The Claude Code plugin discovers manifests through [`marketplace.json`](https://github.com/vercel-labs/skills/blob/main/marketplace.json) by reading, parsing, and validating a catalog file at [`.claude-plugin/marketplace.json`](https://github.com/vercel-labs/skills/blob/main/.claude-plugin/marketplace.json)
- The `getPluginSkillPaths` function in [`src/plugin-manifest.ts`](https://github.com/vercel-labs/skills/blob/main/src/plugin-manifest.ts) enforces the `./` relative path convention and ignores remote plugin entries
- Valid plugins are resolved to absolute directories, with the `addPluginSkillPaths` helper collecting both conventional `skills/` subdirectories and explicitly declared skill paths
- The system falls back to [`plugin.json`](https://github.com/vercel-labs/skills/blob/main/plugin.json) for single-plugin projects when no marketplace catalog exists
- Discovered directories are returned to [`src/skills.ts`](https://github.com/vercel-labs/skills/blob/main/src/skills.ts) for final [`SKILL.md`](https://github.com/vercel-labs/skills/blob/main/SKILL.md) file 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`](https://github.com/vercel-labs/skills/blob/main/.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`](https://github.com/vercel-labs/skills/blob/main/src/plugin-manifest.ts). The discovery system only processes local filesystem plugins that can be scanned for [`SKILL.md`](https://github.com/vercel-labs/skills/blob/main/SKILL.md) files.