Publishing to PyPI
Package and publish a Python plugin to PyPI.
Publishing puts your plugin on PyPI so anyone can install it with pip. There are two ways to do it: let GitHub Actions publish automatically from your repository, or build and upload from your own machine.
Publish from a GitHub repository
The recommended setup publishes straight from your repo with PyPI trusted publishing - GitHub authenticates to PyPI over OIDC, so there are no API tokens to create or store.
The example plugin ships a ready-made release.yml workflow. Triggered from the repo's Actions tab with a version number, it bumps the changelog, tags the release, builds the wheel, and publishes it to PyPI. Trusted publishing hinges on the publishing job:
publish:
runs-on: ubuntu-latest
environment: pypi
permissions:
id-token: write # mint the OIDC token PyPI trusts
steps:
- uses: actions/checkout@v6
- uses: astral-sh/setup-uv@v7
- run: uv build
- uses: pypa/gh-action-pypi-publish@release/v1Add a trusted publisher on PyPI
Before the workflow can publish, tell PyPI to trust it:
- Sign in to PyPI and open your publishing settings.
- Under Add a new pending publisher, choose GitHub and fill in:
- PyPI Project Name - the
namefrom yourpyproject.toml(e.g.endstone-my-plugin). - Owner - your GitHub user or organisation (e.g.
EndstoneMC). - Repository name - your plugin's repository.
- Workflow name -
release.yml. - Environment name -
pypi(must matchenvironment:in the job above).
- PyPI Project Name - the
- Click Add.
Use a pending publisher when the project doesn't exist on PyPI yet - your first successful run creates it. For a project that already exists, add the publisher from its Settings → Publishing page instead.
With the publisher registered, run the release workflow from the Actions tab. PyPI checks that the request came from that exact repository, workflow, and environment, then accepts the upload - and the wheel appears at https://pypi.org/project/<package-name>.
Publish from your local machine
If you'd rather publish by hand, build the package and upload it with Twine.
Build
uv buildThis produces a source distribution and a wheel in dist/. Without uv, pipx run build does the same.
Test on TestPyPI first
TestPyPI is a separate copy of the index for rehearsing the upload without touching the real one. It has its own database, so register a separate account at test.pypi.org/account/register, then upload:
uvx twine upload -r testpypi dist/*Upload to PyPI
When it looks right on TestPyPI, upload to the real index:
uvx twine upload -r pypi dist/*Your plugin then appears at https://pypi.org/project/<package-name>.