Start free
Andrew Hanna

Andrew Hanna

Cross-Package Dependency Resolution in Salesforce: Modelling, Install Order and Safe Upgrades

Cross-Package Dependency Resolution in Salesforce: Modelling, Install Order and Safe Upgrades

Short answer: cross-package dependency resolution is how Salesforce decides which packages must already be present, and at which versions, before another package can install or upgrade. You declare it in the dependencies array of sfdx-project.json, listed in installation order. Get the order and version floors right and subscriber upgrades are boring; get them wrong and an org lands on a version it cannot move off.

What is a package dependency, exactly?

A dependency exists when metadata in one package references metadata in another: a Flow on a managed object, an Apex class extending a base class, a field set on someone else's field. The practical consequence is a precondition: the referenced package must be installed first, at a version at least as new as the one you built against, or the install fails.

How do you declare a dependency between packages?

In the package directory entry of sfdx-project.json, in either of two forms:

  • An alias that already carries the version: { "package": "[email protected]" }
  • Package and version separately: { "package": "MyPackage", "versionNumber": "1.0.0.RELEASED" }

Three rules matter more than the syntax.

  • Order is installation order. When a package has several dependencies, the list is read as the sequence in which they must be installed.
  • Circular dependencies are not supported. A depending on B while B depends on A is not a puzzle to solve, it is a design to break up.
  • Multi-level chains are supported. C can depend on B, and B on A. Set "calculateTransitiveDependencies": true and you declare only direct dependencies while the indirect ones are computed for you. Leave it off and you list every level yourself, forever.

Which package types can depend on which?

This constrains the architecture, so check it before you draw it.

  • Managed 2GP can depend on managed 2GP and on unlocked packages.
  • Managed 2GP cannot depend on managed 1GP. Salesforce blocks installing managed 2GP packages in managed 1GP packaging orgs. Where a product genuinely requires it, the route is a case with Salesforce Partner Support for an individual override, not a trick in a build script.
  • Managed 1GP can depend on managed 1GP, managed 2GP and unlocked packages.
  • Unlocked packages depending on managed packages are not recommended, and nothing supported depends on an unmanaged package.

A 1GP base package under a 2GP extension is the most common dependency design that has to be undone late, and it is expensive by then. Read the supported combinations before the first build, not after.

How do you model packages so the graph stays shallow?

  1. One direction only. Base, then feature, then edge. If two packages want to reference each other, the shared metadata belongs in a third package underneath both.
  2. Small and modular beats one large package. Package creation and installation are both faster, and you are less likely to hit limits.
  3. Depth is the expensive dimension, not breadth. Every level you add multiplies release coordination: a change at the bottom needs a new version at every level above it before a subscriber sees any of it.
  4. Pin deliberately. A pinned build number is reproducible and boring. A keyword such as RELEASED follows your latest released version, which is convenient in development and a surprise in a release build.

What is the correct install order in a subscriber org?

  1. Install or upgrade the lowest package in the graph first, the one with no dependencies of its own.
  2. Work upward one level at a time, so every package meets its precondition when it installs.
  3. Install the leaf package, the one your customer actually bought, last.
  4. Verify rather than assume. A SOQL query on SubscriberPackageVersion tells you what is installed in that org and at which version, which beats reading the Installed Packages page and hoping.

If you cannot state that order from memory, neither can your support team when a customer is blocked.

How do you change a dependency without stranding subscribers?

  • The new version has to be a direct descendant of the installed one. Subscribers cannot skip lineage, so a package whose ancestry was broken is a package some org cannot upgrade.
  • Beta packages are not upgradeable. A subscriber holding a beta must uninstall before installing a released version, which in production is a data-loss conversation. Never leave a beta in a customer org.
  • Removed metadata is deprecated or deleted on upgrade. Moving a component from one package to another is a removal in one and an addition in the other, so the release order across the two is the entire risk.
  • Raise version floors one step at a time. Jumping the required version of a base package by several releases forces a chain of upgrades on subscribers who were not planning one.

The sequence that works when a base package changes: release the base version, move subscribers onto it with a recommended version or a push upgrade, then release the dependent package that requires it. Reversing those two produces an upgrade that fails a precondition in an org you cannot see and cannot fix from your side.

What to automate

  1. Build the graph from sfdx-project.json on every pipeline run and fail the build on a cycle.
  2. Install the full chain into a clean scratch org in declared order every time, so ordering bugs surface at build time, not in a customer org.
  3. Record installed versions per subscriber org, so support can answer the question before asking the customer.

Serpent does cross-package dependency resolution, 1GP, 2GP and managed-package workflows and version management across subscriber orgs on every plan, including the free one. More playbooks live in our SF Guides library.

FAQ

Can a managed 2GP package depend on a managed 1GP package?

Not by default. Salesforce blocks the combination, and an individual override is requested through a case with Salesforce Partner Support.

Do I have to list transitive dependencies?

Only if you want to. Setting "calculateTransitiveDependencies": true lets you declare direct dependencies while the indirect ones are calculated. Without it, every level has to be listed by hand.

What happens if two packages depend on each other?

Nothing good, because circular dependencies are not supported. Extract the shared metadata into a lower package that both depend on.

Which package do I upgrade first in a subscriber org?

The one furthest down the graph. Dependencies are upgraded before the packages that require them, in the same order they were installed.

Why does my customer's upgrade fail with a dependency error?

Usually a version floor. The installed base package is older than the version your new release requires, so upgrade the base package first and retry.

Related Articles

Curious about faster shipping before you dive in? Let's talk

Commitment free!