---
title: "How to Publish Python Packages to PyPI with GitHub Actions Trusted Publishers"
slug: "publish-to-pypi-guide"
published: "2025-08-17"
updated: "2025-08-13"
categories:
  - "Sanity"
tags:
  - "python"
  - "pypi"
  - "github-actions"
  - "packaging"
  - "trusted-publishers"
  - "deployment"
llm-intent: "reference"
audience-level: "intermediate"
framework-versions:
  - "python"
  - "pypi"
  - "github actions"
  - "pyproject.toml"
  - "openid connect"
status: "stable"
llm-purpose: "Learn how to publish Python packages to PyPI using GitHub Actions trusted publishers for secure, automated deployment without API tokens."
llm-prereqs:
  - "Access to Python"
  - "Access to PyPI"
  - "Access to GitHub Actions"
  - "Access to pyproject.toml"
  - "Access to OpenID Connect"
llm-outputs:
  - "Completed outcome: Learn how to publish Python packages to PyPI using GitHub Actions trusted publishers for secure, automated deployment without API tokens."
---

**Summary Triples**
- (pyproject.toml, replaces, setup.py for declarative build metadata)
- (build-system, requires, setuptools>=64 and wheel)
- (build-backend, is, setuptools.build_meta)
- (recommended-package-structure, includes, .github/workflows, your_package/, pyproject.toml, LICENSE, MANIFEST.in, README.md)
- (trusted-publishers, uses, GitHub OpenID Connect (OIDC) to authenticate to PyPI without API tokens)
- (publish-trigger, should-be, GitHub Actions workflow triggered on GitHub release)
- (trusted-publishers-benefit, eliminates, managing long-lived PyPI API tokens and secrets)
- (GitHub-Actions-workflow, must, build sdist and wheel and authenticate via OIDC for PyPI upload)
- (repository-permissions, required, ability to create releases and grant Trusted Publisher permissions in GitHub settings)
- (testing-local, recommended, build and upload test packages to TestPyPI or run python -m build locally)

### {GOAL}
Learn how to publish Python packages to PyPI using GitHub Actions trusted publishers for secure, automated deployment without API tokens.

### {PREREQS}
- Access to Python
- Access to PyPI
- Access to GitHub Actions
- Access to pyproject.toml
- Access to OpenID Connect

### {STEPS}
1. Follow the detailed walkthrough in the article content below.

<!-- llm:goal="Learn how to publish Python packages to PyPI using GitHub Actions trusted publishers for secure, automated deployment without API tokens." -->
<!-- llm:prereq="Access to Python" -->
<!-- llm:prereq="Access to PyPI" -->
<!-- llm:prereq="Access to GitHub Actions" -->
<!-- llm:prereq="Access to pyproject.toml" -->
<!-- llm:prereq="Access to OpenID Connect" -->
<!-- llm:output="Completed outcome: Learn how to publish Python packages to PyPI using GitHub Actions trusted publishers for secure, automated deployment without API tokens." -->

# How to Publish Python Packages to PyPI with GitHub Actions Trusted Publishers
> Learn how to publish Python packages to PyPI using GitHub Actions trusted publishers for secure, automated deployment without API tokens.
Matija Žiberna · 2025-08-17

I recently needed to publish my Python CLI tool `pgdock` to PyPI for the first time. After diving into the current Python packaging ecosystem, I discovered that the traditional approach of using API tokens has been superseded by something much better: trusted publishers with OpenID Connect authentication.

The old way required managing long-lived API tokens, which posed security risks and required manual credential management. The new trusted publisher approach eliminates these issues entirely by using GitHub's built-in authentication to securely publish packages without any secrets to manage.

This guide shows you the exact process I used to set up automated PyPI publishing that triggers on GitHub releases, using modern Python packaging standards and GitHub Actions.

## Setting Up Modern Python Package Structure

The foundation of PyPI publishing starts with proper package structure. Modern Python packaging has moved away from setup.py files in favor of declarative configuration using pyproject.toml.

First, organize your package with this structure:

```
your-package/
├── .github/workflows/
├── your_package/
│   ├── __init__.py
│   └── cli.py
├── pyproject.toml
├── LICENSE
├── MANIFEST.in
└── README.md
```

The pyproject.toml file is where you define all your package metadata and build configuration. This replaces the old setup.py approach with a more standardized format that all modern Python tools understand.

Create your pyproject.toml file with the essential configuration:

```toml
# File: pyproject.toml
[build-system]
requires = ["setuptools>=64", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "your-package-name"
version = "1.0.0"
authors = [
    {name = "Your Name", email = "your-email@example.com"},
]
description = "A concise description of what your package does"
readme = "README.md"
requires-python = ">=3.10"
classifiers = [
    "Development Status :: 4 - Beta",
    "Intended Audience :: Developers", 
    "License :: OSI Approved :: MIT License",
    "Operating System :: OS Independent",
    "Programming Language :: Python :: 3",
    "Topic :: Software Development :: Libraries :: Python Modules",
]
keywords = ["python", "cli", "tool"]
dependencies = [
    "typer>=0.12.0",
    "rich>=13.0.0",
]

[project.urls]
Homepage = "https://github.com/yourusername/your-package"
Repository = "https://github.com/yourusername/your-package"
Issues = "https://github.com/yourusername/your-package/issues"

[project.scripts]
your-command = "your_package.cli:main"
```

This configuration defines everything PyPI needs to know about your package. The build-system section tells tools like `pip` and `build` how to create your package distributions. The project section contains all the metadata that appears on your PyPI page.

