Learn Minimal APIs from start to finish. Perfect for beginners.

Stop updating NuGet packages on every project

David Grace David Grace

Got multiple projects in one solution? Updating a NuGet package across all of them is a pain. Central Package Management fixes that with a single file.

The problem with managing NuGet package versions across projects

When you open a .csproj file in a multi-project solution, you will see all your package references, each with a version number directly on them. The moment you have shared packages, like Entity Framework Core referenced in more than one project, you have a versioning problem.

Every time a new version comes out, you must remember to update it in every project. Miss one and you can end up with version conflicts across your solution that are frustrating to track down.

Introducing Central Package Management

Central Package Management is a NuGet feature that lets you manage your package versions from a single file: Directory.Packages.props. Your individual .csproj files still declare which packages they use, but the version lives in the central file instead.

Creating Directory.Packages.props

The quickest way to create the file is with the .NET CLI. Run this command:

dotnet new packagesprops

This generates a Directory.Packages.props file with ManagePackageVersionsCentrally already set to true:

<Project>
	<PropertyGroup>
		<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
	</PropertyGroup>
	<ItemGroup>
	</ItemGroup>
</Project>

Once the file exists, add a <PackageVersion /> entry for each package your solution uses:

<Project>
	<PropertyGroup>
		<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
	</PropertyGroup>
	<ItemGroup>
		<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="10.0.3" />
		<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.3" />
		<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.3" />
	</ItemGroup>
</Project>

The CLI command only scaffolds the skeleton. It does not pull in your existing packages automatically. You still must add the <PackageVersion /> entries yourself, which is the tedious part for an existing solution.

Updating the project files

With Directory.Packages.props in place, go through each .csproj file and remove the Version attribute from every <PackageReference>. The version will be resolved from Directory.Packages.props instead.

If you leave a Version attribute on a <PackageReference> after enabling Central Package Management, you will get a build error. That is intentional. It enforces the central file as the single source of truth.

<Project Sdk="Microsoft.NET.Sdk">
	<PropertyGroup>
		<TargetFramework>net10.0</TargetFramework>
	</PropertyGroup>
	<ItemGroup>
		<PackageReference Include="Microsoft.EntityFrameworkCore" />
		<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" />
		<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" />
	</ItemGroup>
</Project>

Migrating all packages with CentralisedPackageConverter

Manually moving every package version across a large solution is slow and error prone. There is an open-source tool on GitHub that does the entire migration for you.

The first step is to install it as a .NET global tool:

dotnet tool install CentralisedPackageConverter --global

Then point it at your solution folder:

central-pkg-converter /path/to/your/solution

The tool processes every project file in the solution, builds Directory.Packages.props with all your package versions, and strips the Version attributes from every .csproj. Where it finds version conflicts across projects, it picks the highest version. Always review the generated file before committing.

Updating packages after migration

Updating packages works the same way as before. Go to Tools > NuGet Package Manager > Manage NuGet Packages for Solution, click the Updates tab, and check for newer versions. The difference is that when an update is applied, NuGet writes the new version to Directory.Packages.props rather than to each individual .csproj file.

Overriding a version for one project

Most of the time you want every project on the same version. That's the whole point. But occasionally one project genuinely needs a different version. You can handle that with VersionOverride on the <PackageReference> in that project's .csproj file:

<PackageReference Include="Microsoft.EntityFrameworkCore" VersionOverride="10.0.4" />

That overrides the centrally defined version for just that one project, without affecting anything else. If you find yourself using it frequently, the central file likely needs a rethink.

Opting a project out of Central Package Management

If a specific project cannot participate in Central Package Management, you can opt it out completely by adding this to that project's .csproj file:

<PropertyGroup> 
	<ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally>
</PropertyGroup>

That project will then manage its own package versions as normal, independently of Directory.Packages.props. Bear in mind that you will need to add version numbers back to the <PackageReference> entries in that project, otherwise you will get build errors.

This is useful during a gradual migration where you cannot update every project at once.

Global Package References

Rather than adding a package to every .csproj file individually, you can use a GlobalPackageReference in Directory.Packages.props to make it available across every project in the solution automatically.

This is specifically intended for build-time tooling. To use it, change the entry in Directory.Packages.props from <PackageVersion> to <GlobalPackageReference>:

<Project>
	<ItemGroup>
		<GlobalPackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.3" />
	</ItemGroup>
</Project>

You can then remove the corresponding <PackageReference> entry from each .csproj file. NuGet treats GlobalPackageReference as a dev dependency, so it will not leak into the package's own dependency list when you publish. This makes it particularly useful for build-time tooling, like analysers, versioning packages, and anything else that every project needs but should not show up as a runtime dependency.

Using conditions in Directory.Packages.props

You can apply conditions to <PackageVersion> entries to control which version is used based on the build configuration or the target framework of a project.

Condition based on build configuration

You can restrict a package to Debug builds only using the Condition attribute. This is useful for packages like Microsoft.VisualStudio.Web.CodeGeneration.Design, which you would only want included when debugging:

<Project>
	<ItemGroup>
		<PackageVersion Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="10.0.2" Condition="'$(Configuration)' == 'Debug'" />
	</ItemGroup>
</Project>

Condition based on target framework

You can also set the condition based on the target framework. EF Core 10 requires .NET 10, so if you have projects on both .NET 9 and .NET 10, you can serve the appropriate version to each:

<Project>
	<ItemGroup>
		<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="9.0.3" Condition="'$(TargetFramework)' == 'net9.0'" />
		<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="10.0.3" Condition="'$(TargetFramework)' == 'net10.0'" />
	</ItemGroup>
</Project>

The TargetFramework value in the condition matches what is set in each project's .csproj. A project targeting net10.0 gets EF Core 10. One targeting net9.0 gets EF Core 9. Make sure you have a <PackageVersion> entry covering every target framework in use across your solution, otherwise a project with no matching condition will get no version and will throw a build error.

Watch the video

Watch the video where we demonstrate how to set up Central Package Management, migrate all your existing packages, and cover features like version overrides, global package references, and conditions.