PEP 752 was accepted at the end of June. It lets an organisation apply to PyPI to reserve a package name prefix, after which new uploads matching that prefix are rejected unless they come from the grant holder. Existing packages are grandfathered. The motivation is impersonation: someone publishing malware as google-cloud-something or apache-something gets the vendor’s name in front of it. The PEP names four example prefixes: google-cloud-, opentelemetry-, apache-airflow-providers-, and types-.
I pulled every package name from PyPI (912,665 of them, via ecosyste.ms) and counted prefixes. Those four account for 264, 271, 154, and 1,295 packages respectively, and each is published almost entirely by one organisation. Google publishes google-cloud-*, the OpenTelemetry project publishes opentelemetry-*, the Apache Airflow team publishes apache-airflow-providers-*, and the typeshed maintainers publish types-*. Prefix reservation formalises what those publishers already do; one of the PEP’s authors, Jarek Potiuk, is on the Airflow PMC.
There are 13,934 prefixes on PyPI shared by five or more packages. The biggest is django-* with 17,450, published by thousands of different authors at a rate of about a thousand a year since 2010. flask-* has 2,815, pytest-* has 2,137, mkdocs-* has 764, sphinxcontrib-* has 370. collective.*, the Plone community’s shared prefix using Python’s namespace package machinery, has 1,436 packages and has been in use since at least 2008. Anyone publishes a Django app as django-something, a pytest plugin as pytest-something, a Sphinx extension as sphinxcontrib-something, and the host project’s documentation tells them to.
An earlier draft of PEP 752 had a mode for this, letting a grant holder mark a prefix as open so that anyone could still publish under it while the grant holder’s own packages got a visual indicator. It was removed as having “insufficient motivation and the fact that repositories could technically satisfy such use cases with standard grant semantics.” Multiple organisations can share a grant, which handles a prefix split across a few organisations. Under the accepted design, if the Django Software Foundation reserved django-, the existing 17,450 packages would keep publishing new versions and the roughly one thousand new django-* packages that would otherwise arrive next year would be blocked. I compared namespace designs across registries earlier this year; NuGet, crates.io and npm have each attached something to prefixes already.
Seven registries
Splitting package names on runs of -, _ and . and grouping by the first token:
| | packages | prefixes with ≥5 packages | packages with a shared prefix | ≥100 | ≥500 |
| --- | --- | --- | --- | --- | --- |
| pypi.org | 912,665 | 13,934 | 41% | 427 | 66 |
| crates.io | 311,301 | 9,161 | 54% | 166 | 19 |
| rubygems.org | 209,853 | 4,214 | 46% | 132 | 18 |
| hex.pm | 22,373 | 382 | 32% | 12 | 1 |
| hackage.haskell.org | 19,345 | 521 | 41% | 9 | 0 |
| npmjs.org (unscoped) | 2,678,188 | 37,460 | 68% | 2,604 | 468 |
| nuget.org | 828,361 | 22,377 | 70% | 690 | 72 |
npm has had @scope/name since 2014 so its row counts only the 63% of packages that are unscoped. Between a third and two-thirds of every registry’s packages share a first token with at least four others. The counts here are first-token only; multi-token prefixes like apache-airflow-providers- are counted under apache-* in the table and separately when named in the text.
Open plugin conventions, where many authors publish extensions named after a host project, are the largest category among the biggest prefixes on every registry: django-* (17,450 pypi), react-* (86,142 npm), jekyll-* (1,658 rubygems), bevy-* (1,870 crates), phoenix_* (245 hex), servant-* (152 hackage). Some of these are functional, meaning the name has to match the pattern for the host tool to load the plugin: cargo runs cargo-foo as the cargo foo subcommand, ESLint’s config resolver matches eslint-plugin-foo, pytest loads pytest-foo via entry points.
Generated SDK families, where one organisation publishes hundreds of packages from an API spec, are the next largest. aws-sdk-* (473 crates, 481 gems, 92 pypi), google-cloud-* (258 crates, 510 gems, 264 pypi, 5 hackage), azure-mgmt-* (273 crates, 124 gems, 334 pypi). The Odoo Community Association publishes about 20,000 packages on PyPI across seven versioned prefix trees (odoo8-addon-* through odoo14-addon-* plus odoo-addon-*), which is the largest coordinated prefix use I found on any registry. On npm the generated families are the ones that adopt scopes: @types/* 11,388, @stdlib/* 5,555, @fontsource/* 2,122.
Language and framework tags account for another large block: python-* (6,278), rust-* (1,692), ex_* (1,123 hex, the Elixir convention), node-* (23,389 npm), is-* (3,754 npm, predicate functions), use-* (4,317 npm, React hooks). Hackage has a set of these that mirror module paths (data-*, network-*, language-*).
Getting-started guides produce their own prefixes: hola-* on rubygems has 764 packages from people following the make your own gem guide, example-package-* and example-pkg-* on PyPI have 1,031 between them from the packaging.python.org tutorial, and guessing-game-* on crates.io has 125 from chapter 2 of the Rust book. topsis-* on PyPI has 1,999 packages with student roll numbers embedded in the names (TOPSIS is a decision-analysis method that gets set as a programming assignment), from what appears to be a university course run each year since 2020.
Bulk publication accounts for some of the largest recent prefixes: use-* on crates.io has 820 packages, 809 of them published in 2026 by one account. iflow-mcp-* on PyPI has 2,505 packages of republished MCP servers from one publisher, plus 5,184 more under @iflow-mcp on npm from the same account. free.robux-* on NuGet has 3,620. mcp-* on PyPI, from many publishers, went from 2 packages in 2023 to 3,022 by mid-2026, reaching that count faster than django-* reached 3,000.
For the top 1,000 packages by downloads, between 69% and 80% on crates.io, rubygems, and pypi either belong to a prefix cluster or are the package a large cluster is named after (serde, pytest, rack); hex is 45% and hackage 60%. Of the packages flagged critical on ecosyste.ms, 226 of rubygems’ 974 are aws-sdk-* alone, and 74% of NuGet’s are under Microsoft.* or System.*.
NuGet
PEP 752 cites NuGet’s ID prefix reservation as prior art. It went live in October 2017, works the same way (apply by email, matching uploads are rejected unless from the reservation holder, existing packages grandfathered), and adds a checkmark to verified packages in the gallery and Visual Studio. NuGet also supports subprefix delegation, where the Microsoft.* holder can delegate Microsoft.AspNet.* to a different account, and public reservations, where the holder gets the checkmark on their own packages while other publishers can still upload under the prefix, which is what PEP 752’s earlier draft called an open namespace.
Sampling NuGet’s per-package verified flag via the search API across the top 30 non-spam prefixes shows Microsoft.*, DevExpress.*, Syncfusion.* and JetBrains.* fully verified and single-publisher, and Google.*, Amazon.* and Azure.* mostly verified with some grandfathered community packages.
Serilog.* is 6% verified across 776 packages, and Serilog is a logging library with a plugin model where sinks and enrichers are published as Serilog.Sinks.Foo and Serilog.Enrichers.Foo by whoever writes them. Sampling 60 of those and checking first-publish dates against verified, the core team’s packages (Serilog, Serilog.Sinks.File) are verified and community packages aren’t, including community packages first published in every year through 2025 (Serilog.Enrichers.Metrics.Memory, September 2025, owner Jandini). That is consistent with Serilog.* being a public reservation: community sinks are still being accepted eight years in. Cake.* (607 packages, Cake build addins) shows the same pattern at 7% verified. The checkmark appears per package; the reservation list itself is private to NuGet.
crates.io
Cargo’s RFC 3243, accepted and currently being implemented, takes a different approach: whoever owns the foo crate can publish foo::bar, and only they can, with no application process. Of the 166 crates.io prefixes with 100 or more packages, 156 have a bare crate matching the first token, and of those 37% last released more than five years ago and 20% have exactly one version. The async crate has two versions, last release 2014, while 844 crates are named async-*. The google, aws and tree crates are each owned by individuals with no connection to Google, AWS, or tree-sitter. bevy, serde, tokio, tauri, actix, axum, and windows are owned by their respective projects. RFC 3243 explicitly avoids touching the existing hyphenated names (serde_json stays where it is; serde::json would be a new crate), so the 702 serde-* crates from many authors continue alongside anything the serde owners publish under serde::.
npm
npm added @scope/name in 2014, and 37% of the 4.26 million live packages use one. Among the unscoped 63%, 68% share a prefix with at least four other packages. For the flat prefixes that also have a matching scope, the flat side is almost always larger, with the core team publishing under the scope and the community under the flat prefix: babel-* 6,099 vs @babel/* 239, vue-* 23,871 vs @vue/* 110, eslint-* 16,664 vs @eslint/* 17. The exceptions are generated families that migrated or started scoped, like @types/* (11,388) and @stdlib/* (5,555). eslint-plugin-* (6,957) and babel-plugin-* (3,987) stay flat because the tools string-match those prefixes when resolving config.
Bulk publication appears in scopes as well as flat names: @hyper.fun alone has 30,273 packages, alongside a run of scopes with random-string names ending in npm (@wemnyelezxnpm, @diahkomalasarinpm, @ryniaubenpm) holding 1,500-1,900 packages each, all created in April 2024 during the tea.xyz protocol spam wave. dsr-* (10,742) and tea-* (4,486) from the same wave are unscoped.
Browsing
The projects with the biggest plugin conventions maintain their own directories: jekyllrb.com/docs/plugins, homebridge.io, flow.nodered.org, n8n.io/integrations, fastlane’s plugin list. These are curated, so a package is listed because the project added it. Registry search returns fuzzy-ranked results across the whole index, so searching rubygems.org for jekyll gives popular jekyll-related gems mixed with anything else that mentions the word. NuGet’s checkmark and npm’s npmjs.com/org/babel scope page are the two places a registry currently exposes something membership-like.
A prefix page that lists every jekyll-* gem ranked by dependent count, with jekyll itself pinned and its direct owners’ packages marked, would get most of the way to what those project directories provide, and the data to build it is already in the registry index. Third-party registry browsers like npmx are well placed to try it for npm’s flat prefixes, and I have the cross-registry data on ecosyste.ms so I might build one there.
PEP 755
PEP 752’s four named prefixes cover about 2,000 PyPI packages from one organisation each, against about 25,000 under the open-convention prefixes named above. The companion policy PEP 755 is still in draft and sets out who can apply for a grant and on what terms, including a proposed queue priority for paid organisations. How it handles an application for django- or pytest-, from the project or from anyone else, is still to be worked out, and NuGet’s handling of Serilog.* is one existing precedent.
Data and scripts: github.com/andrew/package-name-prefixes.