Skip to content

Plugin dependencies

Declare the plugins your plugin relies on, pin them with semver ranges, tag releases so ranges resolve, and understand how conflicts play out for users.

Sooner or later one plugin needs another. Your release-kit calls an MCP tool provided by audit-trail; your team's starter bundle is really just a list of other plugins. Plugin dependencies let you declare that relationship so Claude Code installs, checks and prunes the pieces for you.

This page is for authors writing dependencies in plugin.json, and for maintainers whose plugin others depend on. Users installing a plugin with dependencies should read /docs/plugins/install. npm or Bun packages your own code needs are a different mechanism, covered in /docs/plugins/loading.

Why pin a version

An unconstrained dependency follows its marketplace. Every time users update, they get whatever the dependency author released last. If that release renames the MCP tool you call, your plugin breaks for every user overnight, and you did not change a line.

A constraint such as ~1.4.0 on a git-backed dependency keeps users on 1.4.x patches. When 1.5 comes out, you test against it, widen the range, and ship a new version of your own plugin on your schedule. I pin anything whose tool names or skill names my plugin relies on.

Declaring dependencies

Add a dependencies array to .claude-plugin/plugin.json:

{
  "name": "release-kit",
  "version": "2.0.0",
  "dependencies": [
    "changelog-tools",
    "shared-hooks@platform-plugins",
    { "name": "audit-trail", "version": "~1.4.0" }
  ]
}

Entries come in two shapes.

String. Either a bare plugin name ("changelog-tools"), looked up in the same marketplace as your plugin, or "name@marketplace" to look in another marketplace. A string means "whatever version the marketplace currently offers".

Object. Use this to add a constraint or a different marketplace:

FieldRequiredMeaning
nameYesThe dependency's plugin name as listed in its marketplace
versionNoA semver range: ~1.4.0, ^2.0, >=1.4, =2.1.0 and so on. Installs the highest git tag that satisfies it, so the dependency must have tagged releases
marketplaceNoA different marketplace to resolve name in. Subject to an allowlist (below)

Pre-releases such as 2.0.0-beta.1 do not match a normal range. Opt in with a pre-release suffix, for example ^2.0.0-0.

A bundle plugin for a team

Because a manifest only needs name, a plugin can consist of nothing but dependencies. That makes a neat "starter pack":

{
  "name": "frontend-starter",
  "version": "1.2.0",
  "description": "Everything a new frontend engineer at Acme needs",
  "dependencies": [
    "design-tokens",
    "a11y-review",
    { "name": "storybook-helper", "version": "^4.0" },
    "pr-hygiene"
  ]
}

New starters run one claude plugin install frontend-starter@acme-plugins instead of four. To add a fifth plugin later, publish frontend-starter 1.3.0 with an extra entry. If the marketplace does not auto-update by default, engineers either switch auto-update on for it, or run claude plugin update frontend-starter and then /reload-plugins in any open session to pull in the new dependency.

To push a bundle to the whole organisation, an admin adds it to enabledPlugins in managed settings; see /docs/plugins/org.

Depending across marketplaces

By default Claude Code will not install a dependency from a different marketplace than the plugin declaring it, unless the user already has that dependency installed and enabled at the same scope. This stops one catalogue from quietly pulling software from a source the user never reviewed.

To permit it, the root marketplace (the one hosting the plugin being installed) lists allowed targets in allowCrossMarketplaceDependenciesOn in its marketplace.json. Only the root's allowlist counts.

{
  "name": "acme-plugins",
  "owner": { "name": "Acme Platform Team" },
  "allowCrossMarketplaceDependenciesOn": ["acme-shared"],
  "plugins": [
    {
      "name": "release-kit",
      "source": "./release-kit",
      "dependencies": [{ "name": "audit-trail", "marketplace": "acme-shared" }]
    }
  ]
}

What happens without the allowlist entry depends on where the dependency is declared:

  • In the marketplace entry: the install is refused with a message beginning Dependency "audit-trail@acme-shared" (required by release-kit@acme-plugins) is in marketplace "acme-shared", which is not in the allowlist, naming the field to set.
  • In plugin.json: the install completes without the dependency, and your plugin then fails to load.

If the user installed and enabled audit-trail from acme-shared themselves first, at the same scope, no allowlist is needed.

Developing two plugins together

When you are working on both your plugin and its dependency, load both from disk:

claude --plugin-dir ./audit-trail --plugin-dir ./release-kit

