# The Adoption-Share Count Reference: What Each Ecosystem Number Measures

*Take any ecosystem count, name the population it measures and its series breaks, and decide if it supports a cross-package share, a single-package trend, or neither.*

- Canonical URL: https://www.refolk.ai/guides/ecosystem-count-adoption-share-reference
- Pillar: Market and talent intelligence
- Format: Reference
- Published: 2026-10-16
- Last reviewed: 2026-10-16
- Reading time: 20 min
- Keywords: how to measure framework market share from npm downloads, pypi download counts vs dependents adoption, open source adoption metrics for market analysis, github innovation graph developers by language economy, deps.dev dependent count meaning, compare library popularity across ecosystems

## Key takeaways

- Registry downloads count file fetches by people, CI, mirrors, and bots, so they can track one package's own trend but cannot carry a defensible share between rival packages.
- npm silently caps non-bulk download queries at 18 months on an HTTP 200, and in TanStack's case a single request returned 585 million downloads where chunked requests returned 744 million, a 27% gap.
- PyPI stopped counting .metadata sidecar requests on 2026-08-24, which dropped per-package daily metrics by 20 to 40% with no change in real usage, so any trend crossing that date must be re-filtered or flagged.
- A deps.dev dependentCount for one version only counts dependents whose current resolution lands on exactly that version, which is why serde showed 0 on its default version and 60,723 on 1.0.210; query every version and sum.
- Each package release triggers 100 to 150 automated downloads from mirrors, so a rival that ships often gains download share mechanically unless publish spikes are stripped.
- In Refolk's index, 324,890 US profiles list React.js as a skill against 40,583 for Vue.js, giving React 88.9% of the combined US skill population, a human count that mirrors and CI cannot inflate.

This reference is for strategy, research, and talent-intelligence teams who need to say in a review how much of a category one framework, library, or open-source product holds against its rivals, and whether that share is rising. For each public ecosystem count it gives the population the count actually measures, what inflates or deflates it, where its time series breaks, and whether it can carry a cross-package share, a single-package trend, or neither. Each section stands alone, so jump to the count you have in front of you.

## Which ecosystem counts can support an adoption-share claim?

Only counts that measure the same population, collected the same way for every rival, can carry a cross-package share. In practice that means dependent counts summed across versions within one ecosystem, plus developer populations pulled on the same date. Registry downloads can show one package's own trend, but they are not a defensible share metric between packages.

Two terms come up throughout:

- **Cross-package share**: one product's count divided by the category total, for one metric on one date.
- **Single-package trend**: one product's count compared with its own history.

A biased count can still give you a usable trend, as long as the bias stays the same over time. It can't give you a share if the bias differs between rivals, and most download biases do. They scale with release frequency, monorepo structure, and how often a resolver touches the package.

| Count | Population it measures | Cross-package share | Own trend |
|---|---|---|---|
| npm downloads | Fetches by people, CI, mirrors, bots | No | Yes, chunked and de-spiked |
| PyPI downloads | Distribution-file fetches, mirrors included | Weak, post-break only | Yes, break flagged |
| Docker Hub pulls | Manifest requests, no-op checks included | No | Weak, break flagged |
| deps.dev dependents | Packages resolving to a version | Yes, one ecosystem, all versions | Yes |
| Innovation Graph developers | Unique public pushers by language | Yes, but shares overlap | Yes, quarterly |
| Stars | Accounts that starred, some fake | No | Attention only |

#### Where each count is valid

Horizontal axis runs from Not comparable across packages to Comparable across packages. Vertical axis runs from Unstable own history to Stable own history.

| Quadrant | What it means |
| --- | --- |
| Trend only | npm and PyPI downloads, after chunking, break flags, and spike removal |
| Share and trend | deps.dev dependents summed across versions, Innovation Graph pushers |
| Neither | Stars and Docker Hub pulls; use as context, never as share |
| Snapshot share only | Same-date skill-profile counts; re-pull on a fixed calendar to build a trend |

*A count earns a place in a share table only if it is comparable across packages; stability over time alone qualifies it for a trend line.*

> **Rule:** Every share names its population
>
> Never report a share without the population, the ecosystem, and the pull date. "X% of npm direct dependents, pulled on a stated date" holds up in a review. "X% market share" does not.

