> This page is for SDK, version Legacy.
> For other versions, use one of these documentation indexes:
> - Current (default): https://buildwithfern.com/learn/sdks/current/llms.txt
> - Legacy: https://buildwithfern.com/learn/sdks/legacy/llms.txt

> For clean Markdown content of this page, append .md to this URL. For the complete documentation index, see https://buildwithfern.com/learn/llms.txt.

# Publishing to NuGet

> Learn how to publish your Fern-generated C#/.NET SDK to NuGet using OIDC or API key authentication, with step-by-step configuration and GitHub Actions setup.

> **You're viewing the legacy SDK docs**
>
> These pages cover SDK generation configured with `generators.yml`. New projects use `sdk-config.yml`, so see [the Current version of this page](/learn/sdks/generators/csharp/publishing) for up-to-date guidance. To move an existing project, follow the [SDK Config migration guide](/learn/sdks/legacy/generators/migration-guides/sdk-config), which walks through `fern sdk migrate`, and review the [`sdk-config.yml` reference](/learn/sdks/reference/sdk-config).

> **Enterprise feature**
>
> This feature is available only for the [Enterprise plan](https://buildwithfern.com/pricing). To get started, reach out to [support@buildwithfern.com](mailto:support@buildwithfern.com).

Publish your public-facing Fern C#/.NET SDK to the [NuGet registry](https://www.nuget.org/). After following the steps on this page,
you'll have a versioned package published on NuGet. To distribute the SDK
internally instead, generate to the local file system (optionally
[self-hosted](/learn/sdks/legacy/deep-dives/self-hosted)) and build a `.nupkg` with
[`fern generate --package`](/learn/cli-api-reference/cli-reference/sdk-commands#package).

> **Already publishing to NuGet?**
>
> If you're using API key authentication, consider switching to OIDC (trusted publishing). See [Migrating from API key to OIDC publishing](#migrating-from-api-key-to-oidc-publishing) for the steps.

![Versioned package published on NuGet](/learn/_fern-img/c5a21d0bd777a975fd6a18cc8fcd151b81d2ea386ab89d47ab52a7d75047ce35.webp)

> **Info**
>
> This page assumes that you have:
>
> * An initialized `fern` folder, a GitHub repository for your C#/.NET SDK, and a C#/.NET generator group in `generators.yml`. See [Generating an SDK (C#/.NET)](/learn/sdks/legacy/generators/csharp/quickstart).

Before proceeding, verify the prerequisites are actually in place:

1. **Source repo with `fern/` folder**: The user should be working in their source repo, not the SDK repo. Confirm `generators.yml` exists and has a C# generator group.
2. **C# generator group**: If they skipped the [C# quickstart](/learn/sdks/legacy/generators/csharp/quickstart), they need to run `fern add fern-csharp-sdk --group csharp-sdk` first.

Default to OIDC authentication unless the user has a specific reason to use API key auth. OIDC is more secure and avoids managing NuGet API keys.

## Configure SDK package settings

Update your `generators.yml` file to configure the package name, output location, and client name for NuGet publishing. Your `generators.yml` [should live in your source repository](/learn/sdks/legacy/overview/project-structure) (or on your local machine), not the repository that contains your C#/.NET SDK code.

#### Configure \`output\` location

In the `group` for your C#/.NET SDK, change the output location from `local-file-system` (the default) to `nuget` to indicate that Fern should publish your package directly to the NuGet registry:

**`generators.yml`**

```yaml {6-7} title="generators.yml"
groups: 
  csharp-sdk:
    generators:
      - name: fern-csharp-sdk
        version: 2.92.3
        output:
          location: nuget
```

#### Add a unique package name

Your package name must be unique in the NuGet registry, otherwise publishing your SDK will fail.

**`generators.yml`**

```yaml {8} title="generators.yml"
groups: 
  csharp-sdk:
    generators:
      - name: fern-csharp-sdk
        version: 2.92.3
        output:
          location: nuget
          package-name: your-package-name
```

#### Configure \`client-class-name\`

The `client-class-name` option controls the name of the generated client class.

**`generators.yml`**

```yaml {9-10} title="generators.yml"
groups: 
  csharp-sdk:
    generators:
      - name: fern-csharp-sdk
        version: 2.92.3
        output:
          location: nuget
          package-name: your-package-name
        config:
          client_class_name: YourClientName # must be PascalCase
```

The description, authors, tags, copyright, icon, and repository links on your package's nuget.org listing come from the [`package-metadata`](/learn/sdks/legacy/generators/csharp/configuration#package-metadata) config option.

## Configure GitHub publishing

Fern can automatically publish your SDK to NuGet via GitHub Actions. Configure your GitHub repository and publishing mode:

Optionally set the mode to control how Fern handles SDK publishing:

* `mode: release` (default): Fern generates code, commits to the default branch (or the `branch` you specify), and tags a release automatically
* `mode: pull-request` (recommended):  Fern generates code and creates a PR for you to review before release
* `mode: push`: Fern generates code and pushes to a branch you specify for you to review before release

You can also configure other settings, like the reviewers or license. Refer to the [full `github` (`generators.yml`) reference](/learn/sdks/legacy/reference/generators-yml#github) for more information.

**`generators.yml`**

```yaml title="generators.yml" {11-14}
groups:
  csharp-sdk:
    generators:
      - name: fern-csharp-sdk
        version: 2.92.3
        output:
          location: nuget
          package-name: your-package-name
        config:
          client_class_name: YourClientName
        github:
          repository: your-org/company-csharp
          mode: push  # or "pull-request"
          branch: your-branch-name  # Required for mode: push
```

## Configure authentication

Choose how you want to authenticate with NuGet when publishing. **OpenID Connect (OIDC) authentication is recommended** because it removes the need to manage long-lived API keys.

#### OIDC authentication (Recommended)

OIDC-based publishing (also known as "trusted publishing") is the most secure way to publish. With OIDC, you don't need to manage API keys — NuGet trusts your GitHub repository to publish directly via short-lived tokens scoped to your workflow.

#### Add OIDC to generators.yml

Add `api-key: OIDC` to the `output` section:

**`generators.yml`**

```yaml title="generators.yml" {9}
groups:
  csharp-sdk:
    generators:
      - name: fern-csharp-sdk
        version: 2.92.3
        output:
          location: nuget
          package-name: your-package-name
          api-key: OIDC
        config:
          client_class_name: YourClientName
        github:
          repository: your-org/company-csharp
```

#### Generate your SDK

Generate your SDK to create the GitHub Actions workflow with OIDC configuration:

```bash
fern generate --group csharp-sdk
```

This creates a `.github/workflows/ci.yml` file configured to use OIDC for NuGet publishing. The generated workflow includes:

* A `publish` job with `permissions: { contents: read, id-token: write }`
* The [`NuGet/login@v1`](https://github.com/NuGet/login) action, which exchanges a GitHub OIDC token for a short-lived NuGet API key (\~1 hour)
* `dotnet nuget push` using the temporary API key from the login step

Alternatively, you can push your `generators.yml` changes and let the Fern GitHub Action generate the workflow for you.

#### Authorize your repository on nuget.org

Configure trusted publishing on nuget.org to allow your GitHub repository to publish:

1. Go to [nuget.org](https://www.nuget.org/) and click your user avatar
2. Select **Trusted Publishing** > **Create policy**
3. Fill in:
   * **Repository Owner**: Your GitHub organization or username
   * **Repository**: Your C# SDK repository name
   * **Workflow File**: `ci.yml` (filename only, without the `.github/workflows/` prefix)
4. Choose the policy owner (organization profile or personal account). The policy applies to all packages owned by that profile.

#### Set the \`NUGET_USER\` GitHub Actions secret

The generated workflow requires a `NUGET_USER` secret containing your nuget.org profile name (not your email).

1. Open your SDK repository on GitHub and go to **Settings**
2. Navigate to **Secrets and variables** > **Actions**
3. Click **New repository secret**
4. Name it `NUGET_USER` and enter your nuget.org profile name
5. Click **Add secret**

> **Info**
>
> For private repositories, the trust policy starts as **temporarily active for 7 days**. It becomes permanent after the first successful login binds the repository and owner IDs. Run the publish workflow within that 7-day window.

#### Troubleshooting

**"Unable to authenticate" error**

Common causes:

* Workflow filename doesn't match exactly (must be `ci.yml`)
* Trusted publisher configuration on nuget.org doesn't match your repository settings
* A stale `NUGET_API_KEY` secret is interfering with OIDC authentication

**Solution:** Double-check your trusted publisher configuration on nuget.org matches your repository name and workflow filename exactly. Remove any old `NUGET_API_KEY` secret from your repository.

**Private repository limitations**

For private repositories, the trust policy is temporarily active for 7 days. It becomes permanent after the first successful login. Run the publish workflow within that window.

#### API key authentication (Legacy)

#### Generate a NuGet API key

1. Log into [NuGet](https://nuget.org/) or create a new account
2. Click on your profile picture
3. Select **API Keys**, then **Create**
4. Name your key
5. Select **Push > Push new packages and package versions** as the **Select Scopes** type
6. Enter `*` under **Select Packages > Glob Pattern**

   > **Replacing an existing NuGet package**
   >
   > If you are overriding an existing package, select the relevant
   > package instead of entering `*`.
7. Click **Create**

![Creating a new API key](/learn/_fern-img/7d0063cf1d598f3a137208bed660abcabcc990d8936e2d10cca376803c7214df.webp)

> **Warning**
>
> Save your new key — it won't be displayed after you leave the page.

#### Add API key to generators.yml

Add `api-key: ${NUGET_API_KEY}` to the `output` section:

**`generators.yml`**

```yaml title="generators.yml" {9}
groups: 
  csharp-sdk:
    generators:
      - name: fern-csharp-sdk
        version: 2.92.3
        output:
          location: nuget
          package-name: your-package-name
          api-key: ${NUGET_API_KEY}
        config:
          client_class_name: YourClientName
        github: 
          repository: your-org/company-csharp
```

#### Add \`NUGET_API_KEY\` as a GitHub Actions secret

1. Open your repository on GitHub and go to **Settings**
2. Navigate to **Secrets and variables** > **Actions**
3. Click **New repository secret**
4. Name it `NUGET_API_KEY` and paste your NuGet API key
5. Click **Add secret**

![NUGET\_API\_KEY secret](/learn/_fern-img/fe114ac818cc31b427a032317ea8eeeff188210ced36200aaf3b970284b783a9.webp)

## Publish your SDK

Your SDK will automatically be published to NuGet when you create a GitHub release with a version tag:

1. Create a GitHub release with a version tag (for example, `v1.0.0`)
2. The CI workflow will run automatically and publish to NuGet
3. View your package on nuget.org to confirm the version

#### Alternative: Manual workflow dispatch

If you prefer to trigger publishes manually from your source repository, set up a release workflow via [GitHub Actions](https://docs.github.com/en/actions).

#### Add secret for your Fern API key

1. Open your source repository on GitHub and go to **Settings**
2. Navigate to **Secrets and variables** > **Actions**
3. Select **New repository secret**
4. Name your secret `FERN_TOKEN`
5. Add your Fern API key. If you don't already have one, generate one by
   running `fern token`. By default, the API key is generated for the
   organization listed in `fern.config.json`.
6. Click **Add secret**

If you use API key authentication, also add a `NUGET_API_KEY` secret with your NuGet API key.

#### Set up a new workflow

Create a CI workflow that you can manually trigger from the GitHub UI. Navigate to **Actions** > **New workflow** > **Set up workflow yourself**:

**`.github/workflows/publish.yml`**

```yaml title=".github/workflows/publish.yml" maxLines=0
name: Publish C#/.NET SDK

on:
  workflow_dispatch:
    inputs:
      version:
        description: "The version of the C#/.NET SDK that you would like to release"
        required: true
        type: string

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repo
        uses: actions/checkout@v4

      - name: Install Fern CLI
        run: npm install -g fern-api

      - name: Release C#/.NET SDK
        env:
          FERN_TOKEN: ${{ secrets.FERN_TOKEN }}
          NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} # Only needed for API key authentication
        run: |
          fern generate --group csharp-sdk --version ${{ inputs.version }} --log-level debug
```

#### Regenerate and release your SDK

Navigate to the **Actions** tab, select the workflow, specify a version number, and click **Run workflow**. This regenerates your SDK.

![Running C# publish workflow](/learn/_fern-img/519254f94e2afce5b51212349518eb55548d713cc84d77f5aebd1bcc2d688495.webp)

The rest of the release process depends on your chosen mode:

* **Release mode (default):** If you didn't specify a `mode` or set `mode: release`, no further action is required. Fern automatically tags the new release with your specified version number and initiates the publishing workflow in your SDK repository.

* **Pull request or push mode:** If you set `mode: pull-request` or `mode: push`, Fern creates a pull request or pushes to a branch respectively. Review and merge the PR (`pull-request`) or branch (`push`), then [tag a new release](https://docs.github.com/en/repositories/releasing-projects-on-github/managing-releases-in-a-repository) to initiate the publishing workflow in your SDK repository.

Once the workflow completes, view your new release by logging into NuGet and navigating to **Manage Packages**.

#### Alternative: Release via CLI and environment variables

#### Set NuGet environment variable

If you use API key authentication, set the `NUGET_API_KEY` environment variable on your local machine:

```bash
export NUGET_API_KEY=your-actual-nuget-api-key
```

#### Regenerate and release your SDK

Regenerate your SDK, specifying the version:

```bash
fern generate --group csharp-sdk --version <version>
```

The rest of the release process depends on your chosen mode:

* **Release mode (default):** If you didn't specify a `mode` or set `mode: release`, no further action is required. Fern automatically tags the new release with your specified version number and initiates the publishing workflow in your SDK repository.

* **Pull request or push mode:** If you set `mode: pull-request` or `mode: push`, Fern creates a pull request or pushes to a branch respectively. Review and merge the PR (`pull-request`) or branch (`push`), then [tag a new release](https://docs.github.com/en/repositories/releasing-projects-on-github/managing-releases-in-a-repository) to initiate the publishing workflow in your SDK repository.

Once the workflow completes, view your new release by logging into NuGet and navigating to **Manage Packages**.

---

## Migrating from API key to OIDC publishing

If you're using API key authentication and want to migrate to OIDC, follow these steps.

### Why migrate to OIDC

OIDC-based publishing (trusted publishing) removes security risks associated with long-lived API keys:

* Keys can be exposed in logs or configuration files
* Compromised keys can be used persistently until manually revoked
* Key rotation adds operational overhead

With OIDC, NuGet trusts your GitHub Actions workflow directly. The only secret required is `NUGET_USER` (your nuget.org profile name), which isn't sensitive.

### Prerequisites

Before migrating:

* A package published to [NuGet](https://www.nuget.org/)
* A GitHub repository with GitHub Actions configured
* Access to your account on [nuget.org](https://www.nuget.org/)

### Choose your migration path

#### Path 1: Upgrade your generator (Recommended)

This is the easiest path if you can upgrade to a version of the C# SDK generator that supports OIDC.

**When to use this path:**

* You can upgrade to a Fern C# SDK generator version that supports OIDC
* You haven't `.fernignore`'d your CI workflow file

#### Configure trusted publishing on nuget.org

1. Go to [nuget.org](https://www.nuget.org/) and click your user avatar
2. Select **Trusted Publishing** > **Create policy**
3. Fill in:
   * **Repository Owner**: Your GitHub organization or username
   * **Repository**: Your C# SDK repository name
   * **Workflow File**: `ci.yml` (filename only, without the `.github/workflows/` prefix)
4. Choose the policy owner (organization profile or personal account). The policy applies to all packages owned by that profile.

#### Update your generators.yml

Change the `output.api-key` field from `${NUGET_API_KEY}` to `OIDC`:

**`generators.yml`**

```yaml title="generators.yml"
groups:
  csharp-sdk:
	generators:
	  - name: fern-csharp-sdk
		version: 2.92.3
		output:
		  location: nuget
		  package-name: your-package-name
		  api-key: OIDC  # Changed from ${NUGET_API_KEY}
		config:
		  client_class_name: YourClientName
		github:
		  repository: your-org/company-csharp
```

#### Regenerate your SDK

Regenerate the SDK to update the CI workflow with OIDC configuration:

**Locally:**

```bash
fern generate --group csharp-sdk
```

**Or via GitHub Actions:**

Push your updated `generators.yml` and let the Fern GitHub Action regenerate the SDK.

This updates `.github/workflows/ci.yml` with the required OIDC permissions and switches to the `NuGet/login@v1` action.

#### Add the NUGET_USER secret

The OIDC workflow requires a `NUGET_USER` secret containing your nuget.org profile name (not your email).

1. Open your SDK repository on GitHub and go to **Settings**
2. Navigate to **Secrets and variables** > **Actions**
3. Click **New repository secret**
4. Name it `NUGET_USER` and enter your nuget.org profile name
5. Click **Add secret**

#### Remove the NUGET_API_KEY secret

After verifying the migration works, remove the `NUGET_API_KEY` secret from your GitHub repository settings (**Settings** > **Secrets and variables** > **Actions**). A stale API key can interfere with OIDC authentication.

#### Path 2: Manual CI workflow update

Use this path if you can't upgrade the generator or have customized your CI workflow.

**When to use this path:**

* You can't upgrade due to breaking changes or compatibility concerns
* You've customized your CI workflow and added it to `.fernignore`
* Path 1 didn't update your workflow file

#### Configure trusted publishing on nuget.org

Follow the same instructions as Path 1 to create a trusted publishing policy on nuget.org.

#### Update your CI workflow manually

Open your `.github/workflows/ci.yml` file and make these changes to the `publish` job:

**`.github/workflows/ci.yml`**

```yaml title=".github/workflows/ci.yml"
publish:
  needs: [ compile, test ]
  if: github.event_name == 'push' && contains(github.ref, 'refs/tags/')
  runs-on: ubuntu-latest
  permissions:
	contents: read   # ADD THIS: Required for actions/checkout@v4
	id-token: write  # ADD THIS: Required for OIDC
  steps:
	- name: Checkout repo
	  uses: actions/checkout@v4

	- name: Setup .NET
	  uses: actions/setup-dotnet@v4

	# ADD THIS: Exchange GitHub OIDC token for a short-lived NuGet API key
	- name: NuGet OIDC login
	  uses: NuGet/login@v1
	  with:
		nuget-server-url: https://api.nuget.org/v3/index.json
		nuget-user: ${{ secrets.NUGET_USER }}

	# MODIFY THIS: Remove env block with NUGET_API_KEY
	- name: Publish to NuGet
	  run: dotnet nuget push src/*/bin/Release/*.nupkg --api-key ${{ env.NUGET_AUTH_TOKEN }} --source https://api.nuget.org/v3/index.json
```

**Key changes from API key publishing:**

* Adds `permissions` with `id-token: write` and `contents: read`
* Adds the `NuGet/login@v1` step that exchanges the OIDC token for a short-lived API key
* Removes any `env` blocks referencing `NUGET_API_KEY`

#### Add the NUGET_USER secret

The OIDC workflow requires a `NUGET_USER` secret. Follow the same instructions as Path 1, Step 4.

#### (Optional) Add ci.yml to .fernignore

Prevent future generator updates from overwriting your manual changes:

**`.fernignore`**

```text title=".fernignore"
.github/workflows/ci.yml
```

#### Remove the NUGET_API_KEY secret

After verifying the migration works, remove the `NUGET_API_KEY` secret from your GitHub repository settings. A stale API key can interfere with OIDC authentication.

### Verify your migration

After completing either migration path:

1. **Trigger a workflow run** by creating a GitHub release with an alpha tag (e.g., `v1.0.0-alpha`)
2. **Check the workflow logs** to verify the publish step succeeds
3. **Verify your package** by visiting it on [nuget.org](https://www.nuget.org/) to confirm the new version published correctly

### Migration troubleshooting

#### "Unable to authenticate" error

**Common causes:**

* Workflow filename doesn't match exactly (must be `ci.yml` with the `.yml` extension)
* Trusted publisher configuration on nuget.org doesn't match your repository settings
* Missing `id-token: write` or `contents: read` permissions in workflow
* A stale `NUGET_API_KEY` secret is interfering with OIDC authentication
* Using self-hosted runners (not supported)

**Solution:** Double-check your trusted publisher configuration on nuget.org matches your repository name and workflow filename exactly. Remove any old `NUGET_API_KEY` secret.

#### Workflow still using NUGET_API_KEY

If your workflow continues using API key authentication:

* Verify you've removed any `env` blocks referencing `NUGET_API_KEY` from the publish step
* Ensure the `publish` job uses the `NuGet/login@v1` action
* Ensure you're using a generator version that supports OIDC (if using Path 1)

#### Private repository limitations

For private repositories, the trust policy starts as **temporarily active for 7 days**. It becomes permanent after the first successful login binds the repository and owner IDs. Run the publish workflow within that window.