Central Package Management

This section documents MSBuild 18.9, the .NET SDK 10.0.402 (with its dotnet CLI) and NuGet 7.9, as published in the MSBuild, .NET and NuGet documentation on learn.microsoft.com, which is the reference these pages are written and verified against. Material about .NET 11 / NuGet 8.0 is still in preview and is always flagged as such — never presented as baseline.

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.

Source: NU1008, NU1009, NU1010, NU1011.

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

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):

flowchart LR Props["Directory.Packages.props
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.

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.props without importing the parent — the parent’s versions disappear (NU1010).

  • VersionOverride as a habit — the drift CPM was meant to stop; disable it if needed.

  • GlobalPackageReference for a runtime library — Compile is not included.

  • Ignoring NU1507 — versions are pinned, sources are not.