Build1 publisher3 min readPublished
npm is the only one of three registries that will tell an API client who to pay
A developer checked npm, PyPI and crates.io for machine-readable funding data using nothing but their public APIs. npm has a declared field whose value arrives in three shapes, PyPI has hand-typed labels, and the sparse index has neither.
The Engineer · Build desk
What happened
- A developer who maintains a cross-registry metadata normalizer set out to find who to pay for a package he depends on, using nothing but each registry's own public API.
- npm carries a declared funding key that the npm fund command reads across a dependency tree, and 9 of the 25 well-known packages he checked had one.
- PyPI has no reserved funding key, so the pointer is whatever label a maintainer types: Django used Funding, while Flask and Click used Donate.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- capability A sponsorship pass that reads the registry rather than a README is buildable for JavaScript dependency trees and, on this evidence, for neither of the other two ecosystems.
- contradiction The registry with the weaker data scored the higher hit rate, so a coverage percentage measures maintainer habits, not whether a client can query the answer.
- cost Rust support costs a tool author a second endpoint outside the resolver path, plus a network policy that permits reaching it, before any money moves.
- constraint Funding routed through foundations and company payrolls does not appear in the field at all, so a field-driven tool skips those packages even though they are funded.
npm's funding key is a real field with a command behind it. The post describes it as an implemented RFC, and `npm fund` walks the whole dependency tree reading exactly that key [3]. The key is defined; its value can be any of three shapes. A request for `express` returned an object with `url` and `type` [4]. `eslint` returned a bare string, and `uuid` returned an array of two GitHub Sponsors URLs, one per maintainer, because more than one person wanted credit [6]. "If you're writing code against this field, typeof matters before .url does," the author wrote [7].
PyPI has no equivalent key. `project_urls` is free-form key/value text a maintainer types by hand, and neither pip nor the registry looks for a funding entry in it [9]. Matching on Funding, Donate, sponsor and support found something in 8 of 20 packages [11]. That is 40 percent; the npm sample came in at 36 percent [17]. The higher hit rate came from a string match against a human's word choice. A maintainer who wrote Support Us or Ko-fi slips past it, and the author says he had no way to separate a label he did not match from a genuine non-declaration without reading every key by eye [12].
For Rust the data is not on the path the resolver uses. The sparse index is the flat-file format cargo reads on every dependency resolution, and every crate the author checked returned the same ten-or-so keys, with no homepage, repository or funding among them [13]. The richer metadata lives on crates.io's REST API, a separate endpoint that the author's sandbox egress policy blocked that week and that most tooling built around the sparse index never calls [14]. Because the endpoint was unreachable, the post does not establish whether a funding field exists there [14].
Two funding channels for one project also do not have to agree. `chalk` declares a GitHub Sponsors URL in its package.json `funding` field, while chalk's repository has no `.github/FUNDING.yml`, the file GitHub reads to show the Sponsor button on a repo page [15]. A client reading the registry and a human reading the repo page see different answers, and nothing in either system reconciles them [15].
Now the part that decides whether the 36 percent means anything to you. The author says his samples are small and skewed toward famous packages [16]. For that rate to hold on a real lockfile, the tree would have to look like a list of household names, and most trees are mostly transitive packages nobody checks by hand. Sixteen of the 25 npm packages had no funding field [18]. Five of them are react, vue, typescript, jest and next, which the author attributes to foundation or company backing that does not route through a package.json field a generic npm client would read [8].
What to watch
- A repeat of the crates.io REST API check from a network without the egress block, to see what funding metadata is actually served there.
- Whether PyPI reserves a funding key in project_urls or adds a typed metadata field that pip and the registry both recognise.
- Whether npm constrains the funding value to one shape, or client authors keep branching on string, object and array.