SourceLink: Debug NuGet Packages Like Your Own Code

Have you ever stepped into a NuGet package method in the debugger and been met with decompiled code or "Source not available"? SourceLink solves this by embedding source control metadata in your PDBs, allowing the debugger to fetch the exact source code from your repository.

SourceLink embeds a mapping between your compiled code and the source files in your Git repository. When a consumer debugs your library, the debugger uses this mapping to download the source directly from GitHub (or Azure DevOps, GitLab, etc.) at the exact commit the package was built from.

No source code is shipped in the package. The PDB contains a URL template, and the debugger fetches files on demand.

Setting It Up

For .NET 8 and later, SourceLink is built into the SDK. You just need to enable a few properties. Add these to your Directory.Build.props:

Directory.Build.props
<Project>
  <PropertyGroup>
    <!-- Enable SourceLink -->
    <PublishRepositoryUrl>true</PublishRepositoryUrl>
    <EmbedUntrackedSources>true</EmbedUntrackedSources>

    <!-- Include symbols in the package -->
    <IncludeSymbols>true</IncludeSymbols>
    <SymbolPackageFormat>snupkg</SymbolPackageFormat>

    <!-- Deterministic builds for CI -->
    <Deterministic>true</Deterministic>
    <ContinuousIntegrationBuild Condition="'$(CI)' == 'true'">true</ContinuousIntegrationBuild>
  </PropertyGroup>
</Project>

For .NET 7 and earlier, you also need the SourceLink package:

config.xml
<ItemGroup>
  <PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All" />
</ItemGroup>

Replace Microsoft.SourceLink.GitHub with the appropriate provider:

Symbol Packages

The IncludeSymbols and SymbolPackageFormat properties tell dotnet pack to produce a .snupkg file alongside the .nupkg:

terminal
dotnet pack -c Release
# Produces:
#   MyLibrary.1.0.0.nupkg
#   MyLibrary.1.0.0.snupkg

Push both to NuGet.org:

terminal
dotnet nuget push ./nupkgs/*.nupkg --api-key $NUGET_KEY --source https://api.nuget.org/v3/index.json

NuGet.org automatically associates the symbol package with the main package. The .snupkg is pushed alongside the .nupkg in a single command.

Deterministic Builds

SourceLink requires deterministic builds to work correctly. Without Deterministic and ContinuousIntegrationBuild, the PDB contains absolute file paths from the build machine, which are meaningless to consumers.

With these properties enabled, file paths in the PDB are normalised to relative paths rooted at the repository, and the source mapping points to the correct commit in your source control provider.

Configuring the Debugger

Consumers need to configure Visual Studio to use SourceLink:

  1. Go to Tools → Options → Debugging → General.
  2. Uncheck Enable Just My Code.
  3. Check Enable Source Link support.
  4. Go to Tools → Options → Debugging → Symbols.
  5. Enable the NuGet.org Symbol Server.

With these settings, stepping into a SourceLink-enabled NuGet package fetches the source automatically.

Embedding Source (Alternative)

If you'd rather ship the actual source files in the PDB instead of relying on runtime downloads, use embedded PDBs:

config.xml
<PropertyGroup>
  <DebugType>embedded</DebugType>
  <EmbedAllSources>true</EmbedAllSources>
</PropertyGroup>

This increases your package size but eliminates the need for a symbol server and works offline. It's a reasonable choice for internal packages where size isn't a concern.

You can validate that SourceLink is correctly configured using the sourcelink CLI tool:

terminal
dotnet tool install --global sourcelink
sourcelink print-urls MyLibrary.pdb

This prints the URL mappings, so you can verify they point to valid source files. If the URLs return 404s, check that your repository is public (or that authentication is configured for private repos).

CI Pipeline Integration

Here's a GitHub Actions step that builds, packs, and pushes with SourceLink:

config.yaml
      - name: Pack
        run: dotnet pack -c Release -o ./nupkgs
        env:
          CI: true

      - name: Push
        run: dotnet nuget push ./nupkgs/*.nupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate

The CI: true environment variable activates the ContinuousIntegrationBuild condition from the earlier Directory.Build.props configuration.

Summary

SourceLink turns "reading decompiled code" into "stepping through the original source". The setup is minimal — a few MSBuild properties — and the debugging experience for consumers is dramatically better. If you maintain a NuGet package, enable SourceLink. Your users will thank you.