NuGet Central Package Management vs Per‑Project PackageReference: A Decision Guide
Decide whether to adopt NuGet Central Package Management (CPM) or keep per‑project PackageReference. Compare constraints, trade‑offs, and see a step‑by‑step migration example with Directory.Packages.props.
09 Sept 2026, 19:49 UTC

Problem Statement
In a multi‑project .NET solution, keeping each .csproj file in sync on package versions is error‑prone. A single typo can cause subtle runtime failures or build regressions. Two common approaches exist:
- Per‑project
PackageReference– each project declares its ownVersionattribute. - NuGet Central Package Management (CPM) – a single
Directory.Packages.propsfile at the solution root declares all package versions.
This guide helps you decide which strategy fits your constraints and how to migrate to CPM if you choose it.
Decision: Use CPM or Keep Per‑Project References?
Answer the following questions to surface your constraints:
- Do you need a single source of truth for all package versions?
- Is your build pipeline already using the .NET SDK (6.0.300+)?
- Are there legacy
packages.configprojects that must stay? - Do you rely on tooling that only understands per‑project references (e.g., older NuGet.exe)?
- Is version override per project a frequent requirement?
If the answer to (1) is “yes” and (2) is “yes”, CPM is the natural choice. If you have legacy projects or strict tooling constraints, per‑project references may be safer for the short term.
Constraints & Requirements
- CPM requires NuGet 6.2+ (Visual Studio 2022 17.2+ or .NET SDK 6.0.300+). Older build agents must be upgraded.
- CPM does not support legacy
packages.configprojects; they must first migrate toPackageReference. - Non‑SDK‑style projects (pre‑.NET Core 3.1) need
<RestoreProjectStyle>PackageReference</RestoreProjectStyle>explicitly. - Private feeds still rely on
nuget.config; CPM does not alter authentication. - Central overrides are declared with
Override="true"inDirectory.Packages.props; adding aVersionattribute in a project defeats centralization.
Supported Options Comparison
| Feature | Central Package Management (CPM) | Per‑Project PackageReference |
|---|---|---|
| Version Declaration | Single Directory.Packages.props file | Individual PackageReference elements |
| Version Consistency | Enforced across all projects | Possible drift |
| Override Control | Override flag in Directory.Packages.props | Explicit Version in project |
| Tooling Support | Visual Studio 2022+, .NET SDK 6.0.300+ | All NuGet 4.0+ clients |
| Transitive Pinning | Supported via Override="true" | Requires manual PackageReference per project |
| Migration Effort | Collect all unique packages, update Directory.Packages.props, remove versions from projects | No change needed |
| CI Validation | Custom MSBuild target can enforce no Version attributes | Not applicable |
Trade‑Offs
- Consistency vs Flexibility – CPM guarantees a single version per package across the solution but makes per‑project overrides more complex.
- Tooling & Compatibility – CPM is only understood by newer MSBuild and .NET SDK tools; older build agents or scripts that rely on
nuget.exewill fail. - Migration Cost – The initial migration requires careful extraction of all unique version strings and updating each
.csproj. A small oversight can break the build. - Override Granularity – If you need to pin a dependency to a different version in a single project, CPM forces you to add an
Override="true"entry inDirectory.Packages.props, which may be less intuitive than adding aVersionattribute locally. - Legacy Projects – Projects that still use
packages.configcannot benefit from CPM without first migrating toPackageReference.
Concrete Migration Path
- Create
Directory.Packages.propsat the solution root.<Project> <ItemGroup> <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally> </ItemGroup> </Project> - Extract every unique package and its highest version from all
.csprojfiles. Add them to the props file:<ItemGroup> <PackageVersion Include="Newtonsoft.Json" Version="13.0.3" /> <PackageVersion Include="Microsoft.Extensions.Logging" Version="6.0.0" /> </ItemGroup> - In each project, remove the
Versionattribute from everyPackageReferenceelement. The reference should look like:<ItemGroup> <PackageReference Include="Newtonsoft.Json" /> </ItemGroup> - Add a validation target to catch accidental
Versionattributes:<PropertyGroup> <ValidateCentralPackageVersions>true</ValidateCentralPackageVersions> </PropertyGroup> <Target Name="CheckCentralVersions" BeforeTargets="Restore" Condition="$(ValidateCentralPackageVersions) == 'true'"> <Error Text="Project $(MSBuildProjectName) defines PackageReference with Version; use central versioning." Condition="'%(PackageReference.Version)' != ''" /> </Target> - Run a clean build to confirm the migration:
dotnet restore dotnet build --no-restore - Use
dotnet list package --include-transitiveto verify that each package resolves to the central version.dotnet list MyProject.csproj package --include-transitive
Validation Checklist
- All
PackageReferenceelements lack aVersionattribute. - Build succeeds without version conflicts.
- Running
dotnet list package --include-transitiveshows the same version for a package across all projects. - Custom MSBuild target errors if any
PackageReferencestill contains aVersion.
Example Configuration
<Project>
<ItemGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="6.0.0" />
</ItemGroup>
<Target Name="CheckCentralVersions" BeforeTargets="Restore" Condition="'%(PackageReference.Version)' != ''">
<Error Text="PackageReference in project $(MSBuildProjectName) still contains a Version attribute; central versioning is required." />
</Target>
</Project>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" />
<PackageReference Include="Microsoft.Extensions.Logging" />
</ItemGroup>
</Project>
Common Pitfalls & Mitigations
- Multiple
Directory.Packages.propsfiles – If a subfolder contains its own file, the build will use the nearest file up the tree. Keep only one at the solution root unless you intentionally want scoped versioning. - Private feed authentication – Ensure
nuget.configis present on all build agents; CPM does not change how credentials are supplied. - CI agents lacking .NET SDK 6.0.300+ – Update the agent image or install the SDK before running
dotnet restore. - Override usage – To force a lower version down the dependency chain, add
Override="true"inDirectory.Packages.propsinstead of editing a project file.
Summary
Central Package Management offers a single source of truth for package versions, reducing drift and simplifying upgrades. It requires newer tooling and a one‑time migration effort, but the payoff is a more maintainable solution. If your environment cannot upgrade to the required SDK or you have legacy packages.config projects, stick with per‑project PackageReference until you can address those constraints. Use the migration steps above to validate a clean transition and enforce central versioning in your CI pipeline.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.