Poetry Dependency Groups vs Extras: A Decision Guide for Tooling and Optional Features
Choose Poetry dependency groups for internal tooling and extras for consumer-facing optional features. Compare the trade-offs, see a concrete pyproject.toml layout, and validate the result.
23 Sept 2026, 04:45 UTC

The decision: internal tooling or consumer feature?
When you add a dependency to a Poetry project, the first question is not whether it is for development or production. It is who must be able to install it. If only your team and CI need it, it belongs in a dependency group. If a downstream user might need it to unlock an optional feature, it belongs in an extra. The legacy [tool.poetry.dev-dependencies] section is a migration concern, not a design choice for new projects.
This guide assumes Poetry 1.2 or newer. Poetry 2.0 removed dev-dependencies, so projects on 2.x must use groups. If you are on an older 1.x release, groups may not be available; check poetry --version before changing layout.
Supported mechanisms at a glance
| Mechanism | Declaration | Audience | Install control | Best for |
|---|---|---|---|---|
| Dependency group | [tool.poetry.group.<name>.dependencies] | Project maintainers and CI | poetry install --with <name>, --only <name>, --without <name> | Internal tooling: test, lint, docs, type checking |
| Extra | [tool.poetry.extras] | Downstream consumers | pip install your-package[extra] | Optional runtime features: PDF export, database driver, cloud backend |
| Legacy dev-dependencies | [tool.poetry.dev-dependencies] | Project maintainers | poetry install (Poetry 1.x only) | Migration only; removed in Poetry 2.0 |
Trade-offs that affect packaging and CI
Groups keep consumer metadata clean. A group named test or docs does not appear as an installable feature in the package metadata. Extras do appear: they become part of your public API through Provides-Extra and Requires-Dist markers. Renaming an extra or removing it is a breaking change for consumers.
Groups also affect resolution. Poetry resolves all groups together by default, so a version conflict between dev and test is caught when you lock. If you install with --only main,test, Poetry skips resolution for omitted groups; a conflict between test and docs may not surface until CI runs the omitted group. The --only flag also excludes the implicit main group unless you list it explicitly, so --only test is usually not what you want.
Using extras for development tools such as pytest is a common mistake. It forces consumers to see test tooling as an optional runtime feature and can bloat package metadata. Conversely, putting an optional runtime feature in a group makes it invisible to pip install your-package[feature].
Groups and extras can coexist. A typical library uses groups for its own workflow and one or more extras for optional runtime capabilities.
Concrete layout: groups for tooling, extras for optional runtime
The following pyproject.toml fragment shows a project with a required runtime dependency, an optional PDF extra, a default dev group, and an optional docs group. Placeholders such as package name and versions should be replaced with your own.
[tool.poetry]
name = "reportkit"
version = "0.1.0"
description = "Example package with optional PDF export"
authors = ["Maintainer <[contact removed]>"]
packages = [{ include = "reportkit" }]
[tool.poetry.dependencies]
python = "^3.10"
requests = "^2.31"
reportlab = { version = "^4.0", optional = true }
[tool.poetry.extras]
pdf = ["reportlab"]
[tool.poetry.group.dev.dependencies]
pytest = "^8.0"
ruff = "^0.5"
[tool.poetry.group.docs]
optional = true
[tool.poetry.group.docs.dependencies]
sphinx = "^7.0"
In this layout, reportlab is declared as optional in the main dependencies and then exposed through the pdf extra. The dev group is a normal group; the docs group is marked optional = true so it is not installed by default. Whether a group is installed by default depends on the optional flag and the Poetry version, so verify with the commands below.
Commands to run and what to check
Run these from the project root in a shell with permission to write the virtual environment and lock file. Network access is required when Poetry needs to download packages.
poetry --version— confirm 1.2+ or 2.x. If the output is 1.x and the project still has[tool.poetry.dev-dependencies], plan the migration before upgrading.poetry check— validates thepyproject.tomlstructure. On Poetry 2.x, a legacydev-dependenciessection is expected to be rejected; treat that as a signal to migrate.poetry install --with docs— installs the default groups plus the optionaldocsgroup. Check that Sphinx is importable in the Poetry environment.poetry install --only main,test— installs only the main andtestgroups. Check thatdocsanddevpackages are absent. This is a useful CI smoke test for selective installation.poetry build— builds the wheel and sdist. Inspect the wheel metadata forProvides-Extra: pdfand aRequires-Distline forreportlabwith the extra marker. The exact metadata path can vary by build backend version; useunzip -l dist/*.whlto locateMETADATA.- In a clean virtual environment, run
pip install dist/reportkit-0.1.0-py3-none-any.whl[pdf]and check thatreportlabis installed. This validates the consumer-facing extra rather than the Poetry group.
Do not treat any of these commands as producing a fixed output. The expected checks are structural: the version is in range, poetry check exits successfully, the selected groups appear in the environment, and the built metadata contains the extra.
Rollback and limitations
Changing pyproject.toml and poetry.lock changes the environment. If you need to roll back, restore both files from version control and run poetry install --sync to make the virtual environment match the restored lock file. --sync removes packages that are no longer in the lock file, so review the diff before running it in a shared environment.
Limitations to keep in mind:
- Poetry 2.0 removed
[tool.poetry.dev-dependencies]. A project that still uses it will not work on 2.x without migration. - Group installation defaults and the
optionalflag have changed across Poetry releases. Confirm behavior against the exact version you run. --onlyskips resolution for omitted groups, so cross-group conflicts can hide until CI.- Extras are public packaging metadata. Treat their names and contents as a compatibility surface.
This draft is conservative because Poetry's group defaults and version behavior require current verification. Before publishing or applying it to a production repository, run the version check and the selective install commands in a disposable environment.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.