If your package includes non-Python files like templates or configuration files, create a MANIFEST.in file to ensure they're included in the distribution:

```
# File: MANIFEST.in
include README.md
include LICENSE
recursive-include your_package/templates *.j2
recursive-exclude * __pycache__
recursive-exclude * *.py[co]
```

## Creating the GitHub Actions Workflow

GitHub Actions provides the automation layer that handles building and publishing your package. The key advantage of using GitHub Actions with trusted publishers is that you don't need to manage any secrets or API tokens.

Create the workflow file that will handle your package publishing:

```yaml
# File: .github/workflows/pypi-publish.yml
name: Publish Python Package

on:
  release:
    types: [published]

jobs:
  deploy:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Set up Python
      uses: actions/setup-python@v5
      with:
        python-version: '3.x'
        
    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install build
        
    - name: Build package
      run: python -m build
      
    - name: Publish package
      uses: pypa/gh-action-pypi-publish@release/v1
```

The critical part here is the `permissions` section with `id-token: write`. This enables GitHub's OpenID Connect tokens, which the trusted publisher system uses for authentication. The workflow triggers only on published releases, ensuring you have control over when packages get published.

The `python -m build` command creates both a source distribution (.tar.gz) and a wheel (.whl) file. Modern pip installations prefer wheels for faster installation, but source distributions provide fallback compatibility.

## Configuring PyPI Trusted Publisher

The trusted publisher setup on PyPI is what makes the magic happen. Instead of using API tokens, PyPI trusts GitHub to authenticate your publishing requests using OpenID Connect.

Navigate to your PyPI account's publishing settings at https://pypi.org/manage/account/publishing/ and add a new pending publisher with these details:

- **PyPI Project Name**: your-package-name (exactly as in pyproject.toml)
- **Owner**: your-github-username  
- **Repository name**: your-repository-name
- **Workflow name**: pypi-publish.yml
- **Environment name**: (leave empty or set to "Any")

The "pending publisher" approach is particularly useful because it allows you to configure the trusted relationship before your package exists on PyPI. Once you publish for the first time, the pending publisher automatically becomes an active publisher for that project.

This configuration creates a secure channel between your specific GitHub repository and your PyPI project. PyPI will only accept packages from GitHub Actions runs that match these exact parameters.

## Testing the Package Build Process

Before triggering your first PyPI publish, verify that your package builds correctly locally. This catches configuration issues early and ensures your workflow will succeed.

Install the build tool and create your distributions:

```bash
pip install build
python -m build
```

This command creates a `dist/` directory containing your package files:

```
dist/
├── your-package-1.0.0-py3-none-any.whl
└── your-package-1.0.0.tar.gz
```

You can test install your package locally to verify it works:

```bash
pip install dist/your-package-1.0.0-py3-none-any.whl
your-command --help
```

This local testing step is crucial because it catches issues like missing dependencies, incorrect entry points, or packaging problems before they reach PyPI.

## Publishing Your First Release

With everything configured, publishing becomes as simple as creating a GitHub release. The workflow you created will automatically trigger and handle the entire publishing process.

Go to your GitHub repository's releases page and create a new release:

- **Tag version**: v1.0.0 (following semantic versioning)
- **Release title**: Package Name v1.0.0
- **Description**: Describe what's new in this release

When you publish the release, GitHub triggers your workflow, which builds the package and publishes it to PyPI using the trusted publisher authentication you configured.

You can monitor the publishing process in your repository's Actions tab. The workflow will show each step, from building the package to the final PyPI upload. If successful, your package becomes immediately available for installation worldwide.

## Monitoring and Maintaining Your Published Package

After your first successful publish, your package is live on PyPI and users can install it with `pip install your-package-name`. The trusted publisher relationship is now active, and future releases will follow the same automated process.

For subsequent releases, simply update the version number in your pyproject.toml file, commit your changes, and create a new GitHub release. The workflow handles everything else automatically.

You can add badges to your README to show the current PyPI version and build status:

```markdown
[![PyPI version](https://badge.fury.io/py/your-package.svg)](https://badge.fury.io/py/your-package)
[![Publish to PyPI](https://github.com/username/repo/actions/workflows/pypi-publish.yml/badge.svg)](https://github.com/username/repo/actions/workflows/pypi-publish.yml)
```

The trusted publisher approach provides several advantages over traditional API tokens: it's more secure since there are no long-lived credentials to manage, it provides better audit trails through GitHub's logging, and it integrates seamlessly with your existing development workflow.

## Conclusion

Setting up automated PyPI publishing with GitHub Actions trusted publishers eliminates the security risks and management overhead of API tokens while providing a streamlined publishing experience. You now have a modern, secure pipeline that publishes your Python packages automatically when you create GitHub releases.

The combination of declarative package configuration in pyproject.toml, automated building and publishing through GitHub Actions, and secure authentication via trusted publishers represents the current best practice for Python package distribution. This approach scales well as your project grows and provides the foundation for more advanced publishing workflows if needed.

Let me know in the comments if you have questions about implementing this setup, and subscribe for more practical development guides.

Thanks, Matija

## LLM Response Snippet
```json
{
  "goal": "Learn how to publish Python packages to PyPI using GitHub Actions trusted publishers for secure, automated deployment without API tokens.",
  "responses": [
    {
      "question": "What does the article \"How to Publish Python Packages to PyPI with GitHub Actions Trusted Publishers\" cover?",
      "answer": "Learn how to publish Python packages to PyPI using GitHub Actions trusted publishers for secure, automated deployment without API tokens."
    }
  ]
}
```