Publishing Claude Code Skills as Plugins
At OmbuLabs, we’ve been building Claude Code skills for Rails upgrades based on the FastRuby.io methodology. We had three skills working well locally, but distributing them meant telling users to git clone and cp -r files into ~/.claude/skills/. Not great.
Claude Code now supports plugins and marketplaces. This post walks through what I learned packaging our rails-upgrade skill as a plugin.
Skills vs. Plugins vs. Marketplaces
These three concepts confused me at first, so let me clarify:
- Skill: A
SKILL.mdfile inside a named directory. The agent reads it and gains new capabilities. This is the content. - Plugin: A package containing one or more skills (and optionally hooks, agents, MCP servers). Defined by
.claude-plugin/plugin.json. This is the container. - Marketplace: A git repo with a
marketplace.jsonat its root that lists available plugins. This is the distribution channel.
A single repo can serve as both a plugin and a marketplace, which is what makes this simple.
The File Structure
Here’s what a working plugin repo looks like:
1
2
3
4
5
6
7
8
9
10
11
my-skill-repo/
├── marketplace.json # At repo root (distribution)
├── .claude-plugin/
│ └── plugin.json # Plugin manifest (packaging)
├── my-skill/ # Skill directory
│ ├── SKILL.md # Required entry point
│ ├── reference/
│ ├── workflows/
│ └── ...
├── LICENSE
└── README.md
Two things tripped me up:
marketplace.jsonmust be at the repo root, not inside.claude-plugin/. I initially put it in the wrong place.plugin.jsonstays in.claude-plugin/. Onlyplugin.jsongoes there.
plugin.json
The plugin manifest tells Claude Code what’s inside the package:
1
2
3
4
5
6
7
8
9
{
"name": "rails-upgrade",
"description": "Analyzes Rails applications and generates upgrade reports...",
"version": "3.1.0",
"skills": "./",
"author": {
"name": "OmbuLabs.ai"
}
}
The skills field is a path to where Claude Code should look for skill subdirectories. Setting it to "./" means “look at the repo root for directories containing SKILL.md”. In our case, it finds rails-upgrade/SKILL.md.
I initially had "skills": "./rails-upgrade" which points into the skill directory itself. That’s wrong. The path should point to the parent that contains skill directories.
marketplace.json
This file makes your repo discoverable as a marketplace:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"$schema": "https://anthropic.com/claude-code/marketplace.schema.json",
"name": "fastruby-upgrade-rails",
"version": "1.0.0",
"metadata": {
"description": "Claude Code plugin for Rails upgrades by OmbuLabs.ai"
},
"owner": {
"name": "OmbuLabs.ai"
},
"plugins": [
{
"name": "rails-upgrade",
"source": "./",
"description": "Analyzes Rails applications and generates upgrade reports...",
"version": "3.1.0"
}
]
}
The source field tells Claude Code where the plugin lives relative to the marketplace root. "./" means the plugin is the repo itself.
Installation Flow
Once published, users install with two commands:
1
2
3
4
5
# Add the marketplace (one-time)
claude plugin marketplace add https://github.com/ombulabs/claude-code_rails-upgrade-skill
# Install the plugin
claude plugin install rails-upgrade
For local development, skip the marketplace and load directly:
1
claude --plugin-dir /path/to/your/plugin/repo
You can validate your plugin structure before publishing:
1
claude plugin validate /path/to/your/plugin/repo
The One-Marketplace-Per-Skill Problem
We have three skills that work together:
rails-upgrade(the main upgrade workflow)dual-boot(dual-boot setup withnext_rails)rails-load-defaults(incrementalload_defaultsupdates)
Each lives in its own repo. That means users would need to add three separate marketplaces:
1
2
3
4
5
6
claude plugin marketplace add https://github.com/ombulabs/claude-code_rails-upgrade-skill
claude plugin marketplace add https://github.com/ombulabs/claude-code_dual-boot-skill
claude plugin marketplace add https://github.com/ombulabs/claude-code_rails-load-defaults-skill
claude plugin install rails-upgrade
claude plugin install dual-boot
claude plugin install rails-load-defaults
That’s six commands for what should feel like one tool. The better approach is a unified marketplace repo that lists all three plugins:
1
2
3
4
claude plugin marketplace add https://github.com/ombulabs/claude-code-rails-skills
claude plugin install rails-upgrade
claude plugin install dual-boot
claude plugin install rails-load-defaults
You can do this with a monorepo (all skills in one repo) or git submodules (separate repos pulled into a marketplace repo). I looked at maquina, which uses the monorepo approach with seven plugins in one marketplace. That’s the pattern to follow.
What I Learned
- Study a working example. The fastest way to understand the plugin spec was examining an installed marketplace (
~/.claude/plugins/marketplaces/maquina/), not reading docs. plugin installonly works with marketplaces. There’s noclaude plugin install <url>. Users must add your marketplace first.- There’s no
.pluginignore. Everything tracked by git gets included. Use.gitignoreto keep dev files out. - Validate early.
claude plugin validatecatches structural issues before you push.