Central Package Management in .NET
In a solution with dozens of projects, keeping NuGet package versions consistent is a real headache. One project uses Serilog 4.0.0, another uses 3.1.1, and a third uses 4.1.0. Central Package Management (CPM) solves this by declaring all package versions in a single file.
The Problem
In a typical solution, each .csproj specifies its own package versions:
<!-- Project A -->
<PackageReference Include="Serilog" Version="4.1.0" />
<!-- Project B -->
<PackageReference Include="Serilog" Version="4.0.0" />
Over time, versions drift. Developers update packages in the project they're working on but not in others. You end up with inconsistent behaviour and subtle bugs from version mismatches.
Setting Up CPM
Create a Directory.Packages.props file in your repository root:
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="9.0.1" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.1" />
<PackageVersion Include="Serilog" Version="4.2.0" />
<PackageVersion Include="Serilog.Sinks.Console" Version="6.0.0" />
<PackageVersion Include="FluentValidation" Version="11.11.0" />
<PackageVersion Include="xunit" Version="2.9.3" />
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.2" />
<PackageVersion Include="Moq" Version="4.20.72" />
<PackageVersion Include="coverlet.collector" Version="6.0.3" />
</ItemGroup>
</Project>
Now update your .csproj files to remove the Version attribute:
<!-- Before -->
<PackageReference Include="Serilog" Version="4.2.0" />
<!-- After -->
<PackageReference Include="Serilog" />
The version is resolved from Directory.Packages.props. If a project references a package that isn't listed in the central file, the build fails — which is exactly what you want.
Organising Package Versions
For larger solutions, group your package versions with comments:
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup Label="Entity Framework">
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="9.0.1" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.1" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.1" />
</ItemGroup>
<ItemGroup Label="Logging">
<PackageVersion Include="Serilog" Version="4.2.0" />
<PackageVersion Include="Serilog.AspNetCore" Version="9.0.0" />
<PackageVersion Include="Serilog.Sinks.Seq" Version="9.0.0" />
</ItemGroup>
<ItemGroup Label="Testing">
<PackageVersion Include="xunit" Version="2.9.3" />
<PackageVersion Include="FluentAssertions" Version="7.0.0" />
<PackageVersion Include="Testcontainers" Version="4.3.0" />
</ItemGroup>
</Project>
Overriding Versions
Sometimes a specific project genuinely needs a different version. You can override with VersionOverride:
<PackageReference Include="Newtonsoft.Json" VersionOverride="13.0.1" />
Use this sparingly. If you find yourself overriding frequently, it may indicate the central version needs updating.
Transitive Pinning
By default, CPM only manages direct references. Enable transitive pinning to also control transitive dependency versions:
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
</PropertyGroup>
With transitive pinning, if you declare System.Text.Json version 9.0.1 in your central file, all transitive references to System.Text.Json are also resolved to 9.0.1. This is particularly useful for security patches — you can force an updated version of a transitive dependency without waiting for every direct dependency to update.
Migrating an Existing Solution
For an existing solution, the migration is mechanical:
- Create
Directory.Packages.propswithManagePackageVersionsCentrallyset totrue. - For each unique package across all projects, add a
PackageVersionentry. - Remove
Versionattributes from allPackageReferenceitems in.csprojfiles. - Build and fix any conflicts.
The .NET CLI can help. Run dotnet list package to get a summary of all packages and versions in your solution, then use that as the basis for your central file.
CPM and Dependabot
Dependabot and Renovate both support Directory.Packages.props. When a new package version is available, they update the central file rather than individual project files, resulting in cleaner, single-file pull requests.
Summary
Central Package Management is a simple concept with a meaningful impact. One file controls all package versions across your entire solution. No more version drift, no more "which version of EF Core are we on?" questions. If your solution has more than two or three projects, CPM is worth the ten minutes it takes to set up.