惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - 司徒正美
T
The Blog of Author Tim Ferriss
F
Fortinet All Blogs
Martin Fowler
Martin Fowler
罗磊的独立博客
The GitHub Blog
The GitHub Blog
L
LangChain Blog
A
About on SuperTechFans
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
D
DataBreaches.Net
宝玉的分享
宝玉的分享
U
Unit 42
阮一峰的网络日志
阮一峰的网络日志
Last Week in AI
Last Week in AI
N
Netflix TechBlog - Medium
The Cloudflare Blog
Microsoft Azure Blog
Microsoft Azure Blog
H
Help Net Security
美团技术团队
大猫的无限游戏
大猫的无限游戏
雷峰网
雷峰网
爱范儿
爱范儿
酷 壳 – CoolShell
酷 壳 – CoolShell
MongoDB | Blog
MongoDB | Blog

TanStack Blog

TanStack + Vercel Partnership | TanStack Blog TanStack AI Enters the RC Phase | TanStack Blog Inside a TanStack Router Navigation | TanStack Blog Form v2 is here: All you need to know about the alpha | TanStack Blog Announcing TanStack Table V9 | TanStack Blog TanStack Has a New Look | TanStack Blog Introducing TanStack Markdown and TanStack Highlight | TanStack Blog We Removed React Server Components from TanStack.com | TanStack Blog We Stopped Using RSC on TanStack.com | TanStack Blog Inside TanStack Table V9 Reactivity | TanStack Blog Run Any Coding Agent in a Sandbox, With One chat() Call | TanStack Blog TanStack Start and TanStack AI Win 2026 Open Source Awards | TanStack Blog How an Underrated Refactor Saved 90% Memory Usage | TanStack Blog TypeScript Performance in TanStack Table V9 | TanStack Blog TanStack AI Beta: The Switzerland of AI Tooling Grows Up | TanStack Blog TanStack Table V9: Taking Form | TanStack Blog TanStack AI: Your MCP, your way | TanStack Blog TanStack Start Adds First-Class Rsbuild Support | TanStack Blog Introducing Experimental Workflows and Orchestrators in TanStack AI | TanStack Blog Chat UIs Are Lists Until They Aren't | TanStack Blog Structured Output That Remembers Across Turns | TanStack Blog TanStack Virtual just got a lot faster, and finally handles iOS | TanStack Blog TanStack AI now fully speaks AG-UI | TanStack Blog Stop Waiting on JSON: Stream Structured Output with One Schema | TanStack Blog Hardening TanStack After the npm Compromise | TanStack Blog Postmortem: TanStack npm supply-chain compromise | TanStack Blog Who Owns the Tree? RSC as a Protocol, Not an Architecture | TanStack Blog TanStack AI Just Learned to Compose Music | TanStack Blog Your AI Tool Calls Should Fail at Compile Time, Not in Production | TanStack Blog One Flag, Every Chunk: Debug Logging Lands in TanStack AI | TanStack Blog
How We Track Billions of Downloads: The NPM Stats API Dee...
Tanner Linsley · 2025-12-02 · via TanStack Blog

by Tanner Linsley on Dec 2, 2025.

When you're tracking download stats for an ecosystem of 200+ packages that have been downloaded over 4 billion times, you learn a few things about NPM's download counts API. Some of those lessons are documented. Others you discover the hard way.

This post is about one of those hard-learned lessons: why we can't just ask NPM for all-time download stats in a single request, and why the approach matters more than you'd think.


NPM offers two endpoints for download statistics:

The Point Endpoint (/downloads/point/{period}/{package})

This gives you a single aggregate number. Clean, simple, exactly what you want.

The Range Endpoint (/downloads/range/{period}/{package})

This gives you day-by-day breakdowns. More data, but you have to sum it yourself if you want totals.

On the surface, these should return the same numbers. The range endpoint just has more detail, right?

Not quite.


Here's what the docs say: both endpoints are limited to 18 months of historical data for standard queries.

But here's what the docs don't emphasize: when you request more than 18 months, the API silently truncates your results.

Let me show you what I mean.


I built a script to test this. Simple premise: query the same packages with both endpoints across different time ranges, and see what happens.

For short periods (7 days, 30 days, 12 months), everything worked perfectly. Both endpoints returned identical data:

Great! The endpoints match. Time to query all-time stats.


Here's where things get interesting:

Notice something? The numbers are identical for 18 months, 24 months, and all-time.

Same downloads. Same number of days (549). Both endpoints are silently capped at roughly 18 months, returning exactly 549 days of data no matter what date range you request.

This means:

  • Requesting "all downloads since 2015" gives you 18 months of data
  • The API doesn't tell you it truncated your request
  • Both endpoints fail the same way

