Poetry Dependency Groups vs Extras: Choosing the Right Mechanism for Optional Dependencies
Poetry 1.2+ dependency groups keep dev tooling out of published wheels, while extras remain the only way to let downstream users select optional features via pip. Use both: groups for test/lint/docs, extras for public feature flags like [redis] or [async].
27 Feb 2026, 17:32 UTC

The Decision: Internal Tooling vs Public Features
Poetry 1.2 introduced tool.poetry.group as a native way to organize dependencies that should never reach PyPI. The older tool.poetry.extras mechanism remains the only path for optional features that downstream consumers select at install time via pip install pkg[feature]. The choice isn't mutually exclusive—most projects need both—but putting a dependency in the wrong category either bloats your published metadata or breaks consumer installs.
Quick Comparison
| Aspect | Dependency Groups | Extras |
|---|---|---|
| Published to PyPI metadata | No | Yes (Provides-Extra) |
Installable via pip install pkg[extra] | No | Yes |
| Poetry install command | poetry install --with group / --only group | poetry install --extras extra |
| Typical use cases | test, lint, docs, type-checking, dev tooling | redis, postgres, s3, cli, async |
Visible in poetry export by default | No (requires --with) | Yes (with --extras) |
| Version introduced | Poetry 1.2.0 (2022) | Poetry 1.0 |
Trade-offs in Practice
Groups keep your wheel clean
When you run poetry build, only [tool.poetry.dependencies] and [tool.poetry.extras] appear in the generated METADATA / PKG-INFO. A [tool.poetry.group.test] containing pytest, hypothesis, and pytest-cov never reaches end users. This avoids the classic problem where pip install yourpkg pulls in test runners because they were listed in a dev extra.
Extras are the contract with consumers
If your library offers optional Redis support, declare it as an extra:
[tool.poetry.extras]
redis = ["redis-py"]
Consumers then run pip install yourpkg[redis] or poetry add yourpkg[redis] and get the right transitive dependencies. Groups cannot fulfill this role—pip doesn't understand them.
Mixing both is the normal pattern
A typical pyproject.toml ends up with three layers:
[tool.poetry.dependencies]
python = "^3.10"
requests = "^2.31"
[tool.poetry.group.test]
pytest = "^7.4"
pytest-cov = "^4.1"
[tool.poetry.group.lint]
ruff = "^0.1"
mypy = "^1.7"
[tool.poetry.extras]
redis = ["redis-py"]
async = ["aiohttp"]
Main dependencies are always installed. Groups are opt-in for contributors. Extras are opt-in for users.
Concrete Validation Steps
Create a throwaway project to verify the behavior before committing to a structure:
poetry new demo && cd demo- Edit
pyproject.tomlto add the group and extra sections shown above. poetry lock— inspectpoetry.lock; you'll see[metadata.groups]and[metadata.extras]sections recording each.poetry install --only mainthenpoetry show— confirmpytest,ruff,redis-py, andaiohttpare absent.poetry install --with test,lint— verify dev tools appear.poetry install --extras redis— verifyredis-pyappears.poetry build && pip install dist/*.whl[redis]in a clean venv — confirm the extra works throughpip.
Common Pitfalls
- Putting runtime deps in groups only. If
redis-pylives only in[tool.poetry.group.redis],pip install yourpkg[redis]will fail because the extra is empty. Runtime-optional deps must be in an extra (or main deps). - Exporting requirements.txt without groups.
poetry export -f requirements.txtomits groups by default. CI jobs that consume the exported file needpoetry export --with test,lint -f requirements.txt. - Version conflicts across groups/extras. Poetry resolves a single version per package across all selected groups and extras. If
testrequiresrequests<3andasyncextra requiresrequests>=3, resolution fails when both are selected. - Older Poetry versions. Teams pinned to Poetry <1.2 will ignore
[tool.poetry.group]sections entirely. Upgrade or keep dev deps in adevextra as a fallback.
Migration Checklist
Moving from a single dev extra to groups:
- Create
[tool.poetry.group.test],[tool.poetry.group.lint], etc., and move dependencies out ofextras.dev. - Delete or shrink
extras.dev. - Update CI: replace
poetry install --extras devwithpoetry install --with test,lint,docs. - Update contributor docs:
poetry install --with test,lintfor full dev env,poetry install --only mainfor minimal. - Run
poetry checkandpoetry lock --no-updateto verify syntax and lock consistency.
Limitations to Accept
- Groups are Poetry-only. Any workflow that uses
pipdirectly (includingpip install -e .without Poetry) cannot install group dependencies. - No per-group source selection. Private repository credentials in
[tool.poetry.source]apply globally. poetry exportcannot produce multiple requirements files for different group combinations in one pass; you must run it repeatedly with different--withflags.
Verification Checklist Before Merge
poetry checkpasses (syntax valid).poetry lock --no-updatesucceeds (lock file consistent).poetry install --only maininstalls only runtime deps.poetry install --with test,lintinstalls dev tooling.poetry install --extras redis,asyncinstalls optional features.poetry build && pip install dist/*.whl[redis]works in a clean venv.- Exported
requirements.txt(with--withflags) matches CI expectations.
If all seven pass, your group/extra split is sound and won't surprise contributors or consumers.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.