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..github/plugin/marketplace.json– optional Copilot CLI marketplace output. Enable it by addingcopilottomarketplace.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, copilot for Copilot CLI output
claude: output: .claude-plugin/marketplace.json
codex: output: .agents/plugins/marketplace.json
copilot: output: .github/plugin/marketplace.json
# Optional: package sources can be relative to this git base. sourceBase: https://gitlab.corp.example.com/platform/agent-marketplace
build: tagPattern: "v{version}"
packages: - name: example-package description: Human-readable description consumers see source: example-package # -> .../agent-marketplace/example-package version: "^1.0.0"
- name: pinned-package source: acme-org/pinned-package # -> .../agent-marketplace/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. Alongside that rename, apm pack normalises
the top-level name to kebab-case in the compiled output – lowercase
letters, digits, and hyphens only – so the Copilot App accepts it (see the
schema reference). The
raw name is preserved for internal resolution and, for Codex, as
interface.displayName. When a rewrite occurs, apm pack prints a warning
showing both the original and emitted name. Strict schema: unknown keys
raise an error, never
silently ignored.
Use sourceBase when packages live under the same enterprise git base.
The base may target any supported host – GitHub.com, GitHub Enterprise,
self-hosted GitLab, or Azure DevOps. The host is preserved end to end, so a
consumer installs from the same host you authored on. Any relative source
composes onto the base, including two-segment values like
acme-org/pinned-package. Host-prefixed sources like github.com/acme/tool,
full HTTPS URLs (including nested paths such as
https://gitlab.example.com/group/subgroup/package.git), and local ./ paths remain per-entry overrides. If
sourceBase is absent, existing owner/repo source behavior is unchanged.
See the manifest schema
for the full validation and override rules.
apm pack emits source: url for a remote repository and
source: git-subdir when subdir is set.
It also emits the effective source.tag_pattern: a package override takes
precedence over marketplace.build.tagPattern. Repack and publish the generated
metadata after changing either value so consumers receive the new convention.
apm install <package>@<marketplace> accepts both forms, derives the package host from
the generated entry rather than from the marketplace host, and preserves
the generated path and ref.
For annotated or signed tags, source.sha records the peeled checkout
commit, not the tag object; source.ref remains the selected tag. This
correction does not change version selection or verify signatures. To
replace older tag-object pins, upgrade APM, rerun apm pack, and publish
the regenerated marketplace.json. Repacking still resolves configured
version ranges, which may select newer matching tags.
For an Azure DevOps Services marketplace, point sourceBase at the
https://dev.azure.com/{org}/{project}/_git base; relative sources compose
onto it and the dev.azure.com host is kept on the consumer side:
marketplace: sourceBase: https://dev.azure.com/contoso/My%20Projects/_git packages: - name: agent-skills source: agent-skills # -> .../contoso/My%20Projects/_git/agent-skills ref: 3f2a9b1cServices authentication checks ADO_APM_PAT, then the Azure CLI bearer. For
Azure DevOps Server, register the host with ADO_HOST or APM_ADO_HOSTS,
use ADO_APM_PAT, and use the portless root-hosted
https://host/Collection/Project/_git form for sourceBase. Server does not
use the Azure CLI bearer. See
authentication.
Percent-encode ADO path spaces, for example My%20Projects. The generated
URL keeps that encoded spelling, while APM uses the decoded organization,
project, and repository identity for resolution and authentication.
Before:
marketplace: packages: - name: review source: https://gitlab.corp.example.com/platform/agent-marketplace/review ref: v1.0.0 - name: pinned source: https://gitlab.corp.example.com/platform/agent-marketplace/acme-org/pinned-package ref: mainAfter:
marketplace: sourceBase: https://gitlab.corp.example.com/platform/agent-marketplace packages: - name: review source: review ref: v1.0.0 - name: pinned source: acme-org/pinned-package ref: mainAdd 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. Claude output also emits category on any
package where it is set, even though only codex requires it.
The copilot output targets the GitHub Copilot CLI marketplace
format
(.github/plugin/marketplace.json by default). It has no category
requirement. Local plugins[].source values keep their declared
relative paths, such as "./plugins/demo". Remote sources use
{"source":"github","repo":"owner/repo"} or
{"source":"url","url":"https://host/team/repo.git"}, with the
repository subdirectory in path when present. Resolved tag refs
and full 40-character commit pins are retained as ref and sha;
annotated tags use the peeled commit SHA, not the tag-object SHA.
A raw commit pin is emitted only as sha, never as a cloneable ref.
Unrepresentable sources or invalid pins fail explicitly rather than
inventing a local path. Existing branch restrictions remain unchanged.
Marketplace description and version live under metadata.
Select both formats with outputs: {claude: {}, copilot: {}}.
Set outputs.copilot.path to change the Copilot destination;
copilot.output takes precedence over that map path, and
apm pack --marketplace-path copilot=dist/copilot.json overrides both.
Each output format also declares an
APM_MARKETPLACE_<FORMAT>_PATH environment variable name (for
example APM_MARKETPLACE_COPILOT_PATH) reserved for a future release
to override its output path; it is validated at startup but not yet
consumed by apm pack.
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 pipelinesapm pack --strict-metadata # require remote Claude package metadata fetchesStrict remote metadata inheritance currently uses GitHub APIs. For GitLab,
Azure DevOps, or another host, give each remote package a fixed description
and fixed display version (not a range) so --strict-metadata can certify it
without fetching apm.yml. On GitHub, an absent remote apm.yml is also
certifiable when APM verifies a SKILL.md at the same resolved repository,
ref, and package subdirectory.
For 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 a dependencies: mapping, including an empty
mapping (dependencies: {}). Marketplace projects with an omitted or null
dependencies: value 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 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.
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.json,!.agents/plugins/marketplace.json, and!.github/plugin/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 deep-dive 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.