If you’re building Claude Code plugins for other people to install, the hard part isn’t the plugin. It’s getting it to install cleanly on someone else’s machine — someone who didn’t set the thing up, doesn’t share your config, and shouldn’t have to debug your distribution choices before they can use it.
We learned most of this the slow way while publishing plugins to the Design Shaped marketplace. Here’s what we’d tell ourselves before the first one shipped.
Two repos, not one
There are two kinds of repo in this picture, and keeping them separate is the whole game.
The marketplace is the catalog. It holds a marketplace.json with one entry per plugin, and that’s mostly it. The source repo is where a plugin’s code, history, and releases actually live. The marketplace just points at it.
You could vendor every plugin’s code directly into the marketplace repo. Don’t, as a default. Give each plugin its own source repo and have the marketplace reference it. The reasons stack up fast:
- Each plugin gets its own issue tracker and its own release cadence.
- A plugin can be listed from a second marketplace later without copy-pasting code.
- The marketplace stays clean — a curation layer, not a code monorepo.
The exception is a plugin that’s small, marketplace-specific, and never going to move. Those are fine to vendor right inside the marketplace repo. But that’s the exception you reach for deliberately, not the path of least resistance.
Use source: url with HTTPS — never the github shorthand
This is the one that cost us the most before we understood it, so it gets stated plainly.
In your marketplace entry, point at the plugin like this:
"source": {
"source": "url",
"url": "https://github.com/owner/repo.git"
}
Not the github shorthand. The shorthand clones over SSH — git@github.com:… — and SSH fails on any machine whose known_hosts file doesn’t already have GitHub’s host key. That’s most designers. If you came up through GitHub Desktop or VS Code, you’ve been doing HTTPS git this whole time and never touched SSH. So at install time you hit Host key verification failed, with no obvious connection to the plugin you were trying to add.
HTTPS clones public repos with no auth and no setup. It just works. Always use it.
When someone reports the SSH host-key error, the fix is in the marketplace, not on their machine. Switch the entry from source: github to source: url with an HTTPS URL. Fix it once, for everyone.
Version with commit SHAs while you’re still iterating
While a plugin is young and changing, leave version unset — both in the plugin’s own manifest and in the marketplace entry. Users then get every push to the source repo’s main the next time they run /plugin update.
The trade-off is real: a broken commit ships immediately. The mitigation is branch discipline. Only merge to main when you’ve tested locally. Once a plugin stabilizes, set a real version like 1.0.0 and bump it on releases.
Namespacing is forced — design for it
The plugin name becomes a namespace prefix for its slash commands. So the command a user actually types is /<plugin-name>:<skill-name>. The bare /skill form doesn’t exist for marketplace plugins; the namespace is mandatory.
This only affects the slash form. The trigger keywords in a skill’s description still fire either way, so natural-language invocation is unchanged. But if you write docs or a README that tell people to type /something, write the namespaced form — that’s what they’ll actually have.
If you want the bare-slash shortcut for yourself locally, a three-line proxy command that calls the namespaced form gets you there without changing what you ship.
The update workflow you’ll run most
For routine changes to an already-published plugin, you’re working in the source repo, not the marketplace:
git checkout -b some-fix
# edit, then test locally against the working tree:
claude --plugin-dir .
# when it's good:
git push -u origin some-fix
gh pr create --base main --title "..."
gh pr merge --rebase --delete-branch
Users pick up the change on their next /plugin update. The marketplace doesn’t need to be touched at all for routine plugin changes — it’s only pointing at the repo, and the repo moved.
Adding a brand-new plugin
- Create the source repo with the standard shape: a
plugin.jsonmanifest, askills/<skill-name>/SKILL.md(its frontmatter just needs adescription), a README, a license, and — if you’re shipping a CLI — abin/entry. - Test locally with
claude --plugin-dir <path>. Confirm the skill triggers and any CLI lands on PATH. - Push the source repo to GitHub as public.
- Add it to the marketplace — append an entry to
marketplace.jsonpointing at the source repo over HTTPS. - Open a PR and merge the marketplace edit. People who’ve already added the marketplace pick it up with
/plugin marketplace update.
A note on the marketplace card copy
The description on a plugin’s card is public-facing, so write it like it’s public-facing. Describe what the plugin does. If it inherits a particular voice or approach, you can name that. What you don’t do is describe it as a “clone of” someone — it reads like impersonation, and it undersells the tool. Lead with the work it does for the person installing it.
The acid test: zero local install
The trap with publishing a plugin you also use yourself is that it works on your machine because of leftover local copies — a standalone skill folder, a CLI you installed by hand months ago. So before you trust that a plugin installs cleanly for someone else, prove it on your own machine by removing your local advantage:
- Move any local standalone copies of the skill and its CLI to the Trash (recoverable for 30 days if you’re wrong).
- Restart Claude Code so the plugin list rebuilds.
- Install the plugin fresh from the marketplace, trigger the skill by natural language, and run any CLI as a bare command.
- If it fails, restore from Trash and fix the real problem before publishing.
If it passes that, it’ll pass for the person who’s installing it for the first time with none of your history.
The small stuff that bites
A few failure modes worth recognizing on sight:
- A skill doesn’t show up after install. The skill list is cached at session start. Reload plugins or restart Claude Code.
- A CLI isn’t on PATH. Its
bin/entry has to be executable, and it has to resolve its sibling files relative to the plugin root — not via a hardcoded home-directory path. Hardcoded paths break inside the marketplace’s cache layout. - Updates don’t reach people. There’s no auto-pull. They have to run
/plugin updatethemselves. Say so in the README, or they’ll quietly run an old version for weeks.
None of this is hard once you’ve seen it. It’s just easy to get wrong the first time, in ways that land on the person installing your work rather than on you. Get the distribution boring and the plugin gets to be the interesting part.