# How to Publish to PyPI with Trusted Publishing


Trusted publishing lets GitHub Actions upload packages to [PyPI](https://pydevtools.com/handbook/explanation/what-is-pypi.md) without storing any secrets. Instead of creating an API token and pasting it into your CI settings, you tell PyPI which GitHub repository and workflow are allowed to publish. GitHub Actions then proves its identity to PyPI using an OIDC token that lives only for the duration of the workflow run.

This guide covers GitHub Actions, the most common CI provider for Python projects. PyPI also supports trusted publishing from GitLab CI/CD, Google Cloud, and ActiveState.

## Prerequisites

* A [PyPI](https://pypi.org) account
* A GitHub repository containing a Python package with a [`pyproject.toml`](https://pydevtools.com/handbook/reference/pyproject.toml.md)
* [uv](https://pydevtools.com/handbook/reference/uv.md) installed ([installation guide](https://docs.astral.sh/uv/getting-started/installation/))

## Configure PyPI to trust your repository

### Log in to PyPI

Go to [pypi.org](https://pypi.org) and sign in.

### Open the trusted publisher settings

For an existing project: Navigate to your project, go to Settings → Publishing, and scroll to Add a new publisher.

For a new project that has never been uploaded: Go to your [account publishing settings](https://pypi.org/manage/account/publishing/) and scroll to Create a new pending publisher. Enter the package name exactly as it will appear on PyPI.

### Fill in the GitHub details

Enter the following:

| Field | Value |
|---|---|
| Owner | Your GitHub username or organization |
| Repository name | The repository that will publish the package |
| Workflow name | The filename of the workflow, e.g. `publish.yml` |
| Environment name | Optional. If your workflow uses a GitHub Actions environment, enter its name here. |

### Save

Click **Add**. PyPI will now accept uploads from that specific workflow.

## Create the publish workflow

Create `.github/workflows/publish.yml` in your repository:

```yaml
name: Publish to PyPI

on:
  release:
    types: [published]

jobs:
  publish:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
    environment:
      name: pypi
      url: https://pypi.org/p/<YOUR_PACKAGE_NAME>
    steps:
      - uses: actions/checkout@v6
      - uses: astral-sh/setup-uv@v7
      - run: uv build
      - run: uv publish
```

A few things to note:

- `permissions: id-token: write` is required. This allows the workflow to request an [OIDC token from GitHub's token service](https://docs.github.com/en/actions/security-for-github-actions/security-hardening-your-deployments/about-security-hardening-with-openid-connect). Without it, `uv publish` has no credential to present to PyPI.
- `on: release` triggers the workflow when you create a GitHub release. You can also trigger on tag pushes (`on: push: tags: ["v*"]`).
- `uv publish` detects the GitHub Actions environment automatically, requests an OIDC token, exchanges it with PyPI for a short-lived upload credential, and publishes. No `--token` flag is needed.
- The `environment` block is optional but recommended. [GitHub environments](https://docs.github.com/en/actions/managing-workflow-runs-and-deployments/managing-deployments/managing-environments-for-deployment) let you add required reviewers, restrict which branches can deploy, and see deployment history. If you specify an environment here, use the same environment name in your PyPI trusted publisher configuration.

## Publish a release

### Tag your release

```console
$ git tag v0.1.0
$ git push origin v0.1.0
```
```console
$ git tag v0.1.0
$ git push origin v0.1.0
```
### Create a GitHub release

Go to your repository on GitHub, click **Releases** → **Draft a new release**, select the tag you just pushed, and click **Publish release**.

### Watch the workflow

Open the **Actions** tab. The publish workflow should start. If everything is configured correctly, `uv publish` will authenticate via OIDC, upload the built distributions, and the job will complete with a green checkmark.

## Verify the upload

On your package's PyPI page, the upload should show provenance information linking back to the specific GitHub Actions workflow run that produced it. This provenance attestation lets anyone verify that the package was built from the source in your repository.

## Revoke old API tokens

If you were previously using an API token to publish, go to your [PyPI account settings](https://pypi.org/manage/account/) and delete the token. There is no reason to keep a long-lived secret around once trusted publishing is working.

## Troubleshooting

"Token request failed" or 403 errors: Check that `permissions: id-token: write` is set on the job or workflow. Verify that the workflow filename in your PyPI publisher configuration matches the actual file in `.github/workflows/`.

Environment mismatch: If you configured an environment name in PyPI, the workflow must use that same environment. If you left the environment field blank in PyPI, do not set an environment in the workflow.

Pending publisher not matching: The package name in the pending publisher configuration must exactly match the `name` field in your `pyproject.toml` (PyPI normalizes names, so hyphens and underscores are equivalent).

Reusable workflows: PyPI does not currently support reusable GitHub Actions workflows as the trusted workflow. The workflow file that calls `uv publish` must be defined directly in your repository.

## Learn more

* [PyPI Trusted Publishers documentation](https://docs.pypi.org/trusted-publishers/)
* [uv publishing guide](https://docs.astral.sh/uv/guides/publish/)
* [GitHub Actions OIDC security hardening](https://docs.github.com/en/actions/security-for-github-actions/security-hardening-your-deployments/about-security-hardening-with-openid-connect)
* [Why use trusted publishing?](https://pydevtools.com/handbook/explanation/why-use-trusted-publishing-for-pypi.md)
* [How to publish Python packages with digital attestations](https://pydevtools.com/handbook/how-to/how-to-publish-python-packages-with-digital-attestations.md)
* [Setting up GitHub Actions with uv](https://pydevtools.com/handbook/tutorial/setting-up-github-actions-with-uv.md)
* [Publishing your first Python package to PyPI](https://pydevtools.com/handbook/tutorial/publishing-your-first-python-package-to-pypi.md)