## Registry downloads: what npm, PyPI, and Docker Hub count

A registry download is a logged file fetch, not a person installing software, and each registry draws that line in a different place. npm does little to filter automation. PyPI has counted only distribution files since 2026-08-24. Docker Hub counts a pull even when no layer is transferred.

Because each registry defines a download differently, npm and PyPI download counts are never comparable with each other. Compare library popularity across ecosystems with human counts (see the developer-population section), not with download totals.

### npm downloads

npm's own documentation calls its download stats naive by design and says little effort goes into filtering automated bot traffic. A CI run, a mirror sync, and a developer's install all count the same.

Retrieval limits, from the npm download-counts documentation:

- Bulk queries return at most 128 packages and at most 365 days at a time.
- Scoped packages (names like `@tanstack/react-query`) are not supported in bulk queries. Fetch them one at a time.
- All other queries return at most 18 months of data.
- The earliest date with data is January 10, 2015.
- Per-version download counts cover only the previous 7 days.

The 18-month cap is applied silently. In one documented request, a range of 2020-01-01 to 2026-08-10 returned 547 days beginning 2025-02-10, with an HTTP 200 and no warning.

> **Watch out:** An HTTP 200 does not mean you got the range you asked for
>
> Count the days in every returned array before summing. A long range that comes back truncated looks like a complete "all-time" total, and the error always undercounts the oldest packages.

**27% - Gap between a single npm request and chunked requests for one package**

A single request for @tanstack/react-query returned 585 million downloads; chunked requests returned 744 million.

The gap is systematic, not random. The cap cuts history only from packages older than 18 months, so an "all-time" comparison understates incumbents and flatters young rivals. The fix TanStack published is to split the range into chunks of about 500 days or less and add them up.

Two more npm traits matter when you calculate share:

- **Monorepos.** TanStack adds up downloads for every npm package published from the repository. Decide whether each rival counts as one package or as a whole repo, then apply that choice to every rival.
- **Version history.** The public API gives per-version counts for only 7 days, so you can't rebuild which major version drove growth over a longer period.

### PyPI downloads

PyPI's CDN logs are parsed and anonymised by its linehaul functions and published to a public BigQuery dataset. No bot or mirror filter is documented. Anyone can run a full mirror with bandersnatch, and a full mirror downloads every file of every release, so each new file gets fetched by mirrors whether or not anyone uses it.

The comparability break: since 2026-08-24, a request has to end in `.whl`, `.tar.gz`, or `.zip` to produce a download record. Before that, requests for PEP 658 `.metadata` sidecar files were estimated at about 40% of logged traffic. Resolvers fetch these files without installing anything. Before the change, PyPI's daily totals ran from roughly 4.5 billion on weekends to a little over 7 billion midweek. The break is permanent and was not applied retroactively, and pypistats.org daily metrics fell by 20 to 40% for almost every package.

| Date | All objects | Distributions only | Non-distribution share |
|---|---|---|---|
| 2026-08-18 | 77,477,790 | 47,268,787 | 39.0% |
| 2026-08-21 | 69,652,309 | 42,467,783 | 39.0% |
| 2026-08-24 | 64,241,532 | 44,012,651 | 31.5% |
| 2026-08-25 | 46,619,401 | 46,619,401 | 0.0% |

These rows come from PyPI's own post on the change. The package is believed to be urllib3, but that isn't confirmed. Separately, urllib3 averaged about 74 million logged downloads a day before the change, and 39% of them were not distributions. Read the table this way: the distributions-only figure for 08-25 falls inside the 08-18 to 08-21 range. Real usage stayed flat, and only the counting changed.

What this does to share: the per-package drop ranged from 20% to 40%, so before 2026-08-24 the inflation was uneven. Packages that resolvers touch often were inflated most, which means pre-break cross-package shares lean toward them. To stitch a series across the break, re-query the pre-break BigQuery rows and keep only file names ending in `.whl`, `.tar.gz`, or `.zip`. That method follows from the filename rule. PyPI has not published an official stitching procedure, so put a note about the method beside any chart that uses it.

### Docker Hub pulls

Docker Hub defines a pull as up to two `GET` requests on registry manifest URLs. `HEAD` requests are not counted. A pull still counts when the client already has every image layer and nothing is downloaded. So a cluster with an "Always" pull policy, or many nodes behind one NAT address checking for updates, adds pulls without adding any users.

