Skip to main content
A workspace is a collection of one or more packages (called workspace members) that are managed together. Inspired by Cargo workspaces, uv workspaces organize large codebases by splitting them into multiple packages with shared dependencies.

What Are Workspaces?

Workspaces are ideal for:
  • A web application alongside multiple library packages
  • A library with performance-critical extensions in Rust/C++
  • A plugin system with separate packages per plugin
  • Monorepos with related packages versioned together

Shared Lockfile

Single uv.lock for all workspace members ensures consistent dependencies

Independent Packages

Each member has its own pyproject.toml and can be versioned separately

Getting Started

Create a workspace by adding a tool.uv.workspace table to a pyproject.toml:
pyproject.toml
[project]
name = "albatross"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["bird-feeder", "tqdm>=4,<5"]

[tool.uv.sources]
bird-feeder = { workspace = true }

[tool.uv.workspace]
members = ["packages/*"]
exclude = ["packages/seeds"]
Running uv init inside an existing project automatically adds the new project to the workspace and creates the tool.uv.workspace table if needed.

Workspace Configuration

Members and Exclusions

The workspace definition requires:
  • members (required) - Globs matching workspace member directories
  • exclude (optional) - Globs excluding specific directories
Every directory matched by members (and not excluded) must contain a pyproject.toml file.

Workspace Root

Every workspace needs a root, which is also a workspace member:
albatross/                    # Workspace root
├── pyproject.toml            # Root member
├── uv.lock                   # Shared lockfile
├── packages/
│   ├── bird-feeder/          # Workspace member
│   │   └── pyproject.toml
│   └── seeds/                # Excluded
│       └── pyproject.toml
└── src/
    └── albatross/
In this example:
  • albatross is the workspace root
  • bird-feeder is a workspace member
  • seeds is excluded and not part of the workspace

Workspace Commands

Default Behavior

By default, uv run and uv sync operate on the workspace root:
# These are equivalent
uv run pytest
uv run --package albatross pytest

Running in Specific Members

Use --package to target a specific workspace member:
uv run --package bird-feeder pytest
This command works from any directory within the workspace.

Locking the Workspace

uv lock operates on the entire workspace at once:
uv lock
This updates the shared uv.lock with dependencies from all workspace members.

Workspace Sources

Dependencies on workspace members use tool.uv.sources:
pyproject.toml
[project]
name = "albatross"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["bird-feeder", "tqdm>=4,<5"]

[tool.uv.sources]
bird-feeder = { workspace = true }

[tool.uv.workspace]
members = ["packages/*"]
The workspace = true indicates the dependency is provided by a workspace member, not PyPI.
Dependencies between workspace members are always editable.

Inherited Sources

Sources defined in the workspace root apply to all members:
pyproject.toml
[project]
name = "albatross"
version = "0.1.0"
dependencies = ["bird-feeder", "tqdm>=4,<5"]

[tool.uv.sources]
bird-feeder = { workspace = true }
tqdm = { git = "https://github.com/tqdm/tqdm" }

[tool.uv.workspace]
members = ["packages/*"]
Every workspace member will install tqdm from GitHub by default.

Overriding Sources

Members can override workspace sources:
packages/bird-feeder/pyproject.toml
[project]
name = "bird-feeder"
dependencies = ["tqdm"]

[tool.uv.sources]
# Override workspace source
tqdm = { version = ">=4.66" }
If a member provides tool.uv.sources for a dependency, it ignores the workspace source even if limited by a marker that doesn’t match the current platform.

Workspace Layouts

Root Project with Libraries

The most common layout has an explicit root with library packages:
albatross/
├── pyproject.toml            # Root project
├── uv.lock                   # Shared lockfile
├── src/
│   └── albatross/
│       └── main.py
└── packages/
    ├── bird-feeder/          # Library 1
    │   ├── pyproject.toml
    │   └── src/
    │       └── bird_feeder/
    └── seeds/                # Library 2
        ├── pyproject.toml
        └── src/
            └── seeds/

