Automated Package Versioning
This feature is in limited preview — it is available to selected customers only, and behaviour and commands may change based on feedback. Reach out to the flxbl team if you would like early access.
This feature is available in sfp-pro and requires an sfp server, as the server keeps track of the last published version of each package per branch.
Version numbers in sfdx-project.json are a constant source of friction in team development. Every pull request that touches a package potentially fights over the versionNumber field, developers forget to bump versions (or bump them incorrectly), and there is no structured way to express whether a change is a patch, a minor or a major.
Automated package versioning takes the version number out of the day-to-day workflow. Packages you opt in are marked with the sentinel version 0.0.0.NEXT in sfdx-project.json, and the version of every artifact is computed at build time from:
- The last published version of the package, tracked by the sfp server per branch
- The intent of the changes since then — derived from your conventional commit messages, explicit
version:commit footers, or/versioncomments on pull requests
// sfdx-project.json — a server-managed package
{
"path": "src/sales",
"package": "sales",
"versionNumber": "0.0.0.NEXT"
}With this in place, merging a pull request titled feat(sales): add discount calculation and building produces the next minor version of sales automatically — if the last published version was 2.4.1, the new artifact is 2.5.0.<build>. The version field stays constant in source control, so pull requests no longer conflict on it.
How a version is computed
For every package that is part of a build, the version pipeline runs the following stages:
- User override — if the package's
versionNumberis anything other than0.0.0.NEXT, that version is used verbatim (with the build number substituted into the fourth segment) and all other stages are skipped. This is what keeps packages you have not opted in behaving exactly as before. - Explicit intent — a
/versioncomment on a merged pull request, or aversion:footer in a commit message, declares the bump explicitly. Explicit intent always wins over inference. - Smart defaults — the conventional commit prefixes of the commits since the last published version decide the bump:
feat:is a minor,fix:(and everything else) is a patch, andfeat!:or aBREAKING CHANGE:footer is a major. Across multiple commits, the highest intent wins. - Dependents expansion — when a change declares
dependents:<intent>, the direct dependents of the changed packages are also given a version bump and added to the build. - Promoted version guard — for unlocked packages, if the current version series has already been promoted, a patch is automatically escalated to a minor, since Salesforce does not allow patching a promoted series.
- Compute — the intent is applied on top of the last published version.
A few examples, assuming sales was last published as 2.4.1:
| Changes since last publish | Computed version |
|---|---|
fix(sales): correct rounding on totals | 2.4.2.<build> |
feat(sales): add discount calculation | 2.5.0.<build> |
feat(sales)!: rework pricing API | 3.0.0.<build> |
fix(sales): small fix and feat(sales): new feature | 2.5.0.<build> (highest wins) |
chore: update labels (no recognised prefix) | 2.4.2.<build> |
Merged PR carrying the comment /version sales:major | 3.0.0.<build> |
The computed versions are displayed during every build:
Computed Versions:
┌──────────────┬───────────┬─────────────┬────────┬─────────────────────┐
│ Package │ Previous │ New Version │ Intent │ Source │
├──────────────┼───────────┼─────────────┼────────┼─────────────────────┤
│ sales │ 2.4.1.45 │ 2.5.0.46 │ minor │ conventional-commit │
├──────────────┼───────────┼─────────────┼────────┼─────────────────────┤
│ sales-ui │ 1.2.0.45 │ 1.2.1.46 │ patch │ default │
└──────────────┴───────────┴─────────────┴────────┴─────────────────────┘After a successful publish, the new version is recorded on the server and becomes the base for the next build's computation.
What does not change
- Packages you have not opted in are untouched. Any package with an explicit version (
2.4.1.NEXT) keeps its classic behaviour — its version is used as-is, and a manual version bump still triggers a rebuild. You can run a project with a mix of managed and manual packages indefinitely. - Version computation never fails a build. Invalid intents are skipped with a warning, and if the server is temporarily unreachable, the build falls back to the versions recorded in your git tags. A
0.0.0.LATESTdependency is the exception, see Depending on the latest published version.
How dependencies are treated
- A bump never propagates on its own. Changing
core-crmdoes not bump the packages that depend on it. Dependent packages are only bumped when a change explicitly asks for it withdependents:<intent>(in a commit footer or a/versioncomment). - Expansion is single-level.
dependents:patchoncore-crmbumps the packages that directly depend oncore-crm— not their dependents in turn. A package reachable through several dependency paths is bumped once, from its own last published version. - Bumped dependents are built. A dependent that receives a version bump is added to the build so an artifact exists for the new version, even though its own source did not change.
- Dependency version references are not rewritten. Entries in a package's
dependenciesarray stay as written insfdx-project.json. They are resolved at build time, and the exact version used is recorded in the dependent's artifact.
Dependency version references
| Reference | Dependency built in the same run | Dependency not in the run |
|---|---|---|
X.Y.Z.LATEST | The version just created | The newest validated version on the X.Y.Z line that is tagged on the branch |
0.0.0.LATEST | The version just created | The version last published on the branch, as recorded on the server |
X.Y.Z.LATEST selects a major.minor.patch line. It is not a minimum version. When a server-managed dependency moves to a new patch or minor line, a reference such as 1.4.0.LATEST keeps resolving the 1.4.0 line until you change it.
Depending on the latest published version
For a dependency on a server-managed package of the same project, use 0.0.0.LATEST. The dependent then builds against the version of the dependency last published on the branch, so the reference does not need to follow every change.
// sfdx-project.json
{
"path": "src/core",
"package": "core",
"versionNumber": "0.0.0.NEXT"
},
{
"path": "src/sales",
"package": "sales",
"versionNumber": "0.0.0.NEXT",
"dependencies": [
{ "package": "core", "versionNumber": "0.0.0.LATEST" }
]
}- Where it is accepted. The dependency must be a package directory of this project and must itself be server-managed (
0.0.0.NEXT). A0.0.0.LATESTreference to any other package fails the build with an error naming the dependent and the dependency. - Exact version. The build looks up the exact version last published on the branch in the DevHub. If that version is not found as a validated, non-deprecated version (for example, it was created with skip-validation or later deprecated), the build fails and names the version. It does not fall back to an older version.
- Nothing published on the branch yet. When the dependency has no published version on the branch, for example on a new feature branch, the build uses the newest validated version whose git tag is reachable from the branch and logs a warning. If there is none, the build fails.
- Server unavailable. If the published versions cannot be read from the sfp server, a build with a
0.0.0.LATESTdependency fails instead of falling back. - Transitive dependencies. When transitive dependencies are resolved,
0.0.0.LATESTtakes precedence over any other reference to the same package, sosales → core → commonresolvescommonthe same waycoredoes. - Quick builds.
sfp quickbuildresolves0.0.0.LATESTto the version just built or the latest published version, without requiring it to be validated.
Every dependency other than a branch dependency needs a versionNumber. A dependency without one fails the build with an error naming the dependent and the dependency.
Where to go next
- Adopting automated versioning — enabling the feature and onboarding your packages
- Expressing version intent — conventional commits,
version:footers and/versionPR comments, with examples - Versioning across branches — how parallel branches, merges and release lines behave