Hatch brings environments, scripts, versioning, and package builds together. Configuration lives in pyproject.toml, documenting tasks without relying on a collection of informal commands.

Configure a project environment

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "example-app"
version = "0.1.0"
requires-python = ">=3.12"

[tool.hatch.envs.test]
dependencies = ["pytest"]

[tool.hatch.envs.test.scripts]
run = "pytest -q {args:tests}"

After installing Hatch as a user tool, run hatch run test:run. Hatch creates the isolated environment and accepts replacement arguments from the command line.

Separate responsibilities

Hatchling builds distributions; the Hatch CLI orchestrates project tasks. This distinction allows a package to use Hatchling without forcing consumers to install Hatch. Before publishing, run hatch build, inspect the wheel and sdist, and install those artifacts in a clean environment.

The pyproject.toml guide explains metadata, while uv for Python projects offers another workflow. Do not combine overlapping tools without deciding which owns locking, environments, and builds.

The official Hatch documentation, accessed July 22, 2026, covers environments, scripts, and builds. Pin CI tools and protect publishing credentials with review and least privilege.

Separate Hatch from Hatchling

Hatch is the project tool that creates environments, runs scripts, updates versions, and starts builds. Hatchling is a standards-compatible build backend. build-backend = "hatchling.build" tells tools such as python -m build how to produce a wheel and sdist; it does not require contributors or consumers to use the hatch command.

This distinction prevents unnecessary coupling. A team can standardize tasks with Hatch while producing artifacts installable by pip. It can also use Hatchling with another environment tool. Assign one owner to each responsibility and document the choice.

Declare metadata and package layout

For a library stored under src/example, declare the wheel packages explicitly:

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "example-utils"
version = "0.1.0"
description = "Utilities for the Example project"
readme = "README.md"
requires-python = ">=3.11"
dependencies = ["httpx>=0.27,<1"]

[tool.hatch.build.targets.wheel]
packages = ["src/example"]

A distribution name may contain a hyphen while the import package uses a Python identifier. Verify that the wheel contains modules, typing information, and required data files. Do not accidentally ship tests, secrets, or local files.

Organize environments by purpose

Independent environments prevent documentation tools from leaking into the runtime:

[tool.hatch.envs.test]
dependencies = [
  "pytest",
  "pytest-cov",
]

[tool.hatch.envs.test.scripts]
run = "pytest {args:tests}"
cov = "pytest --cov=example --cov-report=term-missing {args:tests}"

[tool.hatch.envs.docs]
dependencies = ["sphinx"]

[tool.hatch.envs.docs.scripts]
build = "sphinx-build -W docs build/docs"

Run hatch run test:run and optionally pass a path. Short scripts provide a stable interface to developers and CI. Do not hide complicated logic in one TOML line; move longer flows into a testable Python module.

Test multiple Python versions

Matrices verify the compatibility claim:

[tool.hatch.envs.test]
matrix-name-format = "py{value}"

[[tool.hatch.envs.test.matrix]]
python = ["3.11", "3.12", "3.13"]

Run only versions the project actually supports. If an interpreter is absent, install it deliberately or configure an appropriate provider. A skipped run does not prove compatibility. Keep the CI matrix visible so the failing version is obvious.

Manage versions from one source

Hatch can read the version from a configured file or source. One source prevents drift among package code, metadata, and documentation. Before automating increments, decide whether the project follows semantic versioning and who authorizes releases.

Avoid deriving versions from Git state without considering builds outside the repository. Artifacts need deterministic versions, and rebuilding the same commit should produce equivalent metadata.

Build and inspect artifacts

hatch build creates a wheel and sdist in dist/. Do not publish immediately. List wheel contents, extract the sdist into a temporary directory, and install both in clean environments. At minimum, test an import and invoke --help for a CLI.

hatch build
python -m pip install dist/example_utils-0.1.0-py3-none-any.whl
python -c "import example; print(example.__name__)"

The exact filename depends on metadata. In automation, discover it in a controlled way and fail if zero or multiple unexpected candidates exist.

Keep publishing behind a boundary

Building and publishing are separate stages. The first can run on every pull request; the second should require a protected tag or approval. Use least-privilege tokens and trusted publishing where the package index supports it. Never print credentials in logs.

Start with a test index while validating the workflow. Check the rendered name, version, description, files, and dependencies. Package indexes generally do not let publishers replace an existing version, so a mistake requires a new version under the index policy.

Adopt Hatch without confusing the team

Document essential README commands for tests, documentation, and builds. Make CI invoke the same scripts. Pin the Hatch version used by automation or define an explicit upgrade policy.

Hatch is most useful when it replaces divergent scripts with understandable configuration. It does not remove the need to review dependencies, test artifacts, and protect releases. A predictable project states which tool resolves dependencies, which creates environments, which builds, and who may publish.