The local copy satisfies the dependency, so nothing is fetched from a marketplace.

  • Version constraints are not checked against local copies, so the dependency's plugin.json does not need a version.
  • From v2.1.242 an entry naming a marketplace (name@marketplace or the marketplace field) also matches the local copy.
  • If both live in one parent folder that is not itself a plugin, --plugin-dir ./plugins once loads every child with .claude-plugin/plugin.json (v2.1.265+).

Until the dependency is properly installed, your plugin depends on that local copy:

  • Local copy disabled: your plugin is disabled at the next load with an error saying the dependency is disabled and telling you to enable it or remove the dependency. If it names the dependency as <name>@inline, that refers to the --plugin-dir copy.
  • Flag forgotten: the error says the dependency is not installed. Add the flag back or install it from the marketplace.

Releasing a plugin others depend on

Constraints resolve against git tags on whichever repository hosts the plugin according to its plugin source:

Plugin sourceWho tagsWhich repo
github, url or git-subdirThe plugin authorThe plugin's own repository
Relative path like ./plugins/audit-trailThe marketplace maintainerThe marketplace repository

Tag format

Tags are <plugin-name>--v<version>, with <version> matching version in that commit's plugin.json. The name prefix lets one repository carry several plugins with separate histories, for example audit-trail--v1.4.2 and release-kit--v2.0.0 side by side.

The easy way is claude plugin tag, run from the plugin folder with an origin remote:

cd plugins/audit-trail
claude plugin tag --dry-run   # show the plan
claude plugin tag --push      # create and push

Before tagging it:

  • validates the plugin,
  • checks plugin.json and the marketplace entry agree on version (when inside a marketplace checkout),
  • requires a clean working tree under the plugin folder,
  • refuses if the tag already exists.

Success prints Created tag audit-trail--v1.4.2, then Pushed to origin with --push; without it you get the git push command to run. Other flags are in /docs/plugins/cli-reference. Plain git tag audit-trail--v1.4.2 also works if you keep both version fields in sync by hand.

Non-git sources

Tag resolution only applies to git-backed sources. For npm, archive and command sources, the range does not choose what is fetched, but it is still checked at load, and the dependent plugin is disabled if the installed version falls outside it. The version checked is the dependency's own plugin.json version, so make sure it sets one: a manifest with no version satisfies no constraint.

Claude Code never installs a command-source dependency itself, and never runs a dependency's headersHelper. Users must install such dependencies before your plugin. Those limits also apply to the other operations that install missing dependencies: /reload-plugins, auto-update of the dependent plugin's marketplace, re-running claude plugin install on the dependent plugin, and claude plugin marketplace add.

What users experience

Resolving against tags

For { "name": "audit-trail", "version": "~1.4.0" }, Claude Code installs the highest audit-trail--v tag in range. If none matches:

  • Plugin in its own repo: install fails with a message containing Dependency "audit-trail@acme-plugins" has no git tag satisfying.
  • Plugin at a relative path: the marketplace's current copy is installed and the range is checked at load. Out of range, the dependent plugin stays disabled and claude plugin list shows something like Requires "audit-trail@acme-plugins" ~1.4.0, installed 2.0.0.

From v2.1.196, a marketplace added as a local folder that is a git repository also resolves relative-path constraints against that folder's tags. A non-git folder has no tags, so its current contents are used.

Checking what resolved

claude plugin list shows a tag-resolved dependency with a 12-character commit suffix, for example 1.4.2-3fa9c0d1b7e2. Constraint checks use the tag's version, even if plugin.json at that commit lags. If a tag is force-moved, the next install fetches the new commit rather than reusing a stale cache; see /docs/plugins/loading.

When several plugins constrain the same dependency

Claude Code picks the highest version satisfying every range.

Plugin APlugin BOutcome
^1.0>=1.3One install at the highest 1.x tag from 1.3.0 up; both load
~1.4~2.0Installing B fails with has conflicting version requirements; A and the dependency are untouched
=1.4.0noneHeld at 1.4.0; auto-update skips newer versions while A is installed

Auto-update fetches a constrained dependency at the highest tag satisfying all installed ranges, not the marketplace's latest. If ranges do not overlap it leaves the dependency alone and the Errors tab names the constraining plugin. If they overlap but no tag fits, it takes the marketplace's current copy and skips the update when that copy is outside any range.

Uninstall the last plugin constraining a dependency and it goes back to tracking its marketplace on the next update. Auto-installed dependencies that nothing needs any more stay until you run claude plugin prune.