HomeNikola Knezevic

In this article

Banner

Central Package Management in .NET

04 Jun 2026
5 min

Sponsor Newsletter

Multi-project .NET solutions tend to grow fast. A Web API here, a few class libraries there, some test projects on top and before you know it the same NuGet packages show up with slightly different versions.

That’s where things start to get messy and you end up with inconsistent builds, runtime issues and maintenance headaches.

Central Package Management (CPM) solves this by introducing a single source of truth for all NuGet package versions in your solution.

Central Package Management

Central Package Management (CPM) is a NuGet feature that moves package version definitions out of individual .csproj files and into a shared solution-level file.

Instead of repeating: Version="10.0.7" on every PackageReference, you define versions once and reference packages by name only in each project.

With CPM:

  • Versions are defined once per solution
  • Projects reference packages without version numbers
  • No version differences between projects
  • Dependency upgrades become predictable and safe

CPM has been stable since .NET 6 and is widely used in modern .NET solutions. For the full overview, see the official docs on Central Package Management.

Getting Started

To enable Central Package Management, add a Directory.Packages.props file at the root of your solution:

xml
<Project>
  <PropertyGroup>
    <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
  </PropertyGroup>
</Project>

This enables MSBuild to treat package versions as centrally managed.

You can also generate it using the .NET CLI:

shell
dotnet new packagesprops

Example

In a real-world solution, your Directory.Packages.props looks like this:

xml
<Project>
  <PropertyGroup>
    <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
    <CentralPackageTransitivePinningEnabled>false</CentralPackageTransitivePinningEnabled>
  </PropertyGroup>
  <ItemGroup>
    <PackageVersion Include="Kommand" Version="1.0.0-alpha.1" />
    <PackageVersion Include="Mapster" Version="10.0.7" />
    <PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.7" />
    <PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="10.0.7" />
    <PackageVersion Include="Microsoft.EntityFrameworkCore" Version="10.0.7" />
    <PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.7" />
    <PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.7" />
    <PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.7" />
    <PackageVersion Include="Microsoft.Extensions.Options" Version="10.0.7" />
    <PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.18.0" />
    <PackageVersion Include="Microsoft.IdentityModel.Tokens" Version="8.18.0" />
    <PackageVersion Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.23.0" />
    <PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.1" />
    <PackageVersion Include="Swashbuckle.AspNetCore" Version="10.1.7" />
  </ItemGroup>
</Project>

Now your .csproj files become significantly cleaner:

xml
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Kommand" />
    <PackageReference Include="Mapster" />
    <PackageReference Include="Microsoft.EntityFrameworkCore" />
  </ItemGroup>
  <ItemGroup>
    <ProjectReference Include="..\Domain\Domain.csproj" />
  </ItemGroup>
</Project>
xml
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <ProjectReference Include="..\Business\Business.csproj" />
  </ItemGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" />
    <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" />
    <PackageReference Include="Microsoft.Extensions.Options" />
    <PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" />
  </ItemGroup>
</Project>
xml
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.EntityFrameworkCore.Design">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
    <PackageReference Include="Microsoft.EntityFrameworkCore.Tools">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
    <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" />
  </ItemGroup>
  <ItemGroup>
    <ProjectReference Include="..\Business\Business.csproj" />
    <ProjectReference Include="..\Domain\Domain.csproj" />
  </ItemGroup>
</Project>

Once CPM is in place, upgrading dependencies becomes trivial, instead of updating multiple .csproj files, you only change a single line and every project picks it up on the next restore.

Additionally, you can control transitive dependencies better to avoid unexpected version conflicts with the CentralPackageTransitivePinningEnabled property:

xml
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>

When enabled, NuGet attempts to align transitive dependencies with centrally defined versions and produce more deterministic and predictable builds.

Centralised Package Converter

Adding CPM to a new project is simple. Migrating an existing solution manually is where the real challenge begins.

Manually you would have to collect every unique package id, pick a version when projects disagree and strip versions from each csproj.

Luckily, Centralised Package Converter automates that. It scans your solution, creates or updates Directory.Packages.props and removes version attributes from project files.

Install it as a global .NET tool:

shell
dotnet tool install CentralisedPackageConverter --global

Then run it against your solution folder or a specific project:

shell
central-pkg-converter C:\path\to\your\solution

Use --dry-run first if you want to preview changes without touching files. The tool also supports reverting back to per-project versions when you need to undo an experiment.

NOTE: If two projects reference the same package with different versions, the converter has to pick one. Resolve those conflicts before or right after migration so you are not surprised at build time.

Conclusion

Central Package Management is a small structural change that pays off heavily in any solution with multiple projects and shared dependencies.

A single Directory.Packages.props file keeps versions consistent, makes upgrades faster and keeps individual csproj files focused on what each project actually uses.

For new projects you can add the file from day one. For existing codebases the Centralised Package Converter gets you most of the way there in one command.

If you want to check out examples I created, you can find the source code here:

Source Code

I hope you enjoyed it, subscribe and get a notification when a new blog is up!

Subscribe

Stay tuned for valuable insights every Thursday morning.