Skip to content

Publish to a marketplace

A marketplace in APM is a curated index of packages that one repo publishes and many repos install from. You author it as a marketplace: block in apm.yml, build it into .claude-plugin/marketplace.json with apm pack, and let consumers register your repo with apm marketplace add. This page walks the producer side: schema, build, and the optional apm marketplace publish flow that opens PRs against pinned consumer repos.

For the consumer side — adding a marketplace, browsing it, installing packages from it — see Marketplaces.

Terminal window
apm marketplace init # 1. add the block to apm.yml
$EDITOR apm.yml # 2. describe each package
apm marketplace check # 3. validate refs resolve
apm pack # 4. compile marketplace.json
git add apm.yml .claude-plugin/marketplace.json
git commit -m "Release v1.0.0" && git push # 5. ship

A consumer in another repo then runs:

Terminal window
apm marketplace add acme-org/my-marketplace
apm install example-package@my-marketplace

That is the loop. Everything below is detail.

APM uses a single source-of-truth model:

  • apm.yml — hand-edited. The marketplace: block declares your registry: owner, packages, version ranges.
  • .claude-plugin/marketplace.json — generated by apm pack. Byte-compatible with Anthropic’s marketplace.json so Claude Code, Copilot CLI, and APM all read the same artefact.

Both files are committed. The legacy standalone marketplace.yml is deprecated; if you still have one, run apm marketplace migrate.

Scaffold the block:

Terminal window
apm marketplace init --owner acme-org

This appends a richly commented marketplace: block to apm.yml (creating apm.yml if absent). The minimal shape:

name: my-project # inherited into marketplace top level
version: 1.0.0
description: Curated plugins for the acme-org engineering team
marketplace:
owner:
name: acme-org
url: https://github.com/acme-org
build:
tagPattern: "v{version}" # default; per-package overridable
packages:
- name: example-package
description: Human-readable description consumers see
source: acme-org/example-package
version: "^1.0.0"
- name: pinned-package
source: acme-org/pinned-package
ref: 3f2a9b1c # explicit SHA/tag/branch
- name: local-tool
source: ./packages/local-tool # ships from this same repo
version: 0.1.0

The key in apm.yml is packages:. It becomes plugins: in the compiled marketplace.json — that rename is the only structural transform apm pack performs. Strict schema: unknown keys raise an error, never silently ignored.

For the full field reference (every key on every entry, including subdir, tag_pattern, include_prerelease, metadata, pluginRoot, and Anthropic pass-through fields like tags, author, license), see Marketplace authoring.

Terminal window
apm pack

apm pack resolves every remote packages: entry against git ls-remote, leaves local-path entries untouched, and writes .claude-plugin/marketplace.json atomically. Useful flags:

Terminal window
apm pack --dry-run # resolve and print; do not write
apm pack --offline # cached refs only
apm pack --include-prerelease # allow pre-release tags
apm pack -v # per-entry resolution detail

Exit codes: 0 build succeeded, 1 build error (network, missing ref, no tag matches range), 2 schema error in the marketplace: block.

The same apm pack run also produces a bundle to ./build/<name>/ when apm.yml declares dependencies:. Marketplace projects with no dependencies: block produce only marketplace.json. See Pack a bundle for the bundle side.

Terminal window
apm marketplace check # every package's ref/range resolves
apm marketplace doctor # local environment diagnostics
apm marketplace outdated # packages with newer matching tags

check is the gate to run in CI: a missing tag or unresolvable range exits non-zero before you push the release commit.

apm marketplace publish is the optional fan-out: it opens PRs against a list of consumer repos that pin the previous marketplace version, bumping each one to the version you just released.

consumer-targets.yml
targets:
- repo: acme-org/service-a
branch: main
- repo: acme-org/service-b
branch: develop
path_in_repo: apm.yml # default
Terminal window
apm marketplace publish --dry-run # preview
apm marketplace publish --yes # push branches and open PRs
apm marketplace publish --no-pr # push branches, skip gh PR creation

It clones each target, edits its apm.yml to point at the new marketplace ref, pushes a feature branch, and opens a PR via gh. State is journaled to .apm/publish-state.json. Failures in one target do not abort the others; the exit code is non-zero if any target failed.

This flow assumes gh is authenticated and the runner has push access to every target — it is targeted at internal/org marketplaces where you control both sides. Public marketplaces should rely on consumers running apm install --update on their own cadence.

  • packages: not plugins: in the apm.yml source. The plugins: name only appears in the compiled JSON.
  • Both apm.yml (marketplace: block) and marketplace.yml present is a hard error. Pick one; prefer the block and run apm marketplace migrate to consolidate.
  • *.json in .gitignore will silently skip the generated file. apm marketplace init warns on this; if you hit it, add an unignore: !.claude-plugin/marketplace.json.
  • Local-path entries skip git resolution. They emit the path verbatim; consumers see the same path. Use metadata.pluginRoot if your plugins live under a common subdirectory.
  • No versions[] array. Each compiled package carries one resolved ref — the highest tag matching the range at build time. Re-run apm pack and re-tag to publish a new version.

Org policy can restrict which marketplaces a consumer is allowed to register and which packages it can install from them. That gate runs on the consumer side at install time. See Governance overview for the producer-side implications (signing, allow-listed sources).