Package Search — npm, PyPI and crates.io in One Shape
Pricing
from $20.00 / 1,000 search runs
Package Search — npm, PyPI and crates.io in One Shape
Search and profile open-source packages across npm, PyPI and crates.io and get them back in one common shape: licence, repository, maintainers, first and last release, release count, download figures, dependent count, deprecation and yank status, and runtime dependencies. Optional.
Pricing
from $20.00 / 1,000 search runs
Rating
0.0
(0)
Developer
Benjamin Jerez
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
2 days ago
Last modified
Categories
Share
Search or look up open-source packages across the three largest language registries and get them back as one row shape, so a JavaScript package, a Python project and a Rust crate line up in the same spreadsheet column.
No API key, no scraping, no login. Three public registry APIs, queried live.
What a row looks like
One run of the default input — the term websocket against all three registries, plus the direct lookups npm:express, pypi:requests, crates:serde — returned 44 packages and 213 version rows in 12 seconds on 19 September 2026.
| Field | express (npm) | requests (PyPI) | serde (crates.io) |
|---|---|---|---|
version | 5.2.1 | 2.34.2 | 1.0.229 |
license | MIT | Apache-2.0 | MIT OR Apache-2.0 |
weeklyDownloads | 121 351 312 | — | — |
monthlyDownloads | 482 331 266 | — | — |
totalDownloads | — | — | 1 412 337 605 |
recentDownloads90d | — | — | 314 361 134 |
downloadsBasis | npm search API, last 7 and last 30 days | none — PyPI publishes no download counts in its JSON API | crates.io, all-time and last 90 days |
dependentsCount | 108 010 | — | 123 264 |
releaseCount | 289 | 163 | 316 |
publishedAt | 2010-12-29 | 2011-02-14 | 2014-12-05 |
lastPublishedAt | 2026-09-18 | 2026-05-14 | 2026-07-18 |
runtimeDependencyCount | 28 | 4 | — |
repositoryUrl | github.com/expressjs/express | github.com/psf/requests | github.com/serde-rs/serde |
Every row also carries description, homepage, documentationUrl, author, maintainers, keywords, categories, licenseRaw, licenseSource, runtimeRequirement, packageUrl, isDeprecated, deprecationMessage, isYanked, yankedReason, and the per-version counts deprecatedVersionCount and yankedVersionCount.
Each query produces a summary row first, before its packages: the registry, the term, how many the registry says match, how many were returned, and a note explaining any zero.
Turning on Include version history adds one version row per release, newest first, with its publication date, licence, size, and yank or deprecation status.
The three registries are not the same, and the output says so
The point of a common shape is that it does not hide the differences. Where a registry cannot answer, the field is empty and a note or downloadsBasis field says why, rather than a plausible number being invented.
PyPI cannot be searched. There is no JSON search endpoint. pypi.org/search returns HTML, and the only machine-readable index, /simple/ as JSON, is a 44 MB list of bare project names with no metadata and no ranking. A search term is therefore resolved against PyPI as an exact project name and nothing more; the summary row says so on every run. PyPI does its own name normalisation, so scikit_learn, Scikit-Learn and scikit-learn all reach the same project. For anything other than an exact name, use the direct-lookups field.
Download figures come from different clocks. npm publishes last-7-day and last-30-day counts inline on its search endpoint. crates.io publishes an all-time total and a 90-day recent figure — verified against crates.io's own daily download series, which summed to 314 481 694 over 2026-06-22 to 2026-09-19 against a reported recent_downloads of 314 361 134. PyPI publishes nothing: its info.downloads object has returned -1 for years. The downloadsBasis field states which clock each row used.
Deprecated npm packages have no download figure here. npm's download counts are only reachable through its search endpoint, and npm search excludes deprecated packages entirely — request and left-pad do not appear in their own search results even at the maximum page size of 250. Those rows carry an explicit downloadsNote instead of a guess.
Deprecation and yanking are not the same thing, and not every registry has both.
| npm | PyPI | crates.io | |
|---|---|---|---|
| Deprecation | Per version. isDeprecated means the version in version (the latest dist-tag) carries a message — the same thing the npm CLI warns about. deprecatedVersionCount gives the rest. | No such concept. isDeprecated is empty, not false. | No such concept. Crates signal it in prose or in a build suffix such as 0.9.34+deprecated, which is not a flag. Empty. |
| Yanking | No such concept. Empty. | Per release file. isYanked is PyPI's own flag for the current version; a release counts as yanked when all of its files are. requests has two. | Per version. isYanked is true when the current version is yanked or the whole crate is. |
Because the meanings differ, empty and false are kept distinct: empty means the registry has no such concept, false means it has one and this package is not flagged.
Licences are normalised, and the raw value is kept beside them. license is one comparable string; licenseRaw is exactly what the registry returned; licenseSource says which field it came from. npm still serves all three historical shapes — a plain string, an object (sax, nopt, semver), and a licenses array of objects (jsdom, connect, npm itself) — and all three are read. PyPI is resolved in order of authority: the PEP 639 license_expression first (numpy has BSD-3-Clause AND 0BSD AND MIT AND Zlib AND CC0-1.0 there and leaves license empty), then the free-text license field when it is short enough to be an identifier rather than a pasted licence text, then the License :: classifiers mapped onto SPDX. crates.io states an SPDX expression per version, so the package licence is the current version's and is not merged across history.
crates.io requires a descriptive User-Agent. Without one it returns HTTP 403 with an empty body. This Actor identifies itself as required by crates.io's published crawler policy.
Limits the registries impose
- npm accepts at most 250 results per search page; asking for more is silently clamped.
- crates.io accepts at most 100 per page and refuses to page past the first 1000 relevance-ranked results, so 1000 is the hard ceiling per crates.io query. The summary row says so when you ask for more.
- npm treats a multi-word query as OR over the words and ranks the union. The query
zzzzz nonexistent term xyzreports 14 715 matches and returnsverrorand@coral-xyz/anchor, none of which contain any of those words as written. A single-token query is exact:qqwwzzxxjjvvreturns zero. The summary row carries a warning whenever a query has more than one token, and thetotalMatchingfigure for such a query is a union, not an intersection. - crates.io returns a true zero for a term that matches nothing.
Size, and what is capped
npm packuments are the one genuinely large object involved. Measured on 19 September 2026: typescript 15.7 MB over 3828 versions, @types/node 11.2 MB over 2373, react 7.0 MB over 2953. Anything past 24 MB is abandoned mid-stream rather than held in memory, and the package is rebuilt from its small /latest manifest with a note saying that release count, first-release date and version history are unavailable for it.
The abbreviated packument (Accept: application/vnd.npm.install-v1+json) was tested and rejected as the default. It is genuinely smaller — typescript drops to 8.7 MB, react to 2.9 MB — but it carries only name, version, dist, dependencies, engines and bin per version. It has no licence, no repository, no description, no maintainers and, decisively, no time map, so first-release and last-release dates cannot be derived from it at all. The full packument is fetched instead, streamed and capped.
Version history is capped by Maximum versions per package, default 25, newest first, because a full history of typescript alone would be 3828 rows.
Who uses this
Engineering and platform teams building an internal catalogue of approved dependencies across several languages at once. Security and licence-compliance work that needs the licence, the repository and the deprecation or yank status of the same package name in three ecosystems. Developer-tool and package-explorer products that want registry-agnostic package cards. Analysts sizing an ecosystem — how many crates match a term, how many are maintained, how the download curve compares. Teams naming a new package who want to see where a name is already taken: put the bare name in the direct-lookups field and it is tried in every registry you selected.
Scope and limits
Current state only. Every field is what the registry says today. Download figures are not backfilled and no historical series is produced.
Dependencies are runtime dependencies of the current version. Build, test and development dependencies are excluded, as are PyPI dependencies gated behind an extra, so what you get is what installs alongside the package. crates.io keeps dependencies on a separate per-version endpoint that would double the request count per crate, so crate rows carry a dependenciesNote instead of a list.
Dependent counts are not comparable between registries. npm counts dependent packages as its search index reports them; crates.io counts reverse dependencies from its own endpoint; PyPI publishes no such figure.
PyPI maintainer accounts are not exposed by the JSON API. The maintainers field for a PyPI row carries the Maintainer metadata field from the package itself, which is often a person's name rather than the PyPI accounts shown on the project page.
Deduplication is by registry and name together. The same name on npm and on PyPI is two packages and is returned twice, which is usually the point. The same package reached by two queries in one run is returned once.
Source and licence
npm — registry.npmjs.org, the public CouchDB registry and its search endpoint. PyPI — pypi.org/pypi/<name>/json, the Python Package Index JSON API. crates.io — crates.io/api/v1, the official Rust crate registry API.
All three are free, keyless and public. The metadata is published by the package authors under each registry's terms of use; licence information is reported as the authors declared it and is not legal advice. Queried live on every run, with no cached copy in between.
Frequently asked
Why is there no download figure for a Python package?
Because PyPI does not publish one. Its JSON API has returned -1 for the download counters for years, and the third-party service that fills the gap is not used here. The downloadsBasis field says this on every PyPI row.
Why did my search return nothing on PyPI but plenty on npm? PyPI has no search endpoint. The term was tried as an exact project name and did not match one. Look the package up by its exact PyPI name in the direct-lookups field.
Why does an npm search for a phrase return packages that have nothing to do with it?
npm matches each word separately and ranks the union. Use a single word, and read the note on the summary row, which flags this on every multi-word query.
Does isDeprecated being empty mean the package is fine?
No. Empty means the registry has no deprecation mechanism at all — PyPI and crates.io do not. false means npm has one and this package's current version is not flagged.
A crate shows version 0.5.6 but crates.io's API says max_version is 0.0.0. Which is right?
Both. max_version reads 0.0.0 once every version of a crate has been yanked, which is the case for whatfeatures. The version field reports default_version, the one cargo would actually resolve to, and isYanked is true.
How many packages can one run handle? Searches are capped per query per registry by Maximum results per query, up to 500, and the registries' own ceilings apply on top — 1000 for a crates.io relevance search. Direct lookups are unlimited. Registry requests run concurrently, with crates.io throttled to honour its published crawler policy.
Is a query that finds nothing charged? A search run is charged once per registry per query whether it matches or not, because the query was still run. Packages are charged only when returned, so a zero-result query costs one search run and nothing else.