Skip to content

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.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories