> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/astral-sh/uv/llms.txt
> Use this file to discover all available pages before exploring further.

# uv publish

> Upload distributions to an index

# uv publish

Upload distributions to a package index (PyPI or private registries).

## Usage

```bash theme={null}
uv publish [OPTIONS] [FILES]...
```

## Description

Upload Python distributions (wheels and source distributions) to a package index. By default, publishes to PyPI, but can be configured to publish to private registries.

Supports multiple authentication methods:

* Username and password
* API tokens
* Trusted publishing (GitHub Actions, GitLab CI/CD)
* Keyring integration

## Arguments

### `[FILES]...`

Paths to the files to upload. Accepts glob expressions.

Defaults to `dist/*`. Selects only wheels and source distributions and their attestations, while ignoring other files.

```bash theme={null}
# Publish all files in dist/
uv publish

# Publish specific files
uv publish dist/my-package-1.0.0.tar.gz dist/my_package-1.0.0-py3-none-any.whl

# Publish using glob pattern
uv publish "dist/my-package-*"
```

## Options

### Index configuration

#### `--index <INDEX>`

The name of an index in the configuration to use for publishing.

The index must have a `publish-url` setting in your `pyproject.toml` or `uv.toml`:

```toml theme={null}
[[tool.uv.index]]
name = "pypi"
url = "https://pypi.org/simple"
publish-url = "https://upload.pypi.org/legacy/"
```

The index `url` will be used to check for existing files to skip duplicate uploads.

With these settings, the following two calls are equivalent:

```bash theme={null}
uv publish --index pypi
uv publish --publish-url https://upload.pypi.org/legacy/ --check-url https://pypi.org/simple
```

Environment variable: `UV_PUBLISH_INDEX`

#### `--publish-url <PUBLISH_URL>`

The URL of the upload endpoint (not the index URL).

Note that there are typically different URLs for index access (e.g., `https://.../simple`) and index upload.

Defaults to PyPI's publish URL: `https://upload.pypi.org/legacy/`

```bash theme={null}
# Publish to Test PyPI
uv publish --publish-url https://test.pypi.org/legacy/

# Publish to private registry
uv publish --publish-url https://my-registry.example.com/upload/
```

Environment variable: `UV_PUBLISH_URL`

#### `--check-url <CHECK_URL>`

Check an index URL for existing files to skip duplicate uploads.

This option allows retrying publishing that failed after only some, but not all files have been uploaded, and handles errors due to parallel uploads of the same file.

Before uploading, the index is checked. If the exact same file already exists in the index, the file will not be uploaded. If an error occurred during the upload, the index is checked again, to handle cases where the identical file was uploaded twice in parallel.

The exact behavior will vary based on the index. When uploading to PyPI, uploading the same file succeeds even without `--check-url`, while most other indexes error. When uploading to pyx, the index URL can be inferred automatically from the publish URL.

The index must provide one of the supported hashes (SHA-256, SHA-384, or SHA-512).

```bash theme={null}
uv publish --check-url https://pypi.org/simple
```

Environment variable: `UV_PUBLISH_CHECK_URL`

### Authentication

#### `-u, --username <USERNAME>`

The username for the upload.

```bash theme={null}
uv publish --username myusername
```

Environment variable: `UV_PUBLISH_USERNAME`

#### `-p, --password <PASSWORD>`

The password for the upload.

```bash theme={null}
uv publish --password mypassword
```

For security, prefer using environment variables or keyring:

```bash theme={null}
export UV_PUBLISH_PASSWORD=mypassword
uv publish --username myusername
```

Environment variable: `UV_PUBLISH_PASSWORD`

#### `-t, --token <TOKEN>`

The token for the upload.

Using a token is equivalent to passing `__token__` as `--username` and the token as `--password`.

```bash theme={null}
uv publish --token pypi-AgEIcHlwaS5vcmc...
```

For security, prefer using environment variables:

```bash theme={null}
export UV_PUBLISH_TOKEN=pypi-AgEIcHlwaS5vcmc...
uv publish
```

Environment variable: `UV_PUBLISH_TOKEN`

#### `--trusted-publishing <TRUSTED_PUBLISHING>`

Configure trusted publishing.

By default, uv checks for trusted publishing when running in a supported environment, but ignores it if it isn't configured.

Supported environments:

* GitHub Actions
* GitLab CI/CD

```bash theme={null}
uv publish --trusted-publishing always
```

Values:

* `always` - Require trusted publishing
* `never` - Disable trusted publishing
* `automatic` - Use if available (default)

#### `--keyring-provider <KEYRING_PROVIDER>`

Attempt to use `keyring` for authentication for remote requirements files.

At present, only `--keyring-provider subprocess` is supported, which configures uv to use the `keyring` CLI to handle authentication.

Defaults to `disabled`.

```bash theme={null}
uv publish --keyring-provider subprocess
```

Environment variable: `UV_KEYRING_PROVIDER`

### Upload options

#### `--dry-run`

Perform a dry run without uploading files.

When enabled, the command will check for existing files if `--check-url` is provided, and will perform validation against the index if supported, but will not upload any files.

```bash theme={null}
uv publish --dry-run
```

Useful for testing your publishing configuration.

#### `--no-attestations`

Do not upload attestations for the published files.

By default, uv attempts to upload matching PEP 740 attestations with each distribution that is published.

```bash theme={null}
uv publish --no-attestations
```

Environment variable: `UV_PUBLISH_NO_ATTESTATIONS`

## Examples

### Publish to PyPI

Using an API token (recommended):

```bash theme={null}
export UV_PUBLISH_TOKEN=pypi-AgEIcHlwaS5vcmc...
uv publish
```

Or with username and password:

```bash theme={null}
uv publish --username myusername --password mypassword
```

### Publish to Test PyPI

```bash theme={null}
uv publish \
  --publish-url https://test.pypi.org/legacy/ \
  --token pypi-AgEIcHlwaS5vcmc...
```

### Publish to a private registry

Configure in `pyproject.toml`:

```toml theme={null}
[[tool.uv.index]]
name = "private"
url = "https://my-registry.example.com/simple"
publish-url = "https://my-registry.example.com/upload/"
```

Then publish:

```bash theme={null}
uv publish --index private --username myuser --password mypass
```

### Publish specific files

```bash theme={null}
uv publish dist/my-package-1.0.0.tar.gz dist/my_package-1.0.0-py3-none-any.whl
```

### Dry run to test configuration

```bash theme={null}
uv publish --dry-run
```

Output:

```
Checking dist/my-package-1.0.0.tar.gz
Checking dist/my_package-1.0.0-py3-none-any.whl
Dry run completed successfully (no files uploaded)
```

### Publish with trusted publishing (GitHub Actions)

In your GitHub Actions workflow:

```yaml theme={null}
name: Publish to PyPI

on:
  release:
    types: [published]

jobs:
  publish:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
    steps:
      - uses: actions/checkout@v4
      - name: Install uv
        uses: astral-sh/setup-uv@v3
      - name: Build
        run: uv build
      - name: Publish
        run: uv publish --trusted-publishing always
```

### Check for existing files before upload

```bash theme={null}
uv publish --check-url https://pypi.org/simple
```

This prevents duplicate upload errors and allows retrying failed uploads.

### Publish using keyring

```bash theme={null}
# Store credentials in keyring first
keyring set https://upload.pypi.org/legacy/ __token__

# Publish using keyring
uv publish --keyring-provider subprocess
```

## Use cases

### CI/CD publishing workflow

Complete workflow for building and publishing:

```bash theme={null}
# Build distributions
uv build

# Verify build artifacts
ls -lh dist/

# Publish to PyPI
uv publish
```

### Publishing to multiple registries

Publish to Test PyPI first, then to PyPI:

```bash theme={null}
# Test on Test PyPI
uv publish --publish-url https://test.pypi.org/legacy/

# Install and test from Test PyPI
uv pip install --index https://test.pypi.org/simple my-package

# Publish to production PyPI
uv publish
```

### Retry failed uploads

If a publish fails partway through:

```bash theme={null}
uv publish --check-url https://pypi.org/simple
```

Files already uploaded will be skipped.

### Workspace publishing

Publish all workspace packages:

```bash theme={null}
uv build --all-packages
uv publish
```

## Security best practices

### Use API tokens instead of passwords

API tokens are more secure and can be scoped:

```bash theme={null}
# Generate a token at https://pypi.org/manage/account/token/
export UV_PUBLISH_TOKEN=pypi-AgEIcHlwaS5vcmc...
uv publish
```

### Use trusted publishing when possible

Trusted publishing eliminates the need for long-lived credentials:

```yaml theme={null}
# GitHub Actions
permissions:
  id-token: write

steps:
  - run: uv publish --trusted-publishing always
```

### Store credentials securely

Never commit credentials to version control:

```bash theme={null}
# Use environment variables
export UV_PUBLISH_TOKEN=...

# Or use keyring
keyring set https://upload.pypi.org/legacy/ __token__
uv publish --keyring-provider subprocess
```

### Test with dry run first

Verify configuration before actual upload:

```bash theme={null}
uv publish --dry-run
```

## Related commands

* [`uv build`](/cli/build) - Build distributions for publishing
* [`uv init`](/cli/init) - Create a new project

## Notes

* By default, uv publishes to PyPI (`https://upload.pypi.org/legacy/`)
* API tokens are preferred over username/password authentication
* Trusted publishing is the most secure option for CI/CD
* Use `--dry-run` to test your configuration without uploading
* PEP 740 attestations are uploaded by default when available
* Use `--check-url` to handle duplicate uploads and retry scenarios