- The unauthenticated limit is 100 pulls per IPv4 address or IPv6 /64 subnet per 6 hours. That limit is tied to network addresses, not people.
- The definition of a pull changed on 2020-11-02, so flag that date on any series that crosses it.
- No public per-image filter exists for no-op pulls.
- Whether pull counts get gamed has not been established publicly. That doesn't mean they are clean.

Verdict: never use pulls for a share. At most, use them as context for one image's trend within a single definition period.

> **Note:** crates.io and Maven Central downloads
>
> I have not established the counting rules for either registry. Read each registry's own documentation on what counts as a download and whether mirrors are filtered before you build a series, and keep both out of any share that mixes ecosystems.

## Dependent counts: deps.dev, npmjs.com, and GitHub "Used by"

A dependent count measures how many other packages declare a dependency on a product, so it counts code that chose the product rather than machines that fetched it. Within one ecosystem it is the strongest cross-package share metric, provided you sum across every version and keep direct and indirect counts separate.

### deps.dev dependentCount

What the fields mean, from the deps.dev API documentation (currently v3alpha):

- **dependentCount**: the number of packages known to depend on this package version, directly or indirectly. It can be less than the direct and indirect counts added together, so don't add them.
- **Direct and indirect dependent counts**: reported separately. Use the direct count as your main share figure, because it reflects a deliberate choice by another maintainer.
- **Coverage**: npm, Cargo, Maven, and PyPI.

Why the version you query changes the answer: a practitioner probe of about 150 queries found that dependentCount for version X counts only dependents whose current resolution lands on exactly X. Ecosystems with open-ended version ranges pile their dependents onto one version. Maven pins exact versions, so its counts spread out, and guava's dependents were spread across six versions. Querying only the default version therefore compares ecosystems unfairly. serde returned a dependentCount of 0 for 1.0.229, its default version, and 60,723 for 1.0.210.

What the count shows:

- **It separates real libraries from noise.** In one practitioner benchmark, the median directDependentCount was 36 for junk packages and 1,032 for real libraries.
- **Most packages have a thin tail.** In a study of 175,000 npm packages, 27% had no direct runtime dependents and 62% were not used in any external repository.

Where it misleads: it counts published packages, so an application that is never published to a registry doesn't show up. A category dominated by end-user applications will be undercounted compared with one dominated by libraries.

### npmjs.com dependents

npm support confirmed that the dependents figure on the npm website counts direct runtime dependencies only. Tools that are usually installed as development dependencies will look smaller there than in a count that includes them. Don't mix this figure with deps.dev counts in one share table.

### GitHub "Used by"

I have not established how GitHub defines this figure. Don't use it in a share until you have confirmed what it counts and whether it uses the same rule across the rivals you are comparing.

## Developer populations: Innovation Graph pushers and skill profiles

Developer-population counts measure people, so mirrors, CI, and release spikes can't inflate them. The GitHub Innovation Graph counts unique public pushers by language for each economy each quarter. Skill-profile counts measure how many professionals list a technology.

### GitHub Innovation Graph

Definitions, from the Innovation Graph methodology:

- **Developers by language**: the number of unique developers who made at least one git push to a repository containing that language.
- **Git push**: one push can contain several commits, so the figures count pushers, not commit volume.
- **Granularity**: quarterly back to January 2020, published one quarter late.
- **Scope**: public activity only.
- **Suppression**: an economy's figure appears only when 100 or more unique developers did the activity in the period.
- **Location**: assigned from IP addresses. One secondary source describes the exact rule, but I haven't verified that rule against GitHub's datasheet.

One study using this data covered 163 economies and 150 languages. Its most important quirk for share work: one developer counts in every language present in a repository they push to, so language shares don't add up to 100%. Treat each language as its own population. Language counts can't separate rival frameworks within one language. Use topic counts where they exist, and say plainly which you used.

### Skill-profile counts

Skill counts come from [Refolk](/), which indexes public professional profiles across LinkedIn, GitHub, and the open web. A listed skill shows that the person claims familiarity. It doesn't show current daily use, and it misleads when profiles are stale or packed with keywords. Because that bias applies roughly equally to rivals in the same category, a same-date ratio holds up better than the absolute counts.

