> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pyx.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Custom views

In pyx, you can create custom "views" on public and private package indexes. A
view can be thought of as a filtered combination of one or more indexes, exposed
as a single index URL.

Views are exposed using the same URL scheme as regular
[registries](/concepts#registries). For example, if your team is `acme`, and you
create a `dev` view, you can install packages from the view with:

```shell theme={null}
uv pip install fastapi --index https://api.pyx.dev/simple/acme/dev
```

To create a view, navigate to the [Views tab](https://app.pyx.dev/views) in the
pyx dashboard.

## Filters

A view can represent filtered subsets of upstream indexes.

For example, while the `public/pypi` index includes all packages from PyPI, in
pyx, you can create your own index URLs that represent filtered subsets of PyPI,
such as:

* A view of all PyPI packages uploaded prior to a given date, i.e., a snapshot
  of PyPI at a given point in time (`package.upload_time <= "2025-01-01"`)
* A view of all PyPI packages that are at least a week old, to guard against
  malicious packages (`package.age_days >= 7`)
* A view of all PyPI packages with at least 1,000 monthly downloads
  (`package.pypi_downloads_30_days >= 1000`)
* A view of all PyPI packages with no critical vulnerabilities
  (`package.cve_max_score < 9.0`)

View filters are expressed in a query language based on a subset of Python. For
example, to filter PyPI packages to only include those uploaded prior to a given
date, you can use the following filter:

```python theme={null}
package.upload_time <= "2025-01-01"
```

The filter language exposes the following fields:

* **`package.name`**: Package name (e.g., `fastapi`)
* **`package.upload_time`**: Package upload time (e.g., `2025-05-01` or
  `2025-05-01T12:00:00`)
* **`package.age_days`**: Package age in days (e.g., `7`)
* **`release.version`**: Release version (e.g., `0.100.0`)
* **`release.upload_time`**: Release upload time (e.g., `2025-06-01` or
  `2025-06-01T18:00:00`)
* **`release.age_days`**: Release age in days (e.g., `7`)
* **`file.name`**: File name (e.g., `fastapi-0.100.0-py3-none-any.whl`)
* **`file.upload_time`**: File upload time (e.g., `2025-06-01` or
  `2025-06-01T18:00:00`)
* **`file.age_days`**: File age in days (e.g., `7`)
* **`package.pypi_downloads_30_days`**: 30-day downloads (e.g., `1000`)
* **`package.pypi_downloads_7_days`**: 7-day downloads (e.g., `100`)
* **`package.cve_max_score`**: Max CVE score (e.g., `9.0`)
* **`release.cve_max_score`**: Release CVE score (e.g., `9.0`)

Conditions can be combined using logical operators, as in Python:

```python theme={null}
package.upload_time <= "2025-01-01" and package.pypi_downloads_30_days >= 1000
```

For example, to filter out a specific package (like `setuptools`) from a view,
you can use the following filter:

```python theme={null}
package.name != "setuptools"
```

Similarly, to filter out a specific *version* of a package (like
`setuptools==69.0.0`), you can use the following filter:

```python theme={null}
not (package.name == "setuptools" and release.version == "69.0.0")
```

## Groups

You can also create views that combine multiple indexes. For example, a single
view can pull in packages from your own private index if they exist, and falls
back to PyPI if they don't. This is achieved using groups.

A view is made up of one or more **groups**, and each group contains one or more
[registries](/concepts#registries) (each with an optional [filter](#filters)).
Groups control how packages from different registries are combined when
resolving dependencies.

### Priority across groups

Groups are ordered by priority. When resolving a package, pyx searches each
group in order and **stops at the first group that contains the package**. Later
groups are only consulted for packages that don't exist in any earlier group.

This enables an override pattern: a high-priority group with your private
registry will serve your internal packages, while a lower-priority group with
PyPI provides everything else. If a package exists in your private registry, it
takes precedence; PyPI is never consulted for that package.

We recommend using this especially for mitigating *dependency confusion*
attacks, where an attacker publishes a malicious package on PyPI with the same
name as an existing private package. Putting your private registry in a
higher-priority group ensures that packages with the same name are never served
from PyPI.

### Merging within a group

Registries within the same group are **merged** together. When a package exists
in multiple registries in the same group, the view combines all of their files
into a single index response. This is useful for supplementing a public index
with additional files, such as custom-built wheels or private distributions. If
two registries provide a file with the same filename, the file from the earlier
registry in the group is preferred.

For example, if a group contains both your private registry and PyPI, and both
provide files for `numpy`, the view will include files from both registries.

## Example

Consider a view with two groups:

1. **Group 1**: Your private `acme/main` registry
2. **Group 2**: `public/pypi`

With this configuration:

* For `acme-core` (which only exists in your private registry), pyx serves it
  from Group 1.
* For `flask` (which only exists on PyPI), pyx skips Group 1 (no match) and
  serves it from Group 2.
* If you publish a custom build of `numpy` to your private registry, pyx serves
  your `numpy` from Group 1 and ignores PyPI's `numpy` entirely.

To merge your custom `numpy` wheels *with* PyPI's `numpy` wheels instead, place
both registries in the same group.
