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:

.github/workflows/release.yml
  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/v1

Add a trusted publisher on PyPI

Before the workflow can publish, tell PyPI to trust it:

  1. Sign in to PyPI and open your publishing settings.
  2. Under Add a new pending publisher, choose GitHub and fill in:
    • PyPI Project Name - the name from your pyproject.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 match environment: in the job above).
  3. 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 build

This 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/*

For manual uploads, create an API token rather than using your password - on both PyPI and TestPyPI. An unrestricted token is fine to start.

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>.

On this page