TanStack Query has been around since 2019. React has been around since 2013. If we just asked NPM for "all-time" stats, we'd be missing years of download history.


To validate this, I ran a second test comparing single requests vs properly chunked requests:

Single Request (2019-10-25 to today):

Chunked Requests (same period, 5 chunks of ~500 days each):

The difference? 159 million downloads. That's 27% of the data completely missing from the single request approach.

You can verify this yourself using tools like npm-stat.com, which properly implements chunking and shows ~744M downloads for TanStack Query - matching our chunked approach, not the naive single-request number.


When you're tracking growth for an open source ecosystem, accuracy matters. Not for vanity metrics, but because:

  1. Sponsorship decisions are made based on real adoption numbers
  2. Contributors want to see impact from their work
  3. Companies evaluating libraries look at download trends

If we naively queried for all-time stats, we'd be reporting 585 million downloads for TanStack Query. The real number? 744 million. That's 159 million downloads (27%) missing.

For the entire TanStack ecosystem with 200+ packages? We'd be off by billions.


The solution is to break time into chunks and request each period separately:

This approach:

  • Breaks the timeline into ~17-month chunks (staying safely under the limit)
  • Fetches each chunk sequentially to avoid rate limiting
  • Sums the results to get true all-time totals
  • Includes retry logic for reliability

It's more work, but it's the only way to get accurate historical data.


After all this, you might wonder: should you use /point/ or /range/?

For all-time stats, use /range/ with chunking. Here's why:

  1. They return the same totals (when you sum the daily breakdowns from range)
  2. Range gives you daily granularity for trend analysis
  3. Range is what you need for chunking anyway (you have to sum across chunks)
  4. Both are limited to 18 months, so there's no advantage to point

The point endpoint is useful for quick spot checks or when you only need recent data. But for building a real stats system, range is the way to go.


At TanStack, we've built a sophisticated stats system that handles this properly:

  • Automatic chunking for packages created before 18 months ago
  • Rate limit handling with exponential backoff
  • Concurrent processing of 8 packages at a time (to balance speed and API limits)
  • Database caching with 24-hour TTL to avoid hammering NPM
  • Scheduled refreshes every 6 hours via Netlify functions
  • Growth rate calculation from the most recent 7 days for live animations

The full implementation is in src/utils/stats.functions.ts if you want to see how we handle the details.

Library-Level Aggregation

One interesting aspect of our system is how we track stats at the library level. Each TanStack library (Query, Table, Router, etc.) maps to a GitHub repository, and we aggregate downloads for all npm packages published from that repo.

This includes:

  1. Scoped packages: Like @tanstack/react-query, @tanstack/query-core
  2. Legacy packages: Like react-query (the pre-rebrand name)
  3. Addon packages: Like @tanstack/react-query-devtools, @tanstack/react-query-persist-client

For example, TanStack Query's library stats include:

This means our library metrics sum up all related packages, which can include addons and dependencies that might also depend on the core package. This could inflate library-level numbers since some downloads might be for packages that themselves depend on other packages in the same library.

We know this. We're keeping it simple for now.

The alternative would be dependency analysis and deduplication - figuring out which packages depend on each other and avoiding double-counting. That's a project for another day. For now, the simple aggregation gives us a reasonable approximation of ecosystem reach, even if it's not perfectly precise.

What matters is consistency: we track the same way over time, so trends and growth rates remain meaningful.


This approach lets us accurately track downloads across 203 packages (199 scoped @tanstack/* + 4 legacy packages), maintaining historical accuracy going back to 2015.

The stats you see on tanstack.com aren't guesses or estimates. They're the sum of thousands of individual API calls, properly chunked, cached, and aggregated.

When we say TanStack has been downloaded over 4 billion times, that number is real. And it's growing by millions every day.


If you're building a system to track NPM download stats:

  1. Never trust a single "all-time" request - it's capped at 18 months
  2. Use the /range/ endpoint with chunking for historical accuracy
  3. Implement retry logic - rate limiting will happen
  4. Cache aggressively - NPM's data doesn't update instantly
  5. Test your assumptions - build experiments to verify behavior

The NPM download counts API is powerful, but it has sharp edges. Understanding these limitations is the difference between showing users vanity metrics and giving them real data.


We care about this because transparency matters. When we show download stats, we want them to be accurate. When we talk about growth, we want it to be real.

The same principle applies to our libraries. We don't hide complexity behind magic. We build tools that are powerful when you need them to be, and simple when you don't.

That's the TanStack way.


Want to dive deeper into how we build TanStack? Join our Discord where we talk about architecture, API design, and the technical decisions behind the ecosystem.

Using TanStack and want to support the work? Check out our sponsors and partners page. Every contribution helps us keep building open source the right way.