Central Package Management
|
This section documents MSBuild 18.9, the .NET SDK 10.0.402 (with its This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The C# Reference bibliography lists the reference material consulted while preparing these pages. |
In a repository with many projects, each PackageReference carrying its own Version drifts: two projects end up on
different versions of the same package, and upgrades become a search-and-replace. Central Package Management (CPM)
moves every version into one Directory.Packages.props file; projects name the package and nothing else. CPM has been
production-ready since NuGet 6.4 (.NET 7 SDK). This page sets it up and triggers each of its rules with NuGet 7.9.0
(.NET SDK 10.0.402), using a scratch repository with an App project, the synthetic Ledgerline.* packages from
Dependency Resolution and
Humanizer.Core from nuget.org.
Turning it on
<!-- Directory.Packages.props, at the repository root -->
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Ledgerline.Money" Version="1.5.0" />
<PackageVersion Include="Ledgerline.Fx" Version="1.0.0" />
<PackageVersion Include="Humanizer.Core" Version="2.14.1" />
</ItemGroup>
</Project>
<!-- App/App.csproj -->
<ItemGroup>
<PackageReference Include="Ledgerline.Money" />
<PackageReference Include="Humanizer.Core" />
</ItemGroup>
dotnet new packagesprops writes the skeleton. PackageVersion declares a version; it does not add a reference — a
project only gets the packages it references. Ledgerline's file:
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Ledgerline" Version="1.0.0" />
<PackageVersion Include="Ledgerline.Build" Version="1.1.0" />
<PackageVersion Include="Microsoft.Bcl.HashCode" Version="6.0.0" />
<PackageVersion Include="System.Text.Json" Version="10.0.13" />
<PackageVersion Include="Microsoft.Build.Utilities.Core" Version="18.9.6" />
<PackageVersion Include="Microsoft.Build.Framework" Version="18.9.6" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.11.0" />
<PackageVersion Include="xunit.v3" Version="4.0.2" />
<PackageVersion Include="xunit.runner.visualstudio" Version="4.0.1" />
</ItemGroup>
</Project>
MSBuild imports the nearest Directory.Packages.props above a project, and only that one. A nested file (for a
sample folder with its own versions) must import its parent explicitly:
<Import Project="$([MSBuild]::GetPathOfFileAbove(Directory.Packages.props, $(MSBuildThisFileDirectory)..))" />
Versions can differ per target framework with a Condition on the PackageVersion or its ItemGroup. CPM is turned
off for one project with <ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally> — the scratch
projects on the other NuGet pages do that. The documentation’s samples still target net6.0; nothing about CPM is
framework-specific. The older Microsoft.Build.CentralPackageVersions MSBuild SDK is a different, deprecated
mechanism, not CPM.
The rules, broken one at a time
A version on the reference — NU1008 (Version="1.*" on the PackageReference):
~/cpm/App/App.csproj : error NU1008: The following PackageReference items cannot define a value for Version: Ledgerline.Money. Projects using Central Package Management must define a Version value on a PackageVersion item. For more information, visit https://aka.ms/nuget/cpm/gettingstarted
A reference without a PackageVersion — NU1010, here from Ledgerline before Ledgerline was added to its
props file:
~/ledgerline/samples/Ledgerline.Report/Ledgerline.Report.csproj : error NU1010: The following PackageReference items do not define a corresponding PackageVersion item: Ledgerline. Projects using Central Package Management must declare PackageReference and PackageVersion items with matching names. For more information, visit https://aka.ms/nuget/cpm/gettingstarted
A PackageVersion for a package an SDK references implicitly — NU1009 (NETStandard.Library in a
netstandard2.0 project):
~/cpm/Lib/Lib.csproj : error NU1009: The following PackageReference items are implicitly defined and cannot define a PackageVersion item: NETStandard.Library. Projects using Central Package Management require that implicit package versions be specified by the PackageReference item. For more information, visit https://aka.ms/sdkimplicitrefs
A floating version — NU1011 (<PackageVersion Include="Ledgerline.Money" Version="1.*" />):
~/cpm/App/App.csproj : error NU1011: The following PackageVersion items cannot specify a floating version: Ledgerline.Money. For more information on how to enable this functionality for projects using Central Package Management, visit https://aka.ms/nu1011
With <CentralPackageFloatingVersionsEnabled>true</CentralPackageFloatingVersionsEnabled> the same file restored
Ledgerline.Money/1.5.0. The property is documented only on the NU1011 page.
Overriding a version
One project can deviate with VersionOverride:
<PackageReference Include="Ledgerline.Money" VersionOverride="2.0.0" />
It restored Ledgerline.Money/2.0.0 while the central version was 1.5.0. A repository that wants no exceptions sets
CentralPackageVersionOverrideEnabled to false, and the same reference fails:
~/cpm/App/App.csproj : error NU1013: The following PackageReference items cannot specify a value for VersionOverride: Ledgerline.Money. Projects using Central Package Management are currently configured to disable this functionality. For more information, visit https://aka.ms/nuget/cpm/versionoverride
Source: CPM: Overriding package versions.
Transitive pinning
A central version applies to the packages a project references. By default it does not apply to packages that
only arrive transitively. With Ledgerline.Money at 2.0.0 in Directory.Packages.props and App referencing only
Ledgerline.Rates (which needs Money >= 1.2.0):
Ledgerline.Rates 1.0.0
Ledgerline.Money 2.0.0"] -. version .-> Rates App["App.csproj
PackageReference Ledgerline.Rates"] --> Rates["Ledgerline.Rates 1.0.0"] Rates -- ">= 1.2.0" --> M15["Ledgerline.Money 1.5.0
(default)"] Props -. "pinned with
CentralPackageTransitivePinningEnabled" .-> M20["Ledgerline.Money 2.0.0
(pinned)"] Rates -.-> M20
== transitive, no pinning
~/cpm/App/App.csproj : warning NU1603: Ledgerline.Rates 1.0.0 depends on Ledgerline.Money (>= 1.2.0) but Ledgerline.Money 1.2.0 was not found. Ledgerline.Money 1.5.0 was resolved instead.
Ledgerline.Money/1.5.0, Ledgerline.Rates/1.0.0
== transitive pinning
Ledgerline.Money/2.0.0, Ledgerline.Rates/1.0.0
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled> makes every central version
also a floor for transitive packages — the way to force a patched version of a vulnerable transitive dependency
repository-wide. Pinning below what a package needs is a downgrade, reported as NU1109 (an error), here with Money
pinned at 1.0.0 and Ledgerline.Fx needing >= 2.0.0:
~/cpm/App/App.csproj : error NU1109: Detected package downgrade: Ledgerline.Money from 2.0.0 to centrally defined 1.0.0. Update the centrally managed package version to a higher version.
~/cpm/App/App.csproj : error NU1109: App -> Ledgerline.Fx 1.0.0 -> Ledgerline.Money (>= 2.0.0)
~/cpm/App/App.csproj : error NU1109: App -> Ledgerline.Money (>= 1.0.0)
A pinned transitive package becomes a direct dependency of a package packed from the project. Neither the CPM page nor the
release notes name the NuGet version that introduced CentralPackageTransitivePinningEnabled; the NuGet 6.4 notes
already list fixes to it (#11760, #12020), so it predates CPM’s "production ready" release.
GlobalPackageReference
A GlobalPackageReference in Directory.Packages.props adds a package to every project — meant for build-time
tools such as analyzers and Source Link:
<GlobalPackageReference Include="Humanizer.Core" Version="2.14.1" />
The assets file shows how it is added:
{"include": "Runtime, Build, Native, ContentFiles, Analyzers", "suppressParent": "All", "target": "Package", "version": "[2.14.1, )", "versionCentrallyManaged": true}
Compile is not included — code cannot call the package — and suppressParent: All (PrivateAssets="all")
keeps it out of packed dependencies. Humanizer is a poor global reference for exactly that reason; an analyzer
package is a good one.
Source: CPM: Global package references.
NU1507: CPM with several sources
CPM pins versions but not where a package comes from. With two HTTP sources and no package source mapping, every restore warns:
~/cpm/App/App.csproj : warning NU1507: There are 2 package sources defined in your configuration. When using central package management, map your package sources with package source mapping (https://aka.ms/nuget-package-source-mapping) or specify a single package source. The following sources are defined: nuget.org, bagetter
The repository had a third source, a local folder; NuGet counted only the HTTP ones. Add package source mapping (Package Sources and Source Mapping) rather than suppressing the warning.
Source: CPM: NU1507, NU1507.
Common mistakes
-
Expecting central versions to apply to transitive packages — only with transitive pinning.
-
A nested
Directory.Packages.propswithout importing the parent — the parent’s versions disappear (NU1010). -
VersionOverrideas a habit — the drift CPM was meant to stop; disable it if needed. -
GlobalPackageReferencefor a runtime library —Compileis not included. -
Ignoring NU1507 — versions are pinned, sources are not.