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 one or more
marketplace artifacts with apm pack, and let consumers register
your repo with apm marketplace add. This page covers the
authoring surface: the registry schema and the apm marketplace
verbs.
For the operational concerns that surround a marketplace, see:
- Repo shapes — single-plugin, aggregator, and monorepo-hybrid layouts.
- Versioning strategies — lockstep vs tag-pattern vs per-package.
- Releasing from any CI — the release pipeline that ships your tags.
- Installing from marketplaces — what consumers do with what you publish.
End to end
Section titled “End to end”apm marketplace init # 1. add the block to apm.yml$EDITOR apm.yml # 2. describe each packageapm marketplace check # 3. validate refs resolveapm pack # 4. build marketplace artifactsgit add apm.yml .claude-plugin/marketplace.jsongit commit -m "Release v1.0.0" && git tag v1.0.0 && git push --tagsA consumer in another repo then runs:
apm marketplace add acme-org/my-marketplaceapm install example-package@my-marketplaceThat is the loop. The rest of this page covers the registry schema
and the apm marketplace verbs.
What lives where
Section titled “What lives where”APM uses a single source-of-truth model:
apm.yml— hand-edited. Themarketplace:block declares your registry: owner, packages, version ranges..claude-plugin/marketplace.json— generated byapm packby default. Byte-compatible with Anthropic’s marketplace.json so Claude Code, Copilot CLI, and APM all read the same artefact..agents/plugins/marketplace.json— optional Codex repo marketplace output. Enable it by addingcodextomarketplace.outputs.
Commit every generated file matching your enabled
marketplace.outputs. The legacy standalone marketplace.yml is
deprecated; if you still have one, run apm marketplace migrate.
Author the registry
Section titled “Author the registry”Scaffold the block:
apm marketplace init --owner acme-orgThis appends a richly commented marketplace: block to apm.yml
(creating apm.yml if absent). The minimal shape:
name: my-projectversion: 1.0.0description: Curated plugins for the acme-org engineering team
marketplace: owner: name: acme-org url: https://github.com/acme-org
outputs: # map form (recommended) claude: {} # default; add codex for Codex output
claude: output: .claude-plugin/marketplace.json
codex: output: .agents/plugins/marketplace.json
build: tagPattern: "v{version}"
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
- name: local-tool source: ./packages/local-tool version: 0.1.0 category: Productivity # required when outputs includes codexThe 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.
Add and edit packages without leaving the shell:
apm marketplace package add acme-org/another-pkg --version "^2.0.0"apm marketplace package set example-package --version "^1.2.0"apm marketplace package remove pinned-packageMarketplace output targets use a map-form pattern. The legacy list
form (outputs: [claude, codex]) still parses with a deprecation
warning. When codex is selected, every package must define
category. Codex output maps local entries to source: local,
remote entries to source: url, and remote subdirectory entries to
source: git-subdir.
apm packapm pack resolves every remote packages: entry against
git ls-remote, leaves local-path entries untouched, and writes each
selected marketplace output atomically. Useful flags:
apm pack --dry-run # resolve and print; do not writeapm pack --offline # cached refs onlyapm pack --include-prerelease # allow pre-release tagsapm pack -v # per-entry resolution detailapm pack --marketplace=claude --json # JSON output for CI pipelinesFor the release-gate flags (--check-versions, --check-clean),
see Releasing from any CI.
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.
Validate before you ship
Section titled “Validate before you ship”apm marketplace check # every package's ref/range resolvesapm marketplace doctor # local environment diagnosticsapm marketplace outdated # packages with newer matching tagscheck is the gate to run in CI: a missing tag or unresolvable
range exits non-zero before you push the release commit.
Publish updates to pinned consumers
Section titled “Publish updates to pinned consumers”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.
targets: - repo: acme-org/service-a branch: main - repo: acme-org/service-b branch: develop path_in_repo: apm.yml # defaultapm marketplace publish --dry-run # previewapm marketplace publish --yes # push branches and open PRsapm marketplace publish --no-pr # push branches, skip gh PR creationIt 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.
Pitfalls
Section titled “Pitfalls”-
packages:notplugins:in theapm.ymlsource. Theplugins:name only appears in the compiled JSON. -
Both
apm.yml(marketplace:block) andmarketplace.ymlpresent is a hard error. Pick one; prefer the block and runapm marketplace migrateto consolidate. -
*.jsonin.gitignorewill silently skip generated files.apm marketplace initwarns on this; if you hit it, add an unignore for every enabled output, such as!.claude-plugin/marketplace.jsonand!.agents/plugins/marketplace.json. -
Local-path entries skip git resolution. They emit the path verbatim; consumers see the same path. Use
metadata.pluginRootif 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-runapm packand re-tag to publish a new version. -
Bare cross-repo
repo:on enterprise (*.ghe.com) marketplaces is refused at install time. Dict-form plugin sources (thesource:mapping with nestedtype:andrepo:keys) that point to a different repo than the marketplace project must host-qualify therepo:field. A bareowner/repocannot be disambiguated from a dependency-confusion attempt where an attacker pre-stages the namespace on publicgithub.com, so the install command fail-closes before validating.# Refused -- ambiguous bare form on enterprise marketplace:source:type: gitrepo: owner/repo# Accepted -- enterprise dep on the same host:source:type: gitrepo: corp.ghe.com/owner/repo# Accepted -- declared cross-host dep on public github.com:source:type: gitrepo: github.com/owner/repo
Governance
Section titled “Governance”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).
Where next
Section titled “Where next”- Repo shapes — pick a layout for your producer repo.
- Versioning strategies — how
--check-versionsenforces version alignment. - Releasing from any CI — ship your tagged releases from GitHub Actions, GitLab, Jenkins, or Azure DevOps.
- Installing from marketplaces — the consumer flow your marketplace feeds into.