dotnet-tools and Local Tool Manifests

.NET CLI tools are console applications distributed as NuGet packages. You can install them globally, but for team projects, local tool manifests are the better approach — they version your tools alongside your code and ensure everyone uses the same versions.

Global vs Local Tools

Global tools are installed per-user and available everywhere:

terminal
dotnet tool install -g dotnet-ef

The problem is that different projects may need different versions of the same tool. Global tools also aren't tracked in source control, so a new team member has to know which tools to install and at which versions.

Local tools solve this. They're declared in a manifest file that lives in your repository.

Creating a Tool Manifest

Initialise a manifest in your repository root:

terminal
dotnet new tool-manifest

This creates .config/dotnet-tools.json:

.config/dotnet-tools.json
{
  "version": 1,
  "isRoot": true,
  "tools": {}
}

Now install tools locally:

terminal
dotnet tool install dotnet-ef
dotnet tool install dotnet-format
dotnet tool install dotnet-reportgenerator-globaltool

Each command updates the manifest:

.config/dotnet-tools.json
{
  "version": 1,
  "isRoot": true,
  "tools": {
    "dotnet-ef": {
      "version": "9.0.1",
      "commands": ["dotnet-ef"],
      "rollForward": false
    },
    "dotnet-format": {
      "version": "5.1.250801",
      "commands": ["dotnet-format"],
      "rollForward": false
    },
    "dotnet-reportgenerator-globaltool": {
      "version": "5.4.3",
      "commands": ["reportgenerator"],
      "rollForward": false
    }
  }
}

Restoring Tools

When a team member clones the repository, they run:

terminal
dotnet tool restore

This installs every tool listed in the manifest at the exact specified version. No guesswork, no "works on my machine" problems.

Running Local Tools

Local tools are invoked with dotnet tool run or simply dotnet followed by the tool command:

terminal
# These are equivalent
dotnet tool run dotnet-ef migrations add Initial
dotnet ef migrations add Initial

The CLI automatically finds the manifest and uses the local version.

Using Tools in CI

Tool manifests work naturally in CI pipelines. Add a restore step:

config.yaml
steps:
  - uses: actions/checkout@v4

  - name: Setup .NET
    uses: actions/setup-dotnet@v4
    with:
      dotnet-version: '9.0.x'

  - name: Restore tools
    run: dotnet tool restore

  - name: Run EF migrations check
    run: dotnet ef migrations has-pending-changes --project src/MyApp

  - name: Generate coverage report
    run: |
      dotnet test --collect:"XPlat Code Coverage"
      dotnet reportgenerator -reports:**/coverage.cobertura.xml -targetdir:coverage

Because the versions are pinned in the manifest, CI always uses exactly the same tool versions as local development.

Updating Tools

Update a specific tool:

terminal
dotnet tool update dotnet-ef

Or update all tools at once:

terminal
dotnet tool update --all

The manifest file is updated automatically. Commit the change, and the whole team gets the update on their next dotnet tool restore.

Common Tools Worth Adding

Here are tools that work well as local tools in .NET projects:

Tool Package Purpose
EF Core CLI dotnet-ef Database migrations
ReportGenerator dotnet-reportgenerator-globaltool Coverage reports
Stryker dotnet-stryker Mutation testing
CSharpier csharpier Code formatting
dotnet-outdated dotnet-outdated-tool Check for outdated packages

Automating Tool Restore

You can ensure tools are always restored by hooking into MSBuild. Add a target to your Directory.Build.targets:

Directory.Build.targets
<Project>
  <Target Name="RestoreTools" BeforeTargets="Restore">
    <Exec Command="dotnet tool restore" />
  </Target>
</Project>

Now dotnet restore also restores your tools. This is especially useful for developers who might forget the separate dotnet tool restore step.

Summary

Local tool manifests are a small file with a large impact. They eliminate version drift between developers, make CI pipelines reproducible, and document exactly which tools a project depends on. If you're still installing .NET tools globally for project work, switch to a manifest — it takes thirty seconds to set up and saves hours of debugging mismatched tool versions.