Skip to content

How to migrate from Poetry to uv

Poetry and uv both manage dependencies and virtual environments from a single tool, but uv adds Python version management, faster resolution, and a standards-first pyproject.toml layout. Migrating rewrites Poetry’s [tool.poetry] tables into PEP 621 project metadata, replaces poetry.lock with uv.lock, and swaps poetry commands for their uv equivalents.

One command handles most of the work. The rest of this guide shows what converts automatically, what needs manual cleanup, and how to fix the cases that trip people up.

Prerequisites

Convert the project with migrate-to-uv

migrate-to-uv reads a Poetry project and rewrites it for uv in place. Run it with uvx, which fetches and executes the tool without a permanent install.

1. Preview the changes

$ uvx migrate-to-uv --dry-run

--dry-run prints the converted pyproject.toml without touching any files. Read it before committing to the migration.

2. Run the migration

$ uvx migrate-to-uv
Successfully migrated project from Poetry to uv!

The tool rewrites pyproject.toml, generates uv.lock from the versions already pinned in poetry.lock, and removes the Poetry-specific files.

3. Verify the result

$ uv sync
$ uv run pytest

uv sync installs the locked dependencies into .venv. If both commands succeed, the migration worked.

What migrate-to-uv converts automatically

For a Poetry project with dependencies, a dev group, extras, a private index, markers, and a console script, migrate-to-uv produces a complete PEP 621 pyproject.toml:

Poetry uv
[tool.poetry] metadata [project] metadata (PEP 621)
authors = ["Jane <jane@x.com>"] authors = [{ name = "Jane", email = "jane@x.com" }]
python = "^3.11" requires-python = ">=3.11,<4"
requests = "^2.31" requests>=2.31,<3
httpx = { extras = ["http2"] } httpx[http2]
[tool.poetry.group.dev.dependencies] [dependency-groups] dev
[tool.poetry.extras] [project.optional-dependencies]
[tool.poetry.scripts] [project.scripts]
[[tool.poetry.source]] [[tool.uv.index]] + [tool.uv.sources]

Caret (^) and tilde (~) constraints become explicit PEP 440 ranges, and dependency markers like sys_platform == 'win32' carry over unchanged.

Check what needs manual cleanup

Three conversions are lossy or surprising. Review each before pushing the change.

  • Build backend. By default the tool replaces poetry-core with uv_build and adds a [tool.uv.build-backend] section. For an application this is fine, but for a package you distribute to PyPI, the new backend may include or exclude different files. Pass --keep-current-build-backend to leave [build-system] untouched, or inspect the generated wheel before publishing.
  • Source priority. Poetry’s priority = "supplemental" and priority = "explicit" on a [[tool.poetry.source]] have no direct uv equivalent. The index URL converts, but you set index precedence yourself in uv.
  • Empty extras. An extra only converts if its package is declared optional = true in [tool.poetry.dependencies]. If it is not, the tool emits cli = [] and warns Could not find dependency "click" listed in "cli" extra. Fix the Poetry file first, or add the dependency to [project.optional-dependencies] by hand.

Map Poetry commands to uv

After migrating, replace poetry invocations with their uv equivalents:

Poetry uv
poetry install uv sync
poetry add requests uv add requests
poetry add --group dev pytest uv add --dev pytest
poetry remove requests uv remove requests
poetry run pytest uv run pytest
poetry shell source .venv/bin/activate
poetry lock uv lock
poetry update uv lock --upgrade
poetry update requests uv lock --upgrade-package requests
poetry show --tree uv tree
poetry build uv build
poetry publish uv publish

Tip

poetry shell spawns a subshell. uv keeps the virtual environment in .venv at the project root. Activate it with source .venv/bin/activate (macOS/Linux) or .venv\Scripts\activate (Windows), or prefix commands with uv run instead of activating.

Troubleshooting

Migration aborts on a private index

By default migrate-to-uv locks dependencies at the end, which requires reaching every index. If a private index needs credentials or is unreachable, the lock step fails and the whole migration rolls back:

error: Could not lock dependencies, aborting the migration.
Consider using "--ignore-locked-versions" ... or "--skip-lock" ...

The index URL converts, but Poetry’s stored credentials do not. uv reads them from environment variables named after the index. Set them before migrating, where company-repo is the index name uppercased with hyphens replaced by underscores:

$ export UV_INDEX_COMPANY_REPO_USERNAME=<username>
$ export UV_INDEX_COMPANY_REPO_PASSWORD=<password>
$ uvx migrate-to-uv

To convert the metadata first and lock later, run uvx migrate-to-uv --skip-lock, then set the credentials and run uv lock.

An extra converted to an empty list

If migrate-to-uv warns that it could not find a dependency listed in an extra, the package was not marked optional in Poetry. Declare it optional = true in [tool.poetry.dependencies] before migrating, or add it directly to [project.optional-dependencies]:

pyproject.toml
[project.optional-dependencies]
cli = ["click>=8.0,<9"]

A dependency marker looks wrong

Markers convert verbatim, but Poetry’s { markers = "..." } table syntax differs from PEP 508 inline markers. Confirm the migrated line matches what you expect:

pyproject.toml
dependencies = [
    "pywin32>=306,<307 ; sys_platform == 'win32'",
]

Run uv sync on the target platform to confirm the marked dependency installs only where intended.

A console script stopped working

[tool.poetry.scripts] converts to [project.scripts] automatically:

pyproject.toml
[project.scripts]
myproj = "myproj.cli:main"

If the script is missing after uv sync, the project itself was not installed. uv installs the project in editable mode only when a [build-system] is present. Confirm pyproject.toml has one, then re-run uv sync.

Consider poetry-to-uv for read-only conversion

poetry-to-uv is a smaller alternative that prints a converted pyproject.toml to stdout without writing files or generating a lockfile. It last released v0.0.4 in August 2024 and covers fewer cases than migrate-to-uv. Reach for it only if you want a quick read-only translation; for an actual migration, migrate-to-uv is the better-maintained choice.

Learn More

Last updated on