| Skill | Country | Profiles in Refolk's index | Derived |
|---|---|---|---|
| React.js | United States | 324,890 | Baseline |
| Vue.js | United States | 40,583 | React is 8.0x Vue |
| React.js | Germany | 35,187 | US is 9.2x Germany |

**88.9% - React's share of the combined React and Vue skill population in the US**

In Refolk's index, 324,890 US profiles list React.js and 40,583 list Vue.js, a human count that mirror and CI traffic cannot touch.

Only the counts are distributions. Any list of titles or employers you pull alongside them is a sample, so don't report it as a mix. I haven't established a matching registry ratio for React and Vue. If you want to compare a human ratio with a registry ratio, pull both on the same date.

If the counts disagree, the quickest tiebreak is to ask the maintainers what they see in their own issue traffic and user reports.

Ask me this: `Maintainers of JavaScript state-management libraries such as Zustand, Jotai or Redux Toolkit, active on GitHub` - [run the search](https://www.refolk.ai/start?q=Maintainers%20of%20JavaScript%20state-management%20libraries%20such%20as%20Zustand%2C%20Jotai%20or%20Redux%20Toolkit%2C%20active%20on%20GitHub).

*Returns named maintainers and contributors with their GitHub activity and professional profiles, so you can check a share read with the people who maintain the libraries.*

## Stars, forks, and other repository counts

Stars measure attention from accounts, and some of those accounts are fake. Stars are not a cross-package share metric. At most they show a short-term attention trend for one repository.

- **What stars measure**: accounts that clicked star at some point. They don't measure use, installs, or dependents.
- **How they mislead**: StarScout flagged 18,617 repositories and 301,000 accounts, about 3.81 million fake stars after post-processing (6 million suspected before it). Fake-star campaigns gave a promotion effect for less than two months.
- **Check**: look at the monthly star distribution for sharp spikes, and compare against the package's dependent count. A high star count with very few dependents is the signature of attention without adoption.
- **Forks**: I have no established counting rule or published study to cite on forks as an adoption signal. Leave them out of share and trend work until you have checked locally what a fork means for your category.

## How to compute a defensible adoption share

Build each metric's share separately, from a fully resolved package list and clean series, then compare the orderings. The procedure takes about 6 to 9 hours for a category of a few rivals, mostly spent on download pulls and dependents.

#### Adoption-share procedure

1. **Resolve the category to package names** - Map every rival to all its published names: scoped npm names, sub-packages, and the repository. Takes about 1 hour; done when there is one row per product with every name listed.
2. **Pull downloads in chunks** - For npm, use ranges of 500 days or less, batches of at most 128 unscoped names, and fetch scoped names one at a time. Takes 1 to 3 hours; done when the daily series sum equals the chunk totals.
3. **Flag every series break** - Mark PyPI's counting change on 2026-08-24 and Docker Hub's pull-definition change on 2020-11-02. Takes 30 minutes; done when every series carries its break flags.
4. **Strip publish spikes** - Overlay release dates and remove the 100 to 150 automated downloads that follow each release. Takes about 1 hour; done when the trend no longer tracks release cadence.
5. **Pull dependents across all versions** - Query deps.dev for every version and sum, rather than the default version alone. Takes 1 to 2 hours; done when direct and indirect counts are recorded per package.
6. **Pull developer populations** - Collect Innovation Graph pushers by language or topic and skill counts from Refolk's index, on one date. Takes about 1 hour; done when each rival has a dated human figure.
7. **Compute share per metric and reconcile** - Calculate one share per metric, never a blend, and label each with population, ecosystem, and date. Done when every share is labelled and any disagreement in ordering is explained.

On order of operations, sources differ. TanStack adds up all-time totals first. An earlier Refolk guide strips publish spikes before trusting any trend. For share work I strip spikes first, because spikes scale with release cadence, which differs between rivals. Summing first bakes that difference into the total.

## How adoption-share counts go wrong

Most bad share claims come from one of eight failures, and each looks plausible on a slide. The table at the top tells you which counts are valid. This section tells you how a valid count gets corrupted while you collect it.

### 1. The silently truncated npm range

- **Looks like**: a flat "all-time" figure for an old package that hasn't grown in years.
- **Why**: non-bulk queries return about 18 months of data with an HTTP 200 and no warning.
- **Check**: count the days in the returned array, and re-pull in chunks of 500 days or less.

### 2. A PyPI trend that crosses 2026-08-24

- **Looks like**: a 20 to 40% fall in usage.
- **Why**: `.metadata` requests stopped counting, and the drop varied by package.
- **Check**: recompute the pre-break series with the `.whl`, `.tar.gz`, `.zip` filename filter, or show two separate series.

### 3. The deps.dev default-version zero

- **Looks like**: a heavily used package with 0 dependents (serde: 0 on 1.0.229).
- **Why**: dependents count only against the exact version their current resolution lands on.
- **Check**: query several versions and compare, then sum all of them.

### 4. Monorepo sibling inflation

- **Looks like**: a small utility package ranked above the components developers actually import. One practitioner audit found `@radix-ui/rect`, at 96 million weekly downloads, ranked above components people really use.
- **Why**: sub-packages get pulled in by their siblings and inherit their volume.
- **Check**: compare against directDependentCount. Real imports show up as direct dependents.

### 5. Release cadence that looks like share gain

- **Looks like**: a rival steadily gaining download share.
- **Why**: each release triggers 100 to 150 automated mirror downloads, so a rival that ships ten times a month gains share without gaining users.
- **Check**: overlay publish dates on the download series. If the gains line up with releases, they are mechanical.

### 6. Deliberate download pumping

- **Looks like**: a new package with sudden, large download volume.
- **Why**: download counts are easy to inflate. Tenable pushed a test package to 17,000 npm downloads in about an hour from a single laptop. A package called ambar-src passed 50,000 downloads in three days with no real users.
- **Check**: compare against direct dependents. Pumped packages have volume and no dependents.

### 7. Docker no-op pulls

- **Looks like**: an image whose pulls grow with its install base.
- **Why**: "Always" pull policies and NAT clusters count update checks as pulls even when no layer is transferred.
- **Check**: no public per-image filter exists. Treat pulls as context only.

### 8. Overlapping or missing Innovation Graph populations

- **Looks like**: language shares that add up to more than 100%, or a market that seems to have no developers.
- **Why**: one developer counts in every language in a repository they push to, and economies under 100 developers are suppressed.
- **Check**: report each language as its own population, and record suppressed cells as "suppressed", not zero.

> A download share is a share of fetches, not of users. Say which one you mean before someone in the room asks.

## Reconciling counts that disagree

Expect your metrics to rank rivals differently, and report each share separately rather than averaging them. Popularity metrics only partly correlate. A study of 175,000 npm packages concluded that popularity can be measured with different, unrelated metrics.

| Metric pair | Spearman rank correlation |
|---|---|
| npm PageRank vs downloads | 0.385 |
| npm PageRank vs GitHub popularity | 0.445 |
| Downloads vs GitHub popularity | 0.567 |

Spearman rank correlation measures how closely two lists agree on order, from 1 (identical order) down. A value of 0.385 means dependency-graph centrality and downloads agree only loosely. Nobody has published a typical gap between download share and dependent share, so don't quote one. Show both and explain the gap using the failure modes above.

How to read a disagreement:

- **Downloads lead, dependents lag**: look for monorepo inflation, frequent releases, or pumping before you believe the lead.
- **Dependents lead, downloads lag**: often an older incumbent whose download history was cut off by npm's cap, or a library with many dependents that each see little traffic.
- **Human counts disagree with both**: the registry figures may be measuring machines. A same-date skill or pusher ratio is the tiebreak, because it counts people.

When you need a quick, defensible human figure for a rival set in one country, skill-population counts from Refolk let you check a registry ordering against people without building a pipeline.

**Share claim block for a strategy review**

```
Product: <product name>
Package names included: <every published name, including scoped and sub-packages>
Metric: <e.g. deps.dev direct dependents, summed across all versions>
Population measured: <e.g. npm packages whose current resolution lands on any version of the product>
Ecosystem: <npm | PyPI | Cargo | Maven | cross-ecosystem human count>
Pulled on: <YYYY-MM-DD>
Share: <X% of category total (N of M)>
Trend window: <start to end, chunking method>
Series breaks flagged: <PyPI 2026-08-24 | Docker 2020-11-02 | none>
Spike treatment: <publish spikes removed, method>
Cross-check metric: <second metric and its share>
Agreement: <agrees | disagrees, with the failure mode that explains it>
Known bias: <one sentence>
```

*One block per metric per product; never merge two metrics into one block.*

## Keeping the share series current

A share series stays defensible only if you re-check the counting rules each time you refresh it, because every source here can change its definitions without changing its endpoint. Re-check the mechanism each time rather than relying on the values printed here.

What to re-check at each refresh:

- **npm**: the download-counts documentation for the 128-package bulk limit, the 365-day and 18-month caps, and scoped-package support.
- **PyPI**: PyPI's blog and the pypistats.org issue tracker for any further change to what produces a download record.
- **deps.dev**: the API is v3alpha, so re-read the field definitions for dependentCount and the direct and indirect counts.
- **Innovation Graph**: the methodology page for the suppression threshold and the location rule, and remember that the newest quarter is published one quarter late.
- **Docker Hub**: the pull definition and rate limits, since a definition change has broken the series before.
- **Skill populations**: re-pull every rival on the same date and keep the dates on file, so the snapshot share becomes a trend.

#### Before the share goes into the deck

- [ ] Every rival maps to all of its published package names, with the repo-or-package choice applied consistently.
- [ ] Every npm chunk returned the number of days requested, and the chunk totals match the daily series sum.
- [ ] Every series that crosses 2026-08-24 (PyPI) or 2020-11-02 (Docker Hub) is flagged or re-filtered.
- [ ] Publish spikes are removed, and the remaining trend no longer lines up with release dates.
- [ ] deps.dev dependents are summed across all versions, with direct and indirect counts recorded separately.
- [ ] Innovation Graph shares are presented as overlapping populations, with suppressed cells marked as suppressed.
- [ ] No share mixes download counts from different registries.
- [ ] Every share carries its population, ecosystem, and pull date, and every disagreement between metrics has a written explanation.

## Frequently asked questions

### Can I use npm downloads to measure a framework's market share?

Not on their own. npm download counts include CI runs, mirror syncs, and bot traffic, which npm does not try hard to filter, and they scale with release frequency and monorepo structure. That bias differs between rivals, so a download share mostly compares machine behaviour. Use downloads for one package's own trend after chunking and removing publish spikes, and use summed deps.dev dependents or developer populations for the share itself.

### Why did my PyPI download numbers drop by a third in late August 2026?

PyPI changed what it counts. From 2026-08-24, only requests for files ending in .whl, .tar.gz, or .zip produce a download record, so .metadata sidecar requests, estimated at about 40% of logged traffic, stopped counting. Per-package daily metrics fell 20 to 40%. Real usage did not change. Re-query the pre-break rows with the same filename filter, or flag the break before comparing periods.

### What does the deps.dev dependentCount actually count?

It counts packages known to depend on one specific version, directly or indirectly, whose current resolution lands on exactly that version. That is why a heavily used crate can show zero on its default version: serde returned 0 for 1.0.229 and 60,723 for 1.0.210. It may also be less than the sum of direct and indirect counts. Query every version, sum them, and keep direct and indirect counts separate.

### Do GitHub Innovation Graph language shares add up to 100%?

No. A developer counts toward every language present in any public repository they push to, so one person can appear under several languages in the same quarter. Treat each language's figure as a population on its own, not as a slice of one total. Economies with fewer than 100 developers doing the activity in a period are suppressed, so small markets show gaps rather than zeros.

### How do I compare library popularity across npm and PyPI?

Don't compare their download counts directly. Each registry has its own definition of a download and its own filters, and PyPI's definition changed on 2026-08-24. Compare within each ecosystem using summed dependents, then compare across ecosystems using human counts such as Innovation Graph pushers by language or skill-profile counts pulled on the same date, and label each figure with its population.

### Are GitHub stars a valid adoption metric for a share comparison?

No. Stars measure attention from accounts, and one study flagged about 3.81 million fake stars across 18,617 repositories and 301,000 accounts. Fake-star campaigns lifted visibility for less than two months. Stars can show a short-term attention trend for one repository at most. For adoption, check the monthly star distribution for spikes and compare against the package's dependent count.

---

*From the Refolk guide library. I revise these guides rather than replacing them, so the current version is always at https://www.refolk.ai/guides/ecosystem-count-adoption-share-reference*
