NuGet Trusted Publishing Setup
Set up NuGet trusted publishing on a GitHub Actions repo. Replaces long-lived API keys with OIDC-based short-lived tokens — no secrets to rotate or leak.
Repository integration:
AGENTS.mdand the repository's existing release workflow are authoritative. Preserve Central Package Management, locked restore, existing package validation, and SHA-pinned GitHub Actions. Examples below use generic action tags only as reference; do not weaken the repository's pinning policy when applying them.
Prerequisites
- GitHub Actions — this skill covers GitHub Actions setup only
- nuget.org account — the user needs access to create trusted publishing policies
When to Use This Skill
Use this skill when:
- Setting up trusted publishing for a NuGet package
- Migrating from
secrets.NUGET_API_KEYto OIDC-based publishing - Asked about keyless or secure NuGet publishing
- Creating a new NuGet publish workflow from scratch
- Asked to "remove NuGet API key" or "use NuGet/login"
- Setting up publishing for a dotnet tool, MCP server, or template package
- Asked about
NuGet/login@v1orid-token: write
Safety Rules
Bail-out rule: If any phase fails after one fix attempt on an infrastructure/auth issue, stop and ask the user. Don't loop on environment problems.
Never delete or overwrite without confirmation: Removing API key secrets, deleting tags/releases, removing workflow steps, or changing package IDs. NuGet package IDs are permanent — mistakes can't be undone.
Process
Phase 1: Assess
Inspect the repo and report findings before making changes.
Find and classify packable projects — check
.csprojfiles andDirectory.Build.props. Classify in this order:<PackageType>Template</PackageType>→ Template<PackageType>McpServer</PackageType>→ MCP server<PackAsTool>true</PackAsTool>→ Dotnet tool- Class library (
IsPackable=trueor noOutputType) → Library <OutputType>Exe</OutputType>with<IsPackable>true</IsPackable>→ Application package<OutputType>Exe</OutputType>withoutPackAsToolorIsPackable→ Not packable by default
Validate structure for each project's type:
Type Required All PackageId,Version(directly or through repo-wide properties)Dotnet tool PackAsTool;ToolCommandNameoptional but recommendedMCP server PackageType=McpServer,.mcp/server.jsonincluded in packageTemplate PackageType=Template,.template.config/template.jsonunder content dirFind existing publish workflows in
.github/workflows/— look fordotnet nuget push,nuget push, ordotnet pack.Check version consistency for package-specific metadata.
Report findings before changing authentication or release behavior.
See references/package-types.md for package details.
Phase 2: Local Verification
Pack and verify before touching nuget.org — publishing errors waste a permanent version number.
- Run the repository's existing deterministic restore/build/test/package validation first.
- Run
dotnet pack -c Release -o ./artifactsor the repository-equivalent pack command. - Inspect package contents and metadata before publishing.
Phase 3: nuget.org Policy
This phase requires user action on nuget.org.
- Determine the repo owner, repo name, and exact workflow filename that publishes.
- Create the trusted publishing policy at nuget.org with those exact values.
- If the workflow uses a GitHub Environment, ensure the policy environment matches exactly.
- Prefer a protected
releaseenvironment when it matches the repository's release design.
Wait for the user to confirm the policy before removing old API-key paths or secrets.
Phase 4: Workflow Setup
For migration from an API key:
- Add OIDC permission to the publishing job:
permissions:
id-token: write
contents: read
- Add NuGet login before push, using the repository's approved pinned action reference:
- name: NuGet login (OIDC)
id: login
uses: NuGet/login@<approved-pinned-reference>
with:
user: ${{ secrets.NUGET_USER }}
- Replace the static API key in the push step:
--api-key ${{ steps.login.outputs.NUGET_API_KEY }} --skip-duplicate
- Verify a real publish before removing any legacy secret.
Troubleshooting
| Problem | Cause | Fix |
|---|---|---|
NuGet/login 403 |
Missing id-token: write |
Add to job permissions |
| "no matching policy" | Workflow filename/environment mismatch | Verify exact policy values |
| Push unauthorized | Package not owned by policy account | Check policy owner on nuget.org |
| Token expired | Login too far before push | Move login closer to push |
already_exists on push |
Re-running same version | Keep --skip-duplicate where appropriate |
| GitHub Release 422 | Duplicate release for tag | Resolve the release conflict without reusing package versions |
References
- references/package-types.md
- references/publish-workflow.md
- NuGet Trusted Publishing