Virtual Root

A workspace can have a “virtual” root that only aggregates packages:
pyproject.toml
[project]
name = "workspace-root"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = []  # No direct dependencies

[tool.uv.workspace]
members = ["packages/*"]

When to Use Workspaces

Good Use Cases

Monorepos

Multiple related packages in one repository

Extension Modules

Pure Python with Rust/C++ performance modules

Plugin Systems

Core library with separate plugin packages

Shared Testing

Common test suite across multiple packages

Example: Core + Plugins

pyproject.toml
[project]
name = "myapp"
version = "0.1.0"
dependencies = []

[tool.uv.workspace]
members = ["plugins/*"]
Each plugin depends on the core:
plugins/auth/pyproject.toml
[project]
name = "myapp-auth"
version = "0.1.0"
dependencies = ["myapp"]

[tool.uv.sources]
myapp = { workspace = true }

When NOT to Use Workspaces

Conflicting Requirements

Workspaces share a single lockfile. If members have conflicting requirements, use path dependencies instead:
pyproject.toml
[project]
name = "albatross"
version = "0.1.0"
dependencies = ["bird-feeder"]

[tool.uv.sources]
bird-feeder = { path = "packages/bird-feeder" }
This approach:
  • Allows separate virtual environments per package
  • Enables fine-grained dependency resolution
  • Loses uv run --package convenience

Different Python Versions

Workspaces enforce a single requires-python for all members (the intersection of all members’ requires-python values). If you need to test a member on a Python version unsupported by other members:
# Use uv pip in a separate environment
uv venv --python 3.9
uv pip install -e packages/old-member

Workspace Isolation

Python doesn’t provide dependency isolation. uv cannot ensure that:
  • A package only uses its declared dependencies
  • Packages don’t import dependencies from other workspace members
Proper dependency declarations in each member’s pyproject.toml are the responsibility of the developer.

Single requires-python

Workspaces take the intersection of all members’ requires-python values:
albatross/pyproject.toml
[project]
requires-python = ">=3.10"
packages/bird-feeder/pyproject.toml
[project]
requires-python = ">=3.11"
The workspace requires-python becomes >=3.11 (the intersection).

Advanced Configuration

Workspace-wide Settings

Some settings in the root pyproject.toml apply workspace-wide:
pyproject.toml
[tool.uv]
# Global cache directory
cache-dir = ".cache/uv"

# Workspace-wide indexes
index-url = "https://pypi.org/simple"

# Development groups default
default-groups = ["dev", "test"]

Member-specific Overrides

Members can override workspace settings:
packages/bird-feeder/pyproject.toml
[tool.uv]
# Override for this member only
default-groups = ["dev"]

Workspace vs Path Dependencies

Advantages:
  • Shared lockfile ensures consistency
  • Single uv run --package command
  • Unified dependency resolution
  • Automatic member discovery
Disadvantages:
  • Single requires-python for all members
  • All members must be compatible
  • Shared virtual environment

Migration Strategies

From Single Project

Convert a single project to a workspace:
# 1. Create workspace structure
mkdir packages
mv src packages/original

# 2. Create package pyproject.toml
cd packages/original
uv init --lib

# 3. Add workspace table to root
cd ../..
# Edit pyproject.toml to add [tool.uv.workspace]

From Path Dependencies

Convert path dependencies to workspace:
pyproject.toml
# Before
[tool.uv.sources]
foo = { path = "packages/foo" }
bar = { path = "packages/bar" }

# After
[tool.uv.sources]
foo = { workspace = true }
bar = { workspace = true }

[tool.uv.workspace]
members = ["packages/*"]

Projects

Learn about individual project structure

Dependencies

Understand dependency management and sources

Python Versions

Learn about Python version requirements

Cache

Understand how uv caches workspace dependencies

Build docs developers (and LLMs) love