> 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 PyPI > Learn how to publish your Fern Python SDK to PyPI using OIDC or token-based authentication. Complete guide with GitHub Actions setup. Publish your public-facing Fern Python SDK to the [PyPI registry](https://pypi.org/). After following the steps on this page, you'll have a versioned package published on PyPI. To distribute the SDK internally instead, generate to the local file system (optionally [self-hosted](/learn/sdks/deep-dives/self-hosted)) and build a wheel with [`fern generate --package`](/learn/cli-api-reference/cli-reference/sdk-commands#package). #### Already publishing to PyPI? If you're using token-based authentication, consider switching to OIDC (trusted publishing). See [Migrating from token-based to OIDC publishing](#migrating-from-token-based-to-oidc-publishing) for the steps. ![Versioned package published on PyPI](/learn/_fern-img/4fc87f38377f7454855545fa0af2644d685cbf5e4e9e9ec04d17d23384499f68.webp) This page assumes that you have: * An initialized `fern` folder, a GitHub repository for your Python SDK, and a Python generator group in `generators.yml`. See [Generating an SDK (Python)](/learn/sdks/generators/python/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 Python generator group. 2. **Python generator group**: If they skipped the [Python quickstart](/learn/sdks/generators/python/quickstart), they need to run `fern add fern-python-sdk --group python-sdk` first. Default to OIDC authentication unless the user has a specific reason to use token-based auth. OIDC is more secure and avoids managing PyPI tokens. ## Configure SDK package settings Update your `generators.yml` file to configure the package name, output location, and client naming for PyPI publishing. Your `generators.yml` [should live in your source repository](/learn/sdks/overview/project-structure) (or on your local machine), not the repository that contains your Python SDK code. #### Configure \`output\` location In the `group` for your Python SDK, change the output location in from `local-file-system` (the default) to `pypi` to indicate that Fern should publish your package directly to the PyPi registry: **`generators.yml`** ```yaml title="generators.yml" {6-7} groups: python-sdk: generators: - name: fern-python-sdk version: 5.30.2 output: location: pypi ``` #### Add a unique package name Your package name must be unique in the PyPI repository, otherwise publishing your SDK to PyPI will fail. **`generators.yml`** ```yaml title="generators.yml" {8} groups: python-sdk: generators: - name: fern-python-sdk version: 5.30.2 output: location: pypi package-name: your-package-name ``` #### Configure \`client-class-name\` The `client-class-name` option controls the name of the generated client. This is the name customers use to import your SDK (`import { your-client-name } from 'your-package-name';`). **`generators.yml`** ```yaml title="generators.yml" {9-10} groups: python-sdk: generators: - name: fern-python-sdk version: 5.30.2 output: location: pypi package-name: your-package-name config: client_class_name: YourClientName # must be PascalCase ``` #### Add PyPI metadata (optional) You can add publishing metadata to your PyPI package to improve discoverability and provide additional information to users. This metadata appears on your package's PyPI page and includes `keywords` for PyPI search and discovery, `documentation-link` for your package documentation, and `homepage-link` for your project homepage. You can additionally add general metadata for the SDK (description, contact email, author, license, etc.) at [the individual SDK level](/learn/sdks/reference/generators-yml#metadata-2) or [globally for all SDKs](/learn/sdks/reference/generators-yml#metadata). **`generators.yml`** ```yaml title="generators.yml" {9, 13} groups: python-sdk: generators: - name: fern-python-sdk version: 5.30.2 output: location: pypi package-name: your-package-name metadata: # Publishing metadata keywords: ["api", "sdk", "client"] documentation-link: "https://docs.yourcompany.com" homepage-link: "https://yourcompany.com" metadata: # General SDK metadata license: MIT config: client_class_name: YourClientName ``` ## Configure GitHub publishing Fern can automatically publish your SDK to PyPI 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/reference/generators-yml#github) for more information. **`generators.yml`** ```yaml title="generators.yml" {11-14} groups: python-sdk: generators: - name: fern-python-sdk version: 5.30.2 output: location: pypi package-name: your-package-name config: client_class_name: YourClientName github: repository: your-org/your-repository mode: push # or "pull-request" branch: your-branch-name # Required for mode: push ``` ## Configure authentication Choose how you want to authenticate with PyPI when publishing. **OpenID Connect (OIDC) authentication is recommended** because it removes the need to manage long-lived API tokens. #### 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 authentication tokens - PyPI trusts your GitHub repository to publish directly. #### Prerequisites * Fern Python SDK generator version `4.38.1` or later * Fern CLI version `5.46.0` or later (only required for local generation with `--local`) #### Add OIDC to generators.yml Add `token: OIDC` to the `output` section: **`generators.yml`** ```yaml title="generators.yml" {9} groups: python-sdk: generators: - name: fern-python-sdk version: 5.30.2 # Must be 4.38.1 or later output: location: pypi package-name: your-package-name token: OIDC config: client_class_name: YourClientName github: repository: your-org/your-repository mode: push branch: your-branch-name ``` #### Generate your SDK Generate your SDK to create the GitHub Actions workflow with OIDC configuration: ```bash fern generate --group python-sdk ``` This creates a `.github/workflows/ci.yml` file that's configured to use OIDC for PyPI publishing. Alternatively, you can push your `generators.yml` changes and let the Fern GitHub Action generate the workflow for you. #### Authorize your repository on PyPI Configure trusted publishing on PyPI to allow your GitHub repository to publish: 1. Navigate to your project on [PyPI](https://pypi.org/) and open **Manage** > **Publishing** 2. Under **Add a new publisher**, select **GitHub** 3. Fill in: * **Owner**: Your GitHub username or organization * **Repository name**: Your Python SDK repository name (e.g., `your-repository`) * **Workflow name**: `ci.yml` * **Environment name**: `pypi` For more details, see PyPI's [trusted publishers documentation](https://docs.pypi.org/trusted-publishers/). #### Troubleshooting **"invalid-publisher" or authentication errors** Common causes: * Workflow filename doesn't match exactly (must be `ci.yml`) * Environment name on PyPI doesn't match the `pypi` environment in the generated workflow * Trusted publisher configuration on PyPI doesn't match your repository settings **Solution:** Double-check your trusted publisher configuration on PyPI matches your repository name, workflow filename, and environment name exactly. #### Token-based authentication #### Generate a PyPI token 1. Log into [PyPI](https://pypi.org/) or create a new account 2. Click on your profile picture and select **Account settings** 3. Scroll down to **API tokens** and click **Add API token** 4. Name your token and set the scope to the relevant projects 5. Click **Create token** ![Creating a New API Token](/learn/_fern-img/e4cd601fc8bc0d93bb4673b9fcfe38547d2e85a62b857779dc8584039de005c5.webp) Save your new token - it won't be displayed after you leave the page. #### Add token to generators.yml Add `token: ${PYPI_TOKEN}` to the `output` section: **`generators.yml`** ```yaml {9} title="generators.yml" groups: python-sdk: generators: - name: fern-python-sdk version: 5.30.2 output: location: pypi package-name: your-package-name token: ${PYPI_TOKEN} config: client_class_name: YourClientName github: repository: your-org/your-repository ``` #### Add PYPI\_TOKEN 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 `PYPI_TOKEN` and paste your PyPI token 5. Click **Add secret** ![PYPI\_TOKEN secret](/learn/_fern-img/8d303ec569da7ccbcc655f65bd22cb9e2b930d0e72343f56424b4a5435607aec.webp) ## Publish your SDK Your SDK will automatically be published to PyPI 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 PyPI 3. View your package on PyPI to confirm the version #### Alternative: Manual workflow dispatch Set up a release workflow via [GitHub Actions](https://docs.github.com/en/actions/get-started/quickstart) so you can trigger new SDK releases directly from your source repository. 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/get-started/quickstart). #### Add secret for your Fern API key 1. Open your Fern repository on GitHub and go to **Settings** > **Secrets and variables** > **Actions** 2. Select **New repository secret** 3. Name your secret `FERN_TOKEN` 4. 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`. 5. Click **Add secret** If you use token-based authentication, also add a `PYPI_TOKEN` secret with your PyPI token. #### Set up a new workflow Set up a CI workflow that you can manually trigger from the GitHub UI. In your repository, navigate to **Actions**. Select **New workflow**, then **Set up workflow yourself**. Add a workflow that's similar to this: **`.github/workflows/publish.yml`** ```yaml title=".github/workflows/publish.yml" maxLines=0 name: Publish Python SDK on: workflow_dispatch: inputs: version: description: "The version of the Python 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 Python SDK env: FERN_TOKEN: ${{ secrets.FERN_TOKEN }} PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} # Only needed for token-based authentication run: | fern generate --group python-sdk --version ${{ inputs.version }} --log-level debug ``` #### Regenerate and release your SDK Navigate to the **Actions** tab, select the workflow you just created, specify a version number, and click **Run workflow**. This regenerates your SDK. ![Running Python publish workflow](/learn/_fern-img/0e57015fd638ff28f1abda12e7bb1d94f97b793d041512051dbbb45124a08566.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, you can view your new release by logging into PyPi and navigating to **Your projects**. #### Alternative: Release via CLI and environment variables #### Set PyPI environment variable If you use token-based authentication, set the `PYPI_TOKEN` environment variable on your local machine: ```bash export PYPI_TOKEN=your-actual-pypi-token ``` #### Regenerate and release your SDK Regenerate your SDK, specifying the version: ```bash fern generate --group python-sdk --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, you can view your new release by logging into PyPI and navigating to **Your projects**. --- ## Migrating from token-based to OIDC publishing If you're using token-based 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 tokens: * Tokens can be exposed in logs or configuration files * Compromised tokens can be used persistently until manually revoked * Token rotation adds operational overhead With OIDC, PyPI trusts your GitHub Actions workflow directly. No secrets to store, rotate, or leak. ### Prerequisites Before migrating: * A package published to [PyPI](https://pypi.org/) * A GitHub repository with GitHub Actions configured * Access to your package settings on [PyPI](https://pypi.org/) * Fern CLI version `5.46.0` or later (only required for local generation with `--local`) ### Choose your migration path #### Path 1: Upgrade your generator (Recommended) This is the easiest path if you can upgrade to version `4.38.1` or later of the Python SDK generator. **When to use this path:** * You can upgrade to Fern Python SDK generator version `4.38.1` or later * You haven't `.fernignore`'d your CI workflow file #### Configure trusted publishing on PyPI 1. Navigate to your project on [PyPI](https://pypi.org/) and open **Manage** > **Publishing** 2. Under **Add a new publisher**, select **GitHub** 3. Fill in: * **Owner**: Your GitHub username or organization * **Repository name**: Your Python SDK repository name * **Workflow name**: `ci.yml` * **Environment name**: `pypi` For more details, see PyPI's [trusted publishers documentation](https://docs.pypi.org/trusted-publishers/). #### Update your generators.yml Change the `output.token` field from `${PYPI_TOKEN}` to `OIDC` and ensure you're using version `4.38.1` or later: **`generators.yml`** ```yaml title="generators.yml" groups: python-sdk: generators: - name: fern-python-sdk version: 5.30.2 # Must be 4.38.1 or later output: location: pypi package-name: your-package-name token: OIDC # Changed from ${PYPI_TOKEN} config: client_class_name: YourClientName github: repository: your-org/your-repository ``` #### Regenerate your SDK Regenerate the SDK to update the CI workflow with OIDC configuration: **Locally:** ```bash fern generate --group python-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 `pypa/gh-action-pypi-publish` action. #### Remove the PYPI\_TOKEN secret After verifying the migration works, remove the `PYPI_TOKEN` secret from your GitHub repository settings (**Settings** > **Secrets and variables** > **Actions**). #### 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 PyPI Follow the same instructions as Path 1 to add your repository as a trusted publisher on PyPI. #### Add a build job to your CI workflow Open `.github/workflows/ci.yml` and add a `build` job that builds and uploads your package as an artifact: **`.github/workflows/ci.yml`** ```yaml title=".github/workflows/ci.yml" build: name: Build distribution runs-on: ubuntu-latest steps: - name: Checkout repo uses: actions/checkout@v4 - name: Set up python uses: actions/setup-python@v4 with: python-version: "3.9" - name: Bootstrap poetry run: | curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1 - name: Install dependencies run: poetry install - name: Build package run: poetry build - name: Store the distribution packages uses: actions/upload-artifact@v4 with: name: python-package-distributions path: dist/ ``` #### Replace the publish job Replace your existing `publish` job with the OIDC-based version: **`.github/workflows/ci.yml`** ```yaml title=".github/workflows/ci.yml" publish: name: Publish to PyPI needs: [compile, test, build] if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') runs-on: ubuntu-latest environment: name: pypi url: https://pypi.org/p/${{ github.event.repository.name }} permissions: contents: read # Required for checkout id-token: write # Required for OIDC steps: - name: Download all the dists uses: actions/download-artifact@v4 with: name: python-package-distributions path: dist/ - name: Publish to PyPI uses: pypa/gh-action-pypi-publish@release/v1 with: repository-url: https://upload.pypi.org/legacy/ ``` **Key changes from token-based publishing:** * Adds `environment` block pointing to `pypi` * Adds `permissions` with `id-token: write` and `contents: read` * Replaces `poetry publish` with `pypa/gh-action-pypi-publish@release/v1` * Removes all `env` blocks referencing `PYPI_TOKEN` or `PYPI_PASSWORD` #### (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 PYPI\_TOKEN secret After verifying the migration works, remove the `PYPI_TOKEN` secret from your GitHub repository settings. ### 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 provenance** by visiting your package on [PyPI](https://pypi.org/) — the package page displays attestation details for OIDC-published versions ### Migration troubleshooting #### "invalid-publisher" or authentication errors **Common causes:** * Workflow filename doesn't match exactly (must be `ci.yml` with the `.yml` extension) * Environment name on PyPI doesn't match the `pypi` environment in the workflow * Missing `id-token: write` or `contents: read` permissions * Using self-hosted runners (not supported) **Solution:** Double-check your trusted publisher configuration on PyPI matches your repository name, workflow filename, and environment name exactly. #### Workflow still using PYPI\_TOKEN If your workflow continues using token-based authentication: * Verify you've removed the `poetry publish` command and any `env` blocks referencing `PYPI_TOKEN` or `PYPI_PASSWORD` * Ensure the `publish` job uses `pypa/gh-action-pypi-publish@release/v1` * Ensure you're using generator version `4.38.1` or later (if using Path 1) * For `--local` generation, Fern CLI version `5.46.0` or later is required > Learn how to publish your Fern Python SDK to PyPI using OIDC or token-based authentication. Complete guide with GitHub Actions setup.