Monorepos
Share dependencies across many projects in one repository, with one lockfile and one store.
If you have a .NET monorepo (one repo, many .csproj files, one
solution or many), PCPM treats it as one workspace: one pcpm.lock,
one store, and one Directory.Packages.props for the whole tree.
This page covers the practical side: how to lay out your repo so PCPM
sees it, and what to expect from pcpm install at the monorepo scale.
Recommended layout
PCPM doesn’t care about layout, but the following is a convention that works well:
monorepo/
├── pcpm-workspace.yaml # project globs
├── Directory.Packages.props # central version manifest
├── pcpm.json # workspace config
├── pcpm.lock # generated by pcpm install
├── apps/
│ ├── web/ # ASP.NET project
│ │ └── web.csproj
│ └── worker/ # background worker
│ └── worker.csproj
├── libs/
│ ├── shared/ # internal library
│ │ └── shared.csproj
│ └── contracts/
│ └── contracts.csproj
└── tools/
└── migrations/
└── migrations.csproj
The matching pcpm-workspace.yaml:
packages:
- "apps/*"
- "libs/*"
- "tools/*"
Every .csproj under apps/, libs/, and tools/ is part of the
workspace. The lockfile covers them all.
Adding a dependency
By default, pcpm add writes the <PackageReference /> to every
project in the workspace. If you only want one:
pcpm add Serilog --project apps/web/web.csproj
If you want a subset:
pcpm add Microsoft.Extensions.Hosting \
--project apps/web/web.csproj \
--project apps/worker/worker.csproj
Versioning
Central Package Management gives you one place to pin versions:
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Serilog" Version="3.1.1" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
</ItemGroup>
</Project>
PCPM writes these <PackageVersion /> entries for you. The
<PackageReference /> in each .csproj references the same id
without a version:
<ItemGroup>
<PackageReference Include="Serilog" />
</ItemGroup>
Transitive resolution
A monorepo with N projects has a single dependency graph. PCPM resolves it as a whole, which means:
- A package required by two projects is downloaded once.
- Conflicts are resolved globally — there’s only one
Newtonsoft.Jsonversion on disk, even ifapps/webandlibs/contractsask for different ranges. pcpm.lockcontains every direct and transitive dependency, so any re-run (CI, new dev, Docker build) produces the same restore.
Sharing internal projects
The projects inside a monorepo reference each other with normal
<ProjectReference /> entries. PCPM doesn’t need to know about them —
it sees the resolved .csproj graph and just resolves the NuGet
dependencies.
If you want to enforce that all projects reference the same version of
an internal library, use a <PackageVersion /> in
Directory.Packages.props and reference the project by id only.
When the monorepo gets large
On a monorepo with hundreds of projects, the lockfile can get big
(think tens of thousands of transitive entries). PCPM handles this —
resolution is O(V + E) in the worst case, with caching that makes
re-runs essentially free — but the lockfile diff can get noisy.
Practical tips:
- Use
pcpm outdatedandpcpm update <pkg>to keep version bumps intentional. Avoid letting every CI run resolve against a wide range. - Keep
pcpm-workspace.yamlnarrow. The wider the project globs, the bigger the lockfile, and the slowerpcpm listbecomes (it pretty- prints the whole graph). - If you have truly independent slices (e.g. mobile + backend in the
same repo), consider multiple workspaces — one
pcpm-workspace.yamlper slice, each with its own lockfile.
When not to use a monorepo
PCPM is happy to run against a single .sln with two .csproj files.
You don’t need a pcpm-workspace.yaml for that. Reach for the
monorepo setup when your repo has 10+ projects, or when you want a
shared lockfile across multiple solutions.