
How to Build a Private Claude Code Plugin Marketplace for Your Team
A private Claude Code plugin marketplace is a git repo with one file in it: .claude-plugin/marketplace.json. Your team runs /plugin marketplace add your-org/claude-plugins, installs what they need, and the git host handles who's allowed to see it. There's no server to run and no permissions layer to build. The setup takes about twenty minutes — most of which you'll spend on the two gotchas below, not on the JSON.
The Copy-Paste Problem This Solves
Here's the shape of the mess. You write a genuinely good skill — a deploy checklist, a code-review pass, a hook that runs your linter after every edit. It lives in .claude/ in one repo. Then you want it in the next repo, so you copy the folder. Then a teammate wants it, so you paste it in Slack. Six weeks later there are four versions of that skill on four machines, three of them stale, and nobody knows which one is right.
I hit this across the projects on my own drive before I bothered to fix it. Every new client repo started with me copying a .claude/ directory out of the last one and then quietly editing it, which meant my “standard” setup had silently forked into a half-dozen dialects. Copy-paste is a distribution strategy with no version, no rollback, and no way to tell who's running what.
A marketplace fixes exactly that, and it does it with less machinery than you'd expect. Everything below is a git repo and a JSON file.
The mental model:
A plugin is a versioned bundle of skills, agents, hooks, and MCP config. A marketplaceis a catalog that says where those bundles live. Neither one is a service you host — they're both just files in a repo Claude Code clones with your own git credentials.
Step 1: Turn Your .claude/ Folder Into a Plugin
A plugin is a directory with a manifest at .claude-plugin/plugin.json. That's the only file that goes inside .claude-plugin/. Everything else sits at the plugin root:
acme-devtools/ ├── .claude-plugin/ │ └── plugin.json # <- ONLY this goes in here ├── skills/ │ └── deploy-check/ │ └── SKILL.md ├── agents/ ├── hooks/ │ └── hooks.json └── .mcp.json
This is the single most common mistake: putting skills/, agents/, or hooks/ inside .claude-plugin/. They belong at the plugin root and Claude Code won't find them anywhere else.
The manifest itself is four fields, two of which are optional:
{
"name": "acme-devtools",
"description": "Deploy checks and review passes for Acme services",
"version": "1.2.0",
"author": { "name": "Platform Team" }
}Migrating existing config is mostly cp -r: copy .claude/skills, .claude/agents, and .claude/commands to the plugin root. Hooks move from the hooks object in settings.json into hooks/hooks.json— same format, new home. If you haven't written hooks yet, that's the highest-leverage thing to put in your first plugin; I went through the patterns in Claude Code hooks in production.
Test before you distribute anything: claude --plugin-dir ./acme-devtools loads it without installing. Your skills show up namespaced, as /acme-devtools:deploy-check. Then run claude plugin validate ./acme-devtools — it catches schema errors before your team does.
One caveat worth knowing up front: after migrating, delete the originals from .claude/. Project and user agents/ definitions override same-named plugin agents, so the plugin copy stays inert until the old one is gone.
Step 2: Write the marketplace.json
The catalog is one file at .claude-plugin/marketplace.json in your marketplace repo. Three required fields: name, owner, and plugins.
{
"name": "acme-tools",
"owner": {
"name": "Platform Team",
"email": "platform@acme.com"
},
"plugins": [
{
"name": "acme-devtools",
"source": "./plugins/acme-devtools",
"description": "Deploy checks and review passes",
"version": "1.2.0"
},
{
"name": "deployment-tools",
"source": { "source": "github", "repo": "acme/deploy-plugin" },
"description": "Deployment automation"
}
]
}Note the two source styles. A relative path means the plugin lives in this same repo — the simplest setup, and the one I'd start with. An object source points at a different repo, which is useful when a plugin already has its own home. If your plugins are scattered across private repos and you'd rather consolidate, a git submodule, subtree, or a CI copy step into the marketplace repo works fine.
Pick the namecarefully — it's public facing. Users type it when installing: /plugin install acme-devtools@acme-tools. Each user can only register one marketplace per name, so a second one with the same name replaces the first. Anthropic also reserves a list of official-sounding names (claude-plugins-official, anthropic-plugins, and similar), and those checks re-run every load — not just at registration.
Step 3: Host It Privately (There's Nothing to Host)
Push the repo. That's the hosting step. GitHub, GitLab, Bitbucket, a self-hosted git server — all of them work, and private repos are supported out of the box, because Claude Code reuses your existing git credential helpers. If git clone works in your terminal, it works in Claude Code.
# GitHub shorthand (clones over SSH by default) /plugin marketplace add acme/claude-plugins # any git URL /plugin marketplace add https://gitlab.com/acme/plugins.git /plugin marketplace add ssh://git@git.internal.acme.com/plugins.git # then install /plugin install acme-devtools@acme-tools
Access control is inherited, not configured. Whoever can clone the repo can install the plugins; whoever can't, can't. For most teams that's the entire security model, and it's the right one — you already manage repo access.
Two flags worth knowing: owner/repo shorthand clones over SSH by default, and CLAUDE_CODE_PLUGIN_PREFER_HTTPS=1 flips that to HTTPS if your team is set up with token auth instead of keys.
The Two Things That Will Bite You
1. Private auto-updates fail silently over HTTPS
This is the one that costs people an afternoon. The commands you type — /plugin install, /plugin marketplace update — use your git credential helpers and authenticate fine. But the background refresh disables credential helpers for its git pull, so on a private HTTPS remote that pull can't authenticate at all. Claude Code then falls back to re-cloning the whole marketplace, which can time out on a big repo. The symptom is maddening: manual updates work, automatic ones intermittently don't.
Three fixes, in the order I'd try them:
- Use an SSH remote with the key loaded in ssh-agent. Background pulls authenticate the same way your manual ones do, and the problem disappears entirely.
- Set CLAUDE_CODE_PLUGIN_KEEP_MARKETPLACE_ON_FAILURE=1 so a failed background pull keeps the last good clone instead of deleting and re-cloning it. Your plugins keep working from the last synced state.
- If you must use HTTPS, configure a scoped global git URL rewrite that embeds a read-only token. It survives the disabled credential helpers because the token is in the URL itself.
# scope the rewrite to the ONE repo — never to the bare host git config --global \ url."https://x-access-token:TOKEN@github.com/acme/plugins".insteadOf \ "https://github.com/acme/plugins"
Scope that rewrite to the repository or org path and nothing broader. A rewrite whose base is just the hostname applies to every fetch and push to that host on the machine — including pushes to your own repos — and it stores the token in plaintext in your gitconfig. Read-only token, narrow path, or skip this option.
Also worth knowing: setting GITHUB_TOKEN in your environment does not by itself enable background auth. Tokens only take effect through a configured credential helper, such as the one gh auth setup-git installs.
2. Skipping the version field ships every commit
version is optional in plugin.json, and that optionality is a trap. Omit it on a git-distributed plugin and the commit SHA becomes the version, so every commit counts as a new releasefor everyone who installed it. Your half-finished Tuesday afternoon refactor lands in a teammate's session.
Set explicit semver and bump it deliberately. It's one line of JSON and it's the difference between publishing and leaking. Note that claude plugin validatewill warn you when a marketplace entry's versiondoesn't match the one in that plugin's own manifest — a drift that's easy to introduce and hard to spot.
Step 4: Push It to the Whole Team Automatically
Telling people to run /plugin marketplace add in Slack is the copy-paste problem wearing a hat. Commit the marketplace into the project's .claude/settings.json instead, and teammates get prompted to install it the moment they trust the folder:
{
"extraKnownMarketplaces": {
"acme-tools": {
"source": { "source": "github", "repo": "acme/claude-plugins" }
}
},
"enabledPlugins": {
"acme-devtools@acme-tools": true,
"deployment-tools@acme-tools": true
}
}extraKnownMarketplaces registers the catalog; enabledPluginsdecides what turns on by default. Ship both. Without the second one, every developer still has to remember to enable the right plugins, which they won't.
For CI and containers there's a build-time path too. Point CLAUDE_CODE_PLUGIN_CACHE_DIR at a directory during your image build, install the plugins there, then set CLAUDE_CODE_PLUGIN_SEED_DIR to that path at runtime. Claude Code reads the seed on startup and skips cloning entirely — which matters in CI, where the private-repo auth problem above is at its worst. In GitHub Actions, remember the default workflow token can only reach its own repo, so a marketplace in a separate private repo needs a PAT or app token.
Plugin or Plain .claude/ Folder? Pick by Blast Radius
Not everything deserves to be a plugin. The honest split is about how many people and repos the thing needs to reach:
| Question | Standalone .claude/ | Plugin + marketplace |
|---|---|---|
| Scope | One project, one machine | Every project, every teammate |
| Skill name | /deploy | /acme-devtools:deploy |
| Versioning | Whatever's in the repo right now | Semver, opt-in updates, rollback |
| Distribution | Copy the folder and hope | /plugin install |
| Setup cost | Zero | One manifest, one catalog |
| Best for | Experiments, one-off project rules | Anything two or more people rely on |
My rule: iterate in .claude/ until the thing stops changing every day, then package it. Premature plugins are a tax you pay on every edit — you bump a version, push, and wait, instead of just saving a file. The namespacing cuts both ways too: it prevents collisions, but /acme-devtools:deploy is more to type than /deploy, and people notice.
If you're still deciding what actually belongs in the bundle, the skills-versus-everything-else question is worth settling first — I compared the options in Claude Skills vs MCP and Claude Skills vs subagents. If your plugin ships an .mcp.json that points at a hosted server, read how to authenticate a remote MCP server before you distribute it — you don't want a plugin install to be the thing that leaks a token.
Questions People Actually Ask
Do I need a separate repo for the marketplace?
No. A single repo can hold both the catalog and the plugins, with source entries pointing at relative paths like ./plugins/acme-devtools. Start there. Split things out only when a plugin genuinely needs its own release cycle.
Can plugins share code with each other?
Not through relative paths. When someone installs a plugin, Claude Code copies that plugin's directory to a cache location, so a reference to ../shared-utils breaks — the file was never copied. Symlinks are the supported way to share files across plugins.
What if I just want it on my own machines?
Skip the marketplace. claude plugin init my-tool scaffolds a plugin straight into your skills directory, where it auto-loads with no marketplace and no install step. That's the right amount of ceremony for a solo setup.
Nothing loaded after I installed. What broke?
Check the install summary first — if it says Run /reload-plugins to activate, do that. If skills still don't appear, you almost certainly nested skills/ inside .claude-plugin/. Run claude plugin validate and check the /pluginmanager's Errors tab.
The Short Version
- A marketplace is a git repo with .claude-plugin/marketplace.json in it. There is no server to run.
- Private repos work out of the box — Claude Code reuses your git credentials, and your git host is the access-control layer.
- Only plugin.json goes inside .claude-plugin/. skills/, agents/, and hooks/ live at the plugin root.
- Set an explicit version. Without one, every commit ships to everyone who installed the plugin.
- Private HTTPS auto-updates fail because the background pull disables credential helpers. Use SSH, or set CLAUDE_CODE_PLUGIN_KEEP_MARKETPLACE_ON_FAILURE=1.
- Commit extraKnownMarketplaces AND enabledPlugins so the rollout doesn't depend on people remembering.
- Iterate in .claude/, package when it stops changing daily.
The official references are worth a skim once you've got the shape: creating plugins and distributing a marketplace. And if you're building the workflows that go inside the plugin, I wrote up how I structure them in dynamic Claude Code workflows.
Want Your Team on One Standard Instead of Six Forks?
I build and standardize AI development tooling — Claude Code plugins, hooks, MCP servers, and the n8n automations behind them — for teams that are past the copy-paste stage. If your engineers are each running their own dialect of the same setup, let's fix that.
Related Posts
AI Agents
Claude Skills vs Subagents: When to Use Each
Claude skills and subagents solve two different problems, and mixing them up is the fastest way to waste context. A skill is reusable instructions loaded into your current conversation — same model, same context, no isolation; it changes how the agent you're already talking to behaves. A subagent is a separate assistant with its own fresh context window, system prompt, tools, and optionally its own model, doing work you never see and returning only a summary. Use a skill for a repeatable procedure that needs the current context — a report format, a QA checklist, a deploy runbook. Use a subagent when you need isolation, parallelism, or context protection: fan out three to five at once, keep a huge read in a throwaway window, run a locked-down reviewer. The strongest setups use both — a skill defines the how, a subagent provides the isolated, parallel where.
AI Agents
Claude Code vs n8n (2026): The Rule I Use to Pick — and Why I Ship Both
They're not competitors — Claude Code builds software, n8n runs operations. The one-line test: if you can draw the task as fixed boxes before running it, it's n8n; if the path is discovered by reasoning, it's Claude Code. Real cost and reliability trade-offs from production, a full comparison table, and the handoff pattern where n8n owns the plumbing and hands the one ambiguous step to a Claude Code agent.
AI Agents
Claude Code Hooks: The 6 I Actually Run in Production (2026)
Hooks are shell commands that fire automatically at lifecycle events — so you enforce a rule instead of hoping the model remembers it. A prompt is a suggestion; a PreToolUse hook is a wall the agent can't walk through. The six I put on every autonomous Claude Code agent: block secret writes, protect .env and migrations, auto-format edited files, run fast tests, inject git context on session start, and ping Telegram when the run finishes.