installs.me
< cd ~/blog

·5 min read#marketplace#reference

marketplace.json: A Field Guide

Everything that goes wrong with Claude Code plugin distribution goes wrong in one file: marketplace.json. It is maybe forty lines of JSON, and half its failure modes are silent. This is the reference page I wanted when I built the installs.me pipeline, which generates one of these files per persona and has to get it right unattended.

Quick orientation first. A Claude Code plugin bundles up to four surfaces: skills (SKILL.md files with YAML frontmatter, loaded on demand), slash commands, hooks, and MCP servers. A marketplace is the index that tells Claude Code where plugins live. Users run /plugin marketplace add <url> to register the index, then /plugin install <name>@<marketplace> to pull a plugin from it. Verified against Claude Code CLI 2.1.x.

The top level

{
  "name": "lautaro-installs",
  "owner": {
    "name": "Lautaro Schiaffino",
    "email": "hola@lauta.blog"
  },
  "plugins": []
}

Three fields matter.

name is the marketplace's identity. Kebab-case, no spaces. This is the string after the @ in the install command: /plugin install lautaro@lautaro-installs. Rename it and every install instruction you have ever published breaks, so treat it as immutable from day one.

owner is metadata shown to the installing user. An object with name and optionally email. It builds trust at install time; fill it in.

plugins is the array of plugin entries. One entry per installable plugin.

The file lives at .claude-plugin/marketplace.json when the marketplace is a git repo or local directory. It can also be served directly at a URL, which is what installs.me does: https://installs.me/lautaro responds with the JSON itself. Both work with /plugin marketplace add. The URL form is where people get hurt, more on that below.

Plugin entries

{
  "name": "lautaro",
  "description": "Thinks, writes and advises like Lautaro Schiaffino.",
  "version": "1.2.0",
  "author": { "name": "Lautaro Schiaffino" },
  "source": {
    "source": "git-subdir",
    "url": "https://github.com/installs-me/personas.git",
    "path": "plugins/lautaro"
  }
}

name is required and must match the name in the plugin's own .claude-plugin/plugin.json. A mismatch is the most common self-inflicted install failure: the marketplace advertises one name, the fetched plugin declares another, and the install errors after the clone instead of before it.

description is what users see in the /plugin browser before deciding to install. One sentence, front-loaded. This is your listing copy.

version is informational but useful: bump it when you ship, so users can tell whether a reinstall actually pulled anything new.

source is where everything interesting and everything broken happens.

The four source types

Relative path. "source": "./plugins/lautaro". The plugin is a subdirectory of the marketplace itself. This is the simplest form and it works perfectly when the marketplace was added from a git repo or a local path, because Claude Code has the whole tree on disk. It fails, silently, when the marketplace was added from a bare HTTP URL. Claude Code fetched one JSON document; there is no tree to resolve ./plugins/lautaro against. Nothing warns you when you write the file. The failure surfaces at install time, on someone else's machine. If your marketplace is served over HTTP, relative paths are dead on arrival. Every plugin source must independently point somewhere git-clonable.

github. {"source": "github", "repo": "owner/repo"}. Clones the repo and treats the repo root as the plugin: .claude-plugin/plugin.json must sit at the top. Clean when the plugin is the whole repo. Failure modes: private repos fail for anyone without git credentials for that host, and the entire repo history comes down on install, so a repo with large assets in history makes every install slow forever.

git. {"source": "git", "url": "https://gitlab.com/owner/repo.git"}. Same semantics as github but for any git host: GitLab, Bitbucket, self-hosted Gitea, whatever git clone accepts. Use the HTTPS clone URL. SSH URLs shift the auth burden onto every installer's SSH config, which is a support ticket generator.

git-subdir. The workhorse for anyone publishing more than one plugin from one repo. A git URL plus a path into the tree:

{
  "source": "git-subdir",
  "url": "https://github.com/installs-me/personas.git",
  "path": "plugins/lautaro"
}

Claude Code clones the repo and installs only the subdirectory at path. The path is relative to the repo root, no leading slash, and it must contain .claude-plugin/plugin.json. Failure modes: a path typo fails at install, not at authoring, so test every entry; and the clone is still the full repo, so the large-repo tax from github sources applies here too.

This is the shape installs.me generates. One monorepo of personas, one HTTP-served marketplace.json, every plugin entry a git-subdir pointing back into the repo. HTTP marketplace plus git-backed sources is the only combination where a URL-served index actually installs.

A decision table

Marketplace served asRelative pathgithub / gitgit-subdir
Local directoryworksworksworks
Git repoworksworksworks
Bare HTTP URLsilent failworksworks

Debugging when it breaks anyway

Three checks resolve most reports.

First, fetch your marketplace URL with curl and run it through jq. A trailing comma or a redirect to an HTML error page both produce "invalid marketplace" errors that look like Claude Code's fault and are not.

Second, clone each source URL yourself, from a machine with no special credentials, and confirm path exists with a .claude-plugin/plugin.json whose name matches the marketplace entry. You are reproducing exactly what the installer does.

Third, remember Claude Code caches marketplaces locally after add. If you edited marketplace.json and the change is not showing up, remove and re-add the marketplace rather than staring at the file wondering why it lies.

The file is small enough that people write it once, by hand, and never think about it again. Write it wrong once, by hand, and every installer inherits the bug. Generate it, validate it, and test the install path from a clean machine before you publish the two commands.

Install a person

installs.me turns your files, calendar and calls into a Claude Code plugin that thinks like you. Anyone installs it with two commands:

/plugin marketplace add https://installs.me/lautaro
/plugin install lautaro@lautaro-installs