Skip to content

How to Publish a Python Package to conda-forge

Your package is on PyPI, so pip install and uv add work. Scientific and machine-learning users reach for conda install instead, and that pulls from conda-forge, the community channel that carries over 25,000 packages.

Getting onto conda-forge means submitting a build recipe once. After it merges, conda-forge’s bots create a feedstock repository for your package and keep it updated as you publish new PyPI releases. This guide walks through that first submission.

Prerequisites

Install grayskull

grayskull reads a package’s PyPI metadata and writes a conda recipe. Install it as a standalone command-line tool:

uv tool install grayskull

Both put a grayskull binary on your PATH. Confirm it runs:

$ grayskull --version
3.1.1

Fork and clone staged-recipes

Every new package enters conda-forge through one repository, conda-forge/staged-recipes. Fork it, clone your fork, and create a branch named after your package:

gh repo fork conda-forge/staged-recipes --clone
cd staged-recipes
git checkout -b humanize

If you don’t use the GitHub CLI, fork the repository in the web UI, then git clone your fork manually.

Generate the recipe

From the recipes/ directory, point grayskull at your package’s name on PyPI:

cd recipes
grayskull pypi --strict-conda-forge humanize

grayskull downloads the sdist, reads its metadata, and writes recipes/humanize/meta.yaml. The --strict-conda-forge flag drops platform selectors that conda-forge doesn’t need. The result for a small pure-Python package looks like this:

recipes/humanize/meta.yaml
{% set python_min = "3.10" %}
{% set version = "4.15.0" %}

package:
  name: humanize
  version: {{ version }}

source:
  url: https://pypi.org/packages/source/h/humanize/humanize-{{ version }}.tar.gz
  sha256: 1dd098483eb1c7ee8e32eb2e99ad1910baefa4b75c3aff3a82f4d78688993b10

build:
  noarch: python
  script: {{ PYTHON }} -m pip install . -vv --no-deps --no-build-isolation
  number: 0

requirements:
  host:
    - python {{ python_min }}.*
    - hatch-vcs
    - hatchling >=1.27
    - pip
  run:
    - python >={{ python_min }}

test:
  imports:
    - humanize
  commands:
    - pip check
  requires:
    - pip
    - python {{ python_min }}.*

about:
  home: https://github.com/python-humanize/humanize
  summary: Python humanize utilities
  license: MIT
  license_file: PLEASE_ADD_LICENSE_FILE

extra:
  recipe-maintainers:
    - AddYourGitHubIdHere

If grayskull stops with There is no sdist package on pypi for <name>, your release shipped only wheels. conda-forge builds from source, so run uv build to produce an sdist, upload it to PyPI, and try again.

Note

You might see two recipe formats mentioned online: the older meta.yaml (shown here) and a newer recipe.yaml. Both build the same package. Use meta.yaml; it’s what grayskull writes by default and what nearly every conda-forge package uses.

Fill in what grayskull leaves blank

grayskull gets most fields right but leaves two that fail CI if you ship them as-is:

  • license_file: grayskull writes PLEASE_ADD_LICENSE_FILE when it can’t locate the license inside the sdist. Replace it with the license file’s name as packaged, for example LICENSE or LICENSE.txt. The recipe linter rejects the missing value.
  • recipe-maintainers: grayskull fills in the GitHub username it detects from your environment, or the literal placeholder AddYourGitHubIdHere when it finds none. Set it to your GitHub handle and add any co-maintainers.

The sha256 line is computed from the sdist grayskull downloaded. If the hash later stops matching (PyPI returns a fresh checksum when a maintainer re-uploads a release), re-run grayskull to regenerate it rather than editing the digit string by hand.

Open the pull request

Commit the recipe and push it to your fork:

git add recipes/humanize/meta.yaml
git commit -m "Add humanize recipe"
git push -u origin humanize

Open a pull request against conda-forge/staged-recipes. The PR template includes a checklist covering the fields you just edited.

Get the recipe reviewed and merged

Opening the PR triggers conda-forge’s CI, which builds your package on Linux, macOS, and Windows and runs a recipe linter. For a noarch: python package, only the Linux build must pass; the macOS and Windows builds are expected to skip. The test: section grayskull generated imports your package and runs pip check inside the freshly built environment.

When the checks are green, request a reviewer by commenting on the PR:

@conda-forge-admin, please ping team

If pip check fails with <package> requires <dep> which is not installed, a runtime dependency is missing from the run: list. Add it, push again, and the check reruns. Reviewers are volunteers, so expect a few days for a response.

Inherit your new feedstock

When a maintainer merges the PR, conda-forge’s bots create a dedicated repository, humanize-feedstock, and add you as a maintainer. The package builds within an hour or two and appears on the channel:

conda install -c conda-forge humanize   # or: pixi add humanize

From this point the feedstock, not staged-recipes, holds the recipe. Edits go to the feedstock repository.

Let the bots ship future releases

You don’t repeat this process for every release. regro-cf-autotick-bot watches PyPI and, within hours of a new version, opens a pull request on your feedstock that bumps the version and sha256. Review it, wait for CI, and merge. When a release adds or drops a dependency, edit the requirements: section in that same PR before merging.

Learn More

Last updated on