
Marketplace Dev
- 471 installs
- 1.3k repo stars
- Updated August 4, 2026
- daymade/claude-code-skills
marketplace-dev is a Claude Code skill that scaffolds two-sided marketplace backends with listings, search, carts, seller onboarding, commissions, payouts, and order state machines for developers building MVP marketplace
About
marketplace-dev is a Claude Code skill from daymade/claude-code-skills that scaffolds complete two-sided marketplace backend architecture for MVP launches. The skill generates backend structures for product listings, search, shopping carts, seller onboarding flows, commission calculations, payout handling, and order state machines that model multi-party transactions. Developers reach for marketplace-dev when starting a marketplace SaaS or ecommerce API and need domain models, service boundaries, and transactional workflows rather than a simple storefront. The output targets production-minded MVPs where buyers, sellers, and platform fees interact through explicit state transitions and auditable order lifecycles.
- Listing, catalog, and search API design
- Seller onboarding and role models
- Commission, fee, and payout flows
- Order and escrow state machines
- Trust, reviews, and dispute hooks
Marketplace Dev by the numbers
- 471 all-time installs (skills.sh)
- Ranked #866 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/daymade/claude-code-skills --skill marketplace-devAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 471 |
|---|---|
| repo stars | ★ 1.3k |
| Last updated | August 4, 2026 |
| Repository | daymade/claude-code-skills ↗ |
How do you scaffold a two-sided marketplace backend?
Scaffold two-sided marketplace backends with listings, search, carts, seller onboarding, commissions, payouts, and order state machines for MVP launches.
Who is it for?
Backend developers launching a two-sided marketplace MVP who need listings, seller onboarding, commissions, payouts, and order lifecycle logic scaffolded in one pass.
Skip if: Single-vendor ecommerce stores or content sites that only need a basic product catalog without seller accounts, commissions, or multi-party order state machines.
When should I use this skill?
A developer starts a two-sided marketplace MVP and needs backend scaffolding for listings, carts, seller flows, commissions, and order state management.
What you get
Marketplace backend modules for listings, search, carts, seller onboarding, commission rules, payout flows, and order state machines.
- Marketplace API modules
- Order state machine definitions
- Seller onboarding flows
Files
marketplace-dev
Convert a Claude Code skills repository into an official plugin marketplace so users can install skills via claude plugin marketplace add and get auto-updates.
Input: a repo with skills/ directories containing SKILL.md files. Output: .claude-plugin/marketplace.json + validated + installation-tested + PR-ready.
Phase 0: Evidence Intake
Before editing an existing marketplace, collect evidence instead of relying on the default template:
1. Read the current .claude-plugin/marketplace.json. 2. Read this repo's marketplace rules (CLAUDE.md, README install section, changelog). 3. Read official docs for marketplace/plugin path semantics. 4. If refining from prior failures, mine local Claude Code session history.
Each project's sessions live under ~/.claude/projects/<escaped-cwd>/:
- Top-level files:
<session-id>.jsonl - Subagent transcripts:
<session-id>/subagents/agent-*.jsonl
Useful search patterns (adjust keywords to the failure you are debugging):
grep -lc "marketplace.json\|claude plugin validate\|claude plugin install" \
~/.claude/projects/<escaped-cwd>/*.jsonl
grep -lc "Unrecognized key\|Plugin not found\|No manifest found\|Duplicate plugin" \
~/.claude/projects/<escaped-cwd>/*.jsonl \
~/.claude/projects/<escaped-cwd>/*/subagents/*.jsonlExtract lessons as evidence-backed rules: command attempted, observed output, root cause, final working command/config. Do not encode guesses from memory.
Phase 1: Analyze the Target Repo
Step 1: Discover all skills
# Find every SKILL.md
find <repo-path>/skills -name "SKILL.md" -type f 2>/dev/nullFor each skill, extract from SKILL.md frontmatter:
name— the skill identifierdescription— the ORIGINAL text, do NOT rewrite or translate
Step 2: Read the repo metadata
VERSIONfile (if exists) — this becomesmetadata.versionREADME.md— understand the project, author info, categoriesLICENSE— note the license type- Git remotes — identify upstream vs fork (
git remote -v)
Step 3: Determine categories
Group skills by function. Categories are freeform strings. Good patterns:
business-diagnostics,content-creation,thinking-tools,utilitiesdeveloper-tools,productivity,documentation,security
Ask the user to confirm categories if grouping is ambiguous.
Step 4: Choose plugin boundaries
Claude Code has three separate levels:
marketplace -> plugin -> skill- Marketplace name is used for install identity:
plugin@marketplace. - Plugin name is the slash namespace:
/plugin-name:skill-name. - Skill name comes from
SKILL.mdfrontmatter when the skill path points to a
directory containing SKILL.md directly.
Choose each plugin boundary by installation/update/cache intent:
- Single-skill plugin: use when the skill should install, update, and roll back
independently with a narrow cache.
- Suite plugin: use when related skills should share one namespace and one
install command, for example /daymade-docs:mermaid-tools.
For detailed source/cache patterns and pitfalls, read references/cache_and_source_patterns.md before changing source or skills.
Phase 2: Create marketplace.json
The official schema (memorize this)
Read references/marketplace_schema.md for the complete field reference. Key rules that are NOT obvious from the docs:
1. `$schema` field is REJECTED by claude plugin validate. Do not include it. 2. `metadata` only has 3 valid fields: description, version, pluginRoot. Nothing else. metadata.homepage does NOT exist — the validator accepts it silently but it's not in the spec. 3. `metadata.version` is the marketplace catalog version, NOT individual plugin versions. It should match the repo's VERSION file (e.g., "2.3.0"). 4. Plugin entry `version` is independent. For first-time marketplace registration, use "1.0.0". 5. `strict: false` is required when there's no plugin.json in the repo. With strict: false, the marketplace entry IS the entire plugin definition. Having BOTH strict: false AND a plugin.json with components causes a load failure. 6. `source` defines the installed plugin root. For single-skill plugins, point source directly at the skill directory (e.g., "./tunnel-doctor") and omit skills entirely — this is the official pattern used by 167/168 plugins in anthropics/claude-plugins-official. For suite plugins, use source: "./<suite>" with explicit skills array listing subdirectories. Avoid source: "./" (installs full repo as cache) and skills: ["./"] (rejected by Claude Code 2.1.x path-escape validator). 7. Reserved marketplace names that CANNOT be used: claude-code-marketplace, claude-code-plugins, claude-plugins-official, anthropic-marketplace, anthropic-plugins, agent-skills, knowledge-work-plugins, life-sciences. 8. `tags` vs `keywords`: Both are optional. In the current Claude Code source, keywords is defined but never consumed in search. tags only has a UI effect for the value "community-managed" (shows a label). Neither affects discovery. The Discover tab searches only name + description + marketplaceName. Include keywords for future-proofing but don't over-invest.
Generate the marketplace.json
Use this template, filling in from the analysis:
{
"name": "<marketplace-name>",
"owner": {
"name": "<github-org-or-username>"
},
"metadata": {
"description": "<one-line description of the marketplace>",
"version": "<from-VERSION-file-or-1.0.0>"
},
"plugins": [
{
"name": "<skill-name>",
"description": "<EXACT text from SKILL.md frontmatter, do NOT rewrite>",
"source": "./<skill-name>",
"strict": false,
"version": "1.0.0",
"category": "<category>",
"keywords": ["<relevant>", "<keywords>"]
}
]
}Naming the marketplace
The name field is what users type after @ in install commands: claude plugin install dbs@<marketplace-name>
Choose a name that is:
- Short and memorable
- kebab-case (lowercase, hyphens only)
- Related to the project identity, not generic
Description rules
- Use the ORIGINAL description from each SKILL.md frontmatter
- Do NOT translate, embellish, or "improve" descriptions
- If the repo's audience is Chinese, keep descriptions in Chinese
- If bilingual, use the first language in the SKILL.md description field
- The
metadata.descriptionat marketplace level can be a new summary
Maintaining an existing marketplace
When adding a new plugin to an existing marketplace.json:
1. Bump `metadata.version` — this is the marketplace catalog version. Follow semver: new plugin = minor bump, breaking change = major bump. 2. Update `metadata.description` — append the new skill's summary. 3. Set new plugin `version` to `"1.0.0"` — it's new to the marketplace. 4. Bump existing plugin `version` when its SKILL.md content changes. Claude Code uses version to detect updates — same version = skip update. 5. Bump existing plugin `version` when its source or skills changes. The installed cache path and component resolution changed even if SKILL.md did not. 6. Audit `metadata` for invalid fields — metadata.homepage is a common mistake (not in spec, silently ignored). Remove if found.
Phase 3: Validate
Step 1: One-shot pre-flight check
Run the bundled validator. It runs four checks in sequence and exits non-zero on any required failure:
bash scripts/check_marketplace.sh # validates current repo
bash scripts/check_marketplace.sh /path # validates a target repoWhat it checks:
| # | Check | Failure means |
|---|---|---|
| 1 | JSON syntax of .claude-plugin/marketplace.json | file is not parseable JSON |
| 2 | claude plugin validate . (skipped if claude CLI missing) | schema-level rejection (e.g. Unrecognized key: "$schema", duplicate names) |
| 3 | source + skills resolution for every plugin entry | a plugin entry points to a SKILL.md that does not exist on disk |
| 4 | Reverse sync (disk → manifest) | WARN-only: a SKILL.md on disk is not registered in any plugin entry |
Common schema failures and fixes:
Unrecognized key: "$schema"→ remove the$schemafieldDuplicate plugin name→ ensure all names are uniquePath contains ".."→ use./relative paths onlyNo manifest found in directorywhen validating an installed cache path → validate
the marketplace manifest or plugin source, not a strict: false cache directory.
Step 2: Installation test
# Add as local marketplace
claude plugin marketplace add .
# Install a plugin
claude plugin install <plugin-name>@<marketplace-name>
# Verify it appears
claude plugin list | grep <plugin-name>
# Check for updates (should say "already at latest")
claude plugin update <plugin-name>@<marketplace-name>
# Clean up
claude plugin uninstall <plugin-name>@<marketplace-name>
claude plugin marketplace remove <marketplace-name>Step 3: Cache footprint test
After installation or update, inspect the actual cache. This is the only way to confirm source produced the intended snapshot:
PLUGIN=<plugin-name>
MARKET=<marketplace-name>
CACHE=$(jq -r --arg id "$PLUGIN@$MARKET" '.plugins[$id][0].installPath' ~/.claude/plugins/installed_plugins.json)
find "$CACHE" -maxdepth 1 -mindepth 1 -exec basename {} \; | sortExpected results:
- Single-skill plugin cache:
SKILL.mdplus its ownscripts/,references/,
assets/ as applicable.
- Suite plugin cache: only the suite member skill directories and suite-scoped
resources.
- If unrelated skill directories appear,
sourceis too broad. - If cache entries are symlinks, the plugin is not self-contained; use canonical
source directories instead of symlink farms.
Step 4: GitHub installation test (if pushed)
# Test from GitHub (requires the branch to be pushed)
claude plugin marketplace add <github-user>/<repo>
claude plugin install <plugin-name>@<marketplace-name>
# Verify
claude plugin list | grep <plugin-name>
# Clean up
claude plugin uninstall <plugin-name>@<marketplace-name>
claude plugin marketplace remove <marketplace-name>Pre-flight Checklist (MUST pass before proceeding to PR)
Run this checklist after every marketplace.json change. Do not skip items.
Automated checks
bash scripts/check_marketplace.shAll four checks must pass. Treat the reverse-sync WARN as a real signal: an unregistered SKILL.md on disk is almost always either an accidentally-dropped skill you forgot to register, or dead code that should be removed.
Metadata check
Verify these by reading marketplace.json:
- [ ]
metadata.versionbumped from previous version - [ ]
metadata.descriptionmentions all skill categories - [ ] No
metadata.homepage(not in spec, silently ignored) - [ ] No
$schemafield (rejected by validator)
Per-plugin check
For each plugin entry:
- [ ]
descriptionmatches SKILL.md frontmatter EXACTLY (not rewritten) - [ ]
versionis"1.0.0"for new plugins, bumped for changed plugins - [ ]
sourcepoints directly at the skill directory (e.g.,"./skill-name") - [ ] Single-skill plugins omit the
skillsfield (auto-discovery fromsource) - [ ] Suite plugins list
skillspaths relative tosource - [ ]
strictisfalse(no plugin.json in repo) - [ ]
nameis kebab-case, unique across all entries
Final validation
bash scripts/check_marketplace.shMust print RESULT: PASSED before creating a PR. A WARN [4/4] is acceptable only when you have consciously decided to leave a SKILL.md unregistered.
Phase 4: Create PR
Principles
- Pure incremental: do NOT modify any existing files (skills, README, etc.)
- Squash commits: avoid binary bloat in git history from iterative changes
- Only add:
.claude-plugin/marketplace.json, optionallyscripts/, optionally update README
README update (if appropriate)
Add the marketplace install method above existing install instructions:
## Install
 <!-- only if demo exists -->
**Claude Code plugin marketplace (one-click install, auto-update):**
\`\`\`bash
claude plugin marketplace add <owner>/<repo>
claude plugin install <skill>@<marketplace-name>
\`\`\`PR description template
Include:
- What was added (marketplace.json with N skills, M categories)
- Install commands users will use after merge
- Design decisions (pure incremental, original descriptions, etc.)
- Validation evidence (
claude plugin validate .passed) - Test plan (install commands to verify)
Bundled hooks (optional, auto-activated)
This skill ships two PostToolUse hooks under hooks/:
hooks/post_edit_validate.sh— runsclaude plugin validatewhenever a
marketplace.json file is written or edited.
hooks/post_edit_sync_check.sh— warns when aSKILL.mdis edited but the
matching plugin entry in marketplace.json does not bump its version.
Both hooks are declared in this plugin's own manifest entry (plugins[].hooks), so they activate automatically when the plugin is enabled in a Claude Code session. No manual settings.json edit is required. To disable them, remove the hooks block from this plugin entry in the user's installed copy or use /plugin disable marketplace-dev (they take effect only when the plugin is enabled).
These hooks are editor-time guardrails. They do NOT replace scripts/check_marketplace.sh — always run the pre-flight check before a PR.
Anti-Patterns (things that went wrong and how to fix them)
Read references/anti_patterns.md for the full list of pitfalls discovered during real marketplace development. These are NOT theoretical — every one was encountered and debugged in production.
Security scan passed
Scanned at: 2026-04-11T22:50:53.799504
Tool: gitleaks + pattern-based validation
Content hash: 7b7f335893c7905da722e12f0099ed1878164b056eae175995257cff433c62fb
#!/usr/bin/env bash
# PostToolUse hook: warn when SKILL.md is edited but marketplace.json version not bumped
# Detects skill content changes that need a corresponding version bump in marketplace.json
set -euo pipefail
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | python3 -c "
import json, sys
try:
data = json.load(sys.stdin)
print(data.get('tool_input', {}).get('file_path', ''))
except:
print('')
" 2>/dev/null)
# Only care about SKILL.md edits
[[ "$FILE_PATH" != *"SKILL.md"* ]] && exit 0
# Find the skill name from path (e.g., /repo/skills/dbs-hook/SKILL.md → dbs-hook)
SKILL_DIR=$(dirname "$FILE_PATH")
SKILL_NAME=$(basename "$SKILL_DIR")
# Search for marketplace.json upward from the edited file
SEARCH_DIR="$SKILL_DIR"
MARKETPLACE_JSON=""
while [[ "$SEARCH_DIR" != "/" ]]; do
if [[ -f "$SEARCH_DIR/.claude-plugin/marketplace.json" ]]; then
MARKETPLACE_JSON="$SEARCH_DIR/.claude-plugin/marketplace.json"
break
fi
SEARCH_DIR=$(dirname "$SEARCH_DIR")
done
[[ -z "$MARKETPLACE_JSON" ]] && exit 0
# Check if this skill is registered and if version needs bumping
python3 -c "
import json, sys
with open('$MARKETPLACE_JSON') as f:
data = json.load(f)
skill_name = '$SKILL_NAME'
found = False
for p in data.get('plugins', []):
skills = p.get('skills', [])
source = p.get('source', '')
# Match via skills array (suite plugins) or source path (single-skill plugins)
matched = any(s.rstrip('/').split('/')[-1] == skill_name for s in skills)
if not matched and isinstance(source, str):
matched = source.rstrip('/').split('/')[-1] == skill_name
if matched:
found = True
version = p.get('version', 'unknown')
print(json.dumps({
'result': f'SKILL.md for \"{skill_name}\" was modified. Remember to bump its version in marketplace.json (currently {version}). Users on the old version won\\'t receive this update otherwise.'
}))
break
if not found:
print(json.dumps({
'result': f'SKILL.md for \"{skill_name}\" was modified but this skill is NOT registered in marketplace.json. Add it if you want it installable via plugin marketplace.'
}))
" 2>/dev/null
#!/usr/bin/env bash
# PostToolUse hook: auto-validate marketplace.json after Write/Edit
# Checks if the edited file is marketplace.json, runs validation if so.
set -euo pipefail
# Read tool use details from stdin
INPUT=$(cat)
# Check if the edited file is marketplace.json
FILE_PATH=$(echo "$INPUT" | python3 -c "
import json, sys
try:
data = json.load(sys.stdin)
path = data.get('tool_input', {}).get('file_path', '')
print(path)
except:
print('')
" 2>/dev/null)
if [[ "$FILE_PATH" == *"marketplace.json"* ]]; then
MARKETPLACE_DIR=$(dirname "$(dirname "$FILE_PATH")")
if [[ -f "$FILE_PATH" ]]; then
RESULT=$(cd "$MARKETPLACE_DIR" && claude plugin validate . 2>&1) || true
if echo "$RESULT" | grep -q "Validation passed"; then
echo '{"result": "marketplace.json validated ✔"}'
else
ERRORS=$(echo "$RESULT" | grep -v "^$" | head -5)
echo "{\"result\": \"marketplace.json validation FAILED:\\n$ERRORS\"}"
fi
fi
fi
Anti-Patterns: Marketplace Development Pitfalls
Every item below was encountered during real marketplace development. Not theoretical — each cost debugging time.
Contents
- Schema Errors —
$schema,metadata.homepage, conflictingstrict: false+plugin.json - Version Confusion — marketplace vs plugin version, silent update skips,
source/skillschanges - Source and Cache Errors — full-repo cache pollution, namespace surprises, symlink suites, cache-directory validation
- Description Errors — rewriting SKILL.md descriptions, inventing features
- Installation Testing — GitHub push timing, test cleanup
- PR Best Practices — unrelated file diffs, commit squashing
- Discovery Misconceptions — what
keywordsandtagsactually do
Schema Errors
Adding $schema field
- Symptom:
claude plugin validatefails withUnrecognized key: "$schema" - Fix: Do not include
$schema. Unlike many JSON schemas, Claude Code rejects it. - Why it's tempting: Other marketplace examples (like daymade/claude-code-skills) include it,
and it works for JSON Schema-aware editors. But the validator is strict.
Using metadata.homepage
- Symptom: Silently ignored — no error, no effect.
- Fix:
metadataonly supportsdescription,version,pluginRoot. Puthomepage
on individual plugin entries if needed.
- Why it's tricky:
homepageIS valid on plugin entries (from plugin.json schema),
so it looks correct but is wrong at the metadata level.
Conflicting strict: false with plugin.json
- Symptom: Plugin fails to load with "conflicting manifests" error.
- Fix: Choose one authority.
strict: falsemeans marketplace.json is the SOLE
definition. Remove plugin.json or set strict: true and let plugin.json define components.
Version Confusion
Using marketplace version as plugin version
- Symptom: All plugins show the same version (e.g., "2.3.0") even though each was
introduced at different times.
- Fix:
metadata.version= marketplace catalog version (matches repo VERSION file).
Each plugin entry version is independent. First time in marketplace = "1.0.0".
Not bumping version after changes
- Symptom: Users don't receive updates after you push changes.
- Fix: Claude Code uses version to detect updates. Same version = skip.
Bump the plugin version in marketplace.json when you change skill content.
Changing source/skills without bumping plugin version
- Symptom: Marketplace cache updates, but installed users stay on an old cache
layout because claude plugin update sees the same plugin version.
- Fix: Treat
sourceandskillschanges as plugin behavior changes. Bump the
plugin version even if SKILL.md content is unchanged.
Source and Cache Errors
Using full repo source for a narrow plugin
- Symptom: Installing a single plugin creates a cache containing many unrelated
skill directories.
- Fix: Point
sourcedirectly at the skill directory (e.g.,"./my-skill")
and omit the skills field. Do not use skills: ["./"] — it is rejected by Claude Code 2.1.x path-escape validator.
Assuming marketplace name controls slash namespace
- Symptom: Expecting
/daymade-skills:mermaid-toolsafter installing
mermaid-tools@daymade-skills.
- Fix: The plugin name controls the slash namespace. Use a suite plugin like
daymade-docs when you want /daymade-docs:mermaid-tools.
Building suite sources with symlinks
- Symptom: Installed cache contains symlinks pointing back to a marketplace
working copy.
- Fix: Use real canonical suite source directories. Do not use symlink farms for
plugin cache boundaries.
Validating a strict:false cache directory as a plugin manifest
- Symptom:
claude plugin validate ~/.claude/plugins/cache/...reports
No manifest found in directory.
- Fix: Validate the marketplace manifest or source repo. Then validate installed
cache footprint with find, not claude plugin validate on the cache.
Description Errors
Rewriting or translating SKILL.md descriptions
- Symptom: Descriptions don't match the actual skill behavior. English descriptions
for a Chinese-audience repo feel foreign.
- Fix: Copy the EXACT
descriptionfield from each SKILL.md frontmatter.
The author wrote it for their audience — preserve it.
Inventing features in descriptions
- Symptom: Description promises "8,000+ consultations" or "auto-backup and rollback"
when the SKILL.md doesn't mention these specifics.
- Fix: Only state what the SKILL.md frontmatter says. If you want to add context,
use the marketplace-level metadata.description, not individual plugin descriptions.
Installation Testing
Not testing after GitHub push
- Symptom: Local validation passes, but
claude plugin marketplace add <user>/<repo>
fails because it clones the default branch which doesn't have the marketplace.json.
- Fix: Push marketplace.json to the repo's default branch before testing GitHub install.
Feature branches only work if the user specifies the ref.
Forgetting to clean up test installations
- Symptom: Next test run finds stale marketplace/plugins and produces confusing results.
- Fix: Always uninstall plugins and remove marketplace after testing:
claude plugin uninstall <plugin>@<marketplace> 2>/dev/null
claude plugin marketplace remove <marketplace> 2>/dev/nullPR Best Practices
Modifying existing files unnecessarily
- Symptom: PR diff includes unrelated changes (empty lines in .gitignore, whitespace
changes in README), making it harder to review and merge.
- Fix: Only add new files. If modifying README, be surgical — only add the install section.
Verify with git diff upstream/main that no unrelated files are touched.
Not squashing commits
- Symptom: Git history has 15+ commits with iterative demo.gif changes, bloating the
repo by megabytes. Users cloning the marketplace download all this history.
- Fix: Squash all commits into one before creating the PR:
git reset --soft upstream/main
git commit -m "feat: add Claude Code plugin marketplace"Discovery Misconceptions
Over-investing in keywords/tags
- Symptom: Spending time crafting perfect keyword lists.
- Reality: In the current Claude Code source (verified by reading DiscoverPlugins.tsx),
the Discover tab searches ONLY name + description + marketplaceName. keywords is defined in the schema but never consumed. tags only affects UI for the specific value "community-managed". Include keywords for future-proofing but don't obsess over them.
Using tags for search optimization
- Symptom: Adding
tags: ["business", "diagnosis"]expecting search improvements. - Reality: Only
tags: ["community-managed"]has any effect (shows a UI label).
The official Anthropic marketplace (123 plugins) uses tags on only 3 plugins, all with the value ["community-managed"].
Cache and Source Patterns
This reference captures marketplace lessons from real Claude Code marketplace work: full-repo cache pollution, suite namespace design, symlink experiments, and version semantics.
Contents
- Mental Model — the three-level marketplace → plugin → skill hierarchy
- Why Plugin Boundaries Matter — toggle granularity, the baoyu-skills failure case, the false-green trap
- Pattern: Single-Skill Narrow Cache — independent install/update for one skill
- Pattern: Suite Plugin — shared namespace for related skills
- Canonical Source for Suite Members — avoiding duplicate skill directories
- Anti-Patterns — full-repo sources, symlink suites, broad text replacement
- Verification Commands — schema, resolution, and cache footprint checks
Mental Model
Claude Code marketplace distribution has three levels:
marketplace -> plugin -> skill- Marketplace is the catalog and install suffix:
plugin@marketplace. - Plugin is the install/update/cache boundary and slash namespace.
- Skill is the actual
SKILL.mdcapability.
source defines the installed plugin root. skills paths are resolved relative to that root.
Why Plugin Boundaries Matter (Toggle Granularity)
The plugin boundary isn't just a cache/namespace detail — it decides what a user can turn on and off. The smallest unit a user can enable/disable (enabledPlugins) is a plugin, not a skill. Multiple skills bundled in one plugin are all-or-nothing — a user cannot disable just one of them. (Platform behavior: skillOverrides does not apply to plugin-sourced skills, and /skills can't toggle them either. Tracking: anthropics/claude-code#14920, long-open.)
So the single-vs-suite choice below is really a product decision: will users want to toggle these abilities separately? Yes → one plugin per ability. Always used together → a suite is fine, but tell users it's all-or-nothing.
Failure case: baoyu-skills
baoyu-skills (20k+ stars) originally split its skills into 3 plugins (content / ai-generation / utility), all sharing "source": "./". The shared source caused duplicate registration (issue #49 "installing one pulls in unrelated skills"; #79 "slash command list 3x"), forcing PR #106 to merge all 3 into 1 — which bound ~21 skills together, so users can no longer toggle them individually. Two lessons: (1) never share "source": "./" across plugins (see Anti-Patterns); (2) if a repo keeps shared code at its root (baoyu's bun packages/), it can't be split into independent plugins at all — on GitHub install each plugin gets only its own source subtree, so repo-root shared code never reaches any plugin's cache. Keep each plugin self-contained.
Trap: local directory-source installs give a false green
A local directory-source install references the source in place (no copy), so repo-root shared code and cross-subdir references appear to work — but a real GitHub install breaks them. Validate subdirectory isolation / self-containment against a real GitHub install, not just a local directory source.
Pattern: Single-Skill Plugin
Use this when a skill should install and update independently. Point source directly at the skill directory and omit skills (auto-discovery):
{
"name": "mermaid-tools",
"source": "./daymade-docs/mermaid-tools",
"strict": false,
"version": "1.0.2"
}Expected cache:
SKILL.md
references/
scripts/The slash command remains /mermaid-tools:mermaid-tools because the plugin and skill have the same name. This is acceptable when independence matters more than namespace aesthetics.
This is the official pattern used by 167 of 168 plugins in anthropics/claude-plugins-official.
Pattern: Suite Plugin
Use this when related skills should share one namespace:
{
"name": "daymade-docs",
"source": "./daymade-docs",
"strict": false,
"version": "1.0.1",
"skills": [
"./doc-to-markdown",
"./mermaid-tools",
"./pdf-creator",
"./ppt-creator",
"./docs-cleaner",
"./meeting-minutes-taker"
]
}Expected slash commands:
/daymade-docs:doc-to-markdown
/daymade-docs:mermaid-tools
/daymade-docs:pdf-creatorExpected cache top level:
doc-to-markdown/
docs-cleaner/
meeting-minutes-taker/
mermaid-tools/
pdf-creator/
ppt-creator/Canonical Source for Suite Members
If users also need single-skill installs for suite members, point the individual plugin entries at the same canonical subdirectories and omit skills:
{
"name": "pdf-creator",
"source": "./daymade-docs/pdf-creator",
"strict": false,
"version": "1.3.2"
}Avoid keeping duplicate root-level skill directories and suite copies. Duplication creates drift and makes version bumps ambiguous.
Anti-Patterns
Full repo source for a single skill
{
"name": "mermaid-tools",
"source": "./"
}This installs a full repository cache for one plugin. The cache will contain unrelated skills and can confuse debugging. Use source: "./mermaid-tools" instead.
Using skills: ["./"]
{
"name": "pdf-creator",
"source": "./daymade-docs/pdf-creator",
"skills": ["./"]
}Rejected by Claude Code 2.1.x path-escape validator with skills path "./" escapes plugin root. Omit the skills field — auto-discovery finds SKILL.md in the source directory.
Symlink suite directories
Do not build suite sources from symlinks to canonical skill directories. Claude Code preserves the symlink in the cache, and the symlink can point back to the marketplace working copy. That cache is not self-contained or version-immutable.
Text-wide source replacement
Do not patch source fields by broad text replacement. In a real failure, a patch that intended to change only docs plugins also changed unrelated plugins like skill-creator and statusline-generator. Use a structured JSON edit keyed by plugins[].name, then run a source+skills resolution check.
Verification Commands
Schema + resolution + reverse sync in one shot (uses the bundled script):
bash scripts/check_marketplace.sh # validate current repo
bash scripts/check_marketplace.sh /path # validate a target repocheck_marketplace.sh runs four checks:
1. JSON syntax of .claude-plugin/marketplace.json 2. claude plugin validate . (skipped if claude CLI is missing) 3. source+skills resolution (every plugin path resolves to a real SKILL.md) 4. Reverse sync (WARN-only when a disk SKILL.md is not registered)
Inspect the installed cache footprint (cannot be done by the pre-flight script because it depends on what claude plugin install actually produced):
PLUGIN=<plugin-name>
MARKET=<marketplace-name>
CACHE=$(jq -r --arg id "$PLUGIN@$MARKET" \
'.plugins[$id][0].installPath' ~/.claude/plugins/installed_plugins.json)
find "$CACHE" -maxdepth 1 -mindepth 1 -exec basename {} \; | sort
find "$CACHE" -maxdepth 1 -type l -lsA symlink in the cache almost always means the plugin was built from a symlink suite and is not self-contained. Fix by pointing source at a real canonical directory.
marketplace.json Complete Schema Reference
Source: https://code.claude.com/docs/en/plugin-marketplaces + https://code.claude.com/docs/en/plugins-reference Verified against Claude Code source code and claude plugin validate.
Root Level
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Marketplace identifier, kebab-case. Users see this: plugin install X@<name> |
owner | object | Yes | name (required string), email (optional string) |
plugins | array | Yes | Array of plugin entries |
metadata | object | No | See metadata fields below |
metadata fields (ONLY these 3 are valid)
| Field | Type | Description |
|---|---|---|
metadata.description | string | Brief marketplace description |
metadata.version | string | Marketplace catalog version (NOT plugin version) |
metadata.pluginRoot | string | Base directory prepended to relative plugin source paths |
Invalid metadata fields (silently ignored or rejected):
metadata.homepage— does NOT exist in specmetadata.repository— does NOT exist in spec$schema— REJECTED byclaude plugin validate
Plugin Entry Fields
Each entry in the plugins array. Can include any field from the plugin.json manifest schema, plus marketplace-specific fields.
Required
| Field | Type | Description |
|---|---|---|
name | string | Plugin identifier, kebab-case |
source | string or object | Where to fetch the plugin |
Standard metadata (from plugin.json schema)
| Field | Type | Description |
|---|---|---|
description | string | Brief plugin description |
version | string | Plugin version (independent of metadata.version) |
author | object | name (required), email (optional), url (optional) |
homepage | string | Plugin homepage URL |
repository | string | Source code URL |
license | string | SPDX identifier (MIT, Apache-2.0, etc.) |
keywords | array | Tags for discovery (currently unused in search) |
Marketplace-specific
| Field | Type | Description |
|---|---|---|
category | string | Freeform category for organization |
tags | array | Tags for searchability (only community-managed has UI effect) |
strict | boolean | Default: true. Set false when no plugin.json exists |
Component paths (from plugin.json schema)
| Field | Type | Description |
|---|---|---|
commands | string or array | Custom command file/directory paths |
agents | string or array | Custom agent file paths |
skills | string or array | Custom skill directory paths |
hooks | string or object | Hook configuration or path |
mcpServers | string or object | MCP server config or path |
lspServers | string or object | LSP server config or path |
outputStyles | string or array | Output style paths |
userConfig | object | User-configurable values prompted at enable time |
channels | array | Channel declarations for message injection |
Source Types
| Source | Format | Example |
|---|---|---|
| Relative path | string "./<dir>" | "source": "./my-skill" |
| GitHub | object | {"source": "github", "repo": "owner/repo"} |
| Git URL | object | {"source": "url", "url": "https://..."} |
| Git subdirectory | object | {"source": "git-subdir", "url": "...", "path": "..."} |
| npm | object | {"source": "npm", "package": "@scope/pkg"} |
All object sources support optional ref (branch/tag) and sha (40-char commit).
Reserved Marketplace Names
Cannot be used: claude-code-marketplace, claude-code-plugins, claude-plugins-official, anthropic-marketplace, anthropic-plugins, agent-skills, knowledge-work-plugins, life-sciences.
Names impersonating official marketplaces are also blocked.
#!/usr/bin/env python3
"""Drift guard: keep the skill lists in CLAUDE.md / README.md / README.zh-CN.md
in sync with the authoritative source (.claude-plugin/marketplace.json).
The marketplace manifest is the single source of truth for which skills exist
(single-skill plugins + every suite's `skills` array, expanded). The three
human-facing docs each maintain their own numbered skill list, and those lists
drift over time — skills get added to the manifest but not the docs, or a skill
is deleted but its doc entry lingers as a ghost.
This script reports, per document:
- MISSING: skills in the manifest but absent from that doc's list
- GHOST: skills listed in that doc but not in the manifest (deleted/renamed)
Exit code is non-zero when any drift is found, so it can gate CI / pre-push.
Usage:
check_doc_skill_lists.py [repo_root] # defaults to two levels up
"""
import json
import os
import re
import sys
# A few bold tokens in prose match the "**name**" list pattern but are not
# skills. Ignore them so they don't show up as false GHOSTs.
PROSE_TOKENS = {"Metadata", "gitleaks", "Unreleased"}
def manifest_skills(repo):
d = json.load(open(os.path.join(repo, ".claude-plugin", "marketplace.json")))
skills = set()
for p in d["plugins"]:
if p.get("skills"):
for s in p["skills"]:
skills.add(s.strip("./").split("/")[-1])
else:
skills.add(p["source"].strip("./").split("/")[-1])
return skills
def doc_listed(path):
"""Skills referenced in a numbered list line: `### 12. **name**` or `12. **name**`."""
if not os.path.exists(path):
return None
txt = open(path, encoding="utf-8").read()
found = set(re.findall(r"^\s*#*\s*\d+\.\s+\*\*([a-zA-Z0-9_-]+)\*\*", txt, re.M))
return found - PROSE_TOKENS
def main():
repo = sys.argv[1] if len(sys.argv) > 1 else os.path.abspath(
os.path.join(os.path.dirname(__file__), "..", "..", "..")
)
authoritative = manifest_skills(repo)
docs = {
"CLAUDE.md": os.path.join(repo, "CLAUDE.md"),
"README.md": os.path.join(repo, "README.md"),
"README.zh-CN.md": os.path.join(repo, "README.zh-CN.md"),
}
print(f"Authoritative skills in marketplace.json: {len(authoritative)}")
drift = False
for name, path in docs.items():
listed = doc_listed(path)
if listed is None:
print(f"\n{name}: NOT FOUND")
continue
missing = sorted(authoritative - listed)
ghost = sorted(listed - authoritative)
status = "OK" if not (missing or ghost) else "DRIFT"
print(f"\n{name}: {len(listed)} listed — {status}")
if missing:
drift = True
print(" MISSING (in manifest, not in doc):")
for s in missing:
print(f" - {s}")
if ghost:
drift = True
print(" GHOST (in doc, not in manifest):")
for s in ghost:
print(f" - {s}")
# Version-badge coherence: the README version badge must equal the manifest's
# metadata.version. This badge is a derived value that has silently drifted
# twice (1.63->1.64, 1.64->1.65) when a metadata bump forgot to move the badge,
# so the drift guard now asserts it instead of leaving it to manual discipline.
meta_version = json.load(
open(os.path.join(repo, ".claude-plugin", "marketplace.json"))
)["metadata"]["version"]
print(f"\nmarketplace metadata.version: {meta_version}")
for name in ("README.md", "README.zh-CN.md"):
path = os.path.join(repo, name)
if not os.path.exists(path):
continue
m = re.search(r"version-(\d+\.\d+\.\d+)-", open(path, encoding="utf-8").read())
badge = m.group(1) if m else "(none)"
if badge != meta_version:
drift = True
print(f"{name} version badge: {badge} — DRIFT (expected {meta_version})")
else:
print(f"{name} version badge: {badge} — OK")
if drift:
print("\nResult: DRIFT — sync the doc lists with marketplace.json.")
sys.exit(1)
print("\nResult: all doc skill lists are in sync with marketplace.json.")
if __name__ == "__main__":
main()
#!/usr/bin/env bash
# check_marketplace.sh — One-shot validation for a plugin marketplace repo.
#
# Usage:
# bash scripts/check_marketplace.sh # validate current repo
# bash scripts/check_marketplace.sh /path/to/repo
#
# Runs four checks:
# 1. JSON syntax of .claude-plugin/marketplace.json
# 2. `claude plugin validate .` (skipped if claude CLI missing)
# 3. source+skills resolution (every plugin path points to a real SKILL.md)
# 4. reverse sync (warns when a disk SKILL.md is not registered)
#
# Exit codes:
# 0 all required checks passed (check 4 is warn-only)
# 1 at least one required check failed
#
# Zero external dependencies beyond bash + python3 (ships with macOS and Linux).
set -uo pipefail
REPO="${1:-.}"
if [[ ! -d "$REPO" ]]; then
echo "FAIL: repo path '$REPO' is not a directory" >&2
exit 1
fi
REPO="$(cd "$REPO" && pwd)"
MANIFEST="$REPO/.claude-plugin/marketplace.json"
if [[ ! -f "$MANIFEST" ]]; then
echo "FAIL: $MANIFEST not found" >&2
exit 1
fi
echo "Validating marketplace at: $REPO"
echo ""
FAILED=0
# --- Check 1: JSON syntax ------------------------------------------------
if python3 -m json.tool "$MANIFEST" >/dev/null 2>&1; then
echo "PASS [1/4] JSON syntax"
else
echo "FAIL [1/4] JSON syntax: marketplace.json is not valid JSON"
python3 -m json.tool "$MANIFEST" 2>&1 | head -5 | sed 's/^/ /'
FAILED=1
fi
# --- Check 2: claude plugin validate -------------------------------------
if command -v claude >/dev/null 2>&1; then
if output=$(cd "$REPO" && claude plugin validate . 2>&1); then
echo "PASS [2/4] claude plugin validate"
else
echo "FAIL [2/4] claude plugin validate:"
echo "$output" | sed 's/^/ /'
FAILED=1
fi
else
echo "SKIP [2/4] claude plugin validate (claude CLI not installed)"
fi
# --- Check 3: source + skills resolution ---------------------------------
if python3 - "$MANIFEST" "$REPO" <<'PY'
import json, os, sys
manifest_path, repo = sys.argv[1], sys.argv[2]
with open(manifest_path) as f:
data = json.load(f)
errors = []
for plugin in data.get("plugins", []):
name = plugin.get("name", "<unnamed>")
source = plugin.get("source", "")
# Remote sources (github/git/npm) can't be resolved on disk
if isinstance(source, dict):
continue
if not isinstance(source, str) or not source.startswith("./"):
continue
root = source.lstrip("./").rstrip("/") or "."
skills = plugin.get("skills")
if skills is None:
skill_dirs = [""]
else:
skill_dirs = [s.lstrip("./").rstrip("/") or "" for s in skills]
for sd in skill_dirs:
skill_md = os.path.join(repo, root, sd, "SKILL.md")
if not os.path.isfile(skill_md):
errors.append(f" {name}: source={source!r} skill={sd!r} -> missing {os.path.relpath(skill_md, repo)}")
if errors:
print("FAIL [3/4] source+skills resolution:")
for e in errors:
print(e)
sys.exit(1)
print("PASS [3/4] source+skills resolution")
PY
then
:
else
FAILED=1
fi
# --- Check 4: reverse sync (warn-only) -----------------------------------
python3 - "$MANIFEST" "$REPO" <<'PY'
import json, os, sys
manifest_path, repo = sys.argv[1], sys.argv[2]
with open(manifest_path) as f:
data = json.load(f)
registered = set()
for plugin in data.get("plugins", []):
source = plugin.get("source", "")
if isinstance(source, dict) or not isinstance(source, str) or not source.startswith("./"):
continue
root = source.lstrip("./").rstrip("/") or "."
skills = plugin.get("skills") or [""]
for s in skills:
sd = s.lstrip("./").rstrip("/") or ""
full = os.path.normpath(os.path.join(repo, root, sd))
registered.add(full)
ignored = {".git", "node_modules", "dist", "build", ".claude-plugin", ".githooks", "backups"}
disk = set()
for root_dir, dirs, files in os.walk(repo):
# Prune hidden and ignored directories
dirs[:] = [d for d in dirs if d not in ignored and not d.startswith(".")]
if "SKILL.md" in files:
disk.add(os.path.normpath(root_dir))
unregistered = disk - registered
if unregistered:
print("WARN [4/4] disk SKILL.md files not registered in marketplace.json:")
for u in sorted(unregistered):
print(f" {os.path.relpath(u, repo)}")
else:
print("PASS [4/4] reverse sync")
PY
echo ""
if [[ $FAILED -eq 1 ]]; then
echo "RESULT: FAILED"
exit 1
fi
echo "RESULT: PASSED"
Related skills
FAQ
What marketplace domains does marketplace-dev scaffold?
marketplace-dev scaffolds listings, search, carts, seller onboarding, commissions, payouts, and order state machines. The skill targets two-sided marketplace MVPs where buyers and sellers interact through platform-managed transactions.
Is marketplace-dev suited for single-store ecommerce?
marketplace-dev targets two-sided marketplaces with seller accounts, commissions, and multi-party order state machines. Single-vendor stores without seller onboarding or payout splits should use simpler ecommerce backend patterns.