- How to Publish Python Packages to PyPI with GitHub Actions Trusted Publishers
How to Publish Python Packages to PyPI with GitHub Actions Trusted Publishers
Secure, automated PyPI publishing without managing API tokens

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:
# 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:
# 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:
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:
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:
[](https://badge.fury.io/py/your-package)
[](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