# npm Download Trends & Package Comparison (`capable_santolina/download-trends-for-npm-packages`) Actor

Compare npm package downloads (daily, weekly or monthly) for any list of packages: growth %, week-over-week and month-over-month change, a 0-100 index, market share, version adoption and outage-gap correction. Public npm downloads API only. Independent tool, not affiliated with npm, Inc. or GitHub.

- **URL**: https://apify.com/capable_santolina/download-trends-for-npm-packages.md
- **Developed by:** [Doyle Pounders](https://apify.com/capable_santolina) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.40 / 1,000 package analyseds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Package Download Trends & Comparison

> Independent tool. Not affiliated with, sponsored or endorsed by npm, Inc. or GitHub. npm is a registered trademark of npm, Inc.

> Current as of 2026-10-26. Behaviour, limits and data-source rules were checked against the sources linked below on that date.

Compare download counts for any list of packages in the npm public registry. You get growth % (week over week, month over month, second half vs first half), a 0–100 comparison index, market share, rank, momentum labels, version adoption and clean daily / weekly / monthly series. Everything comes from npm's official public downloads API ([docs](https://github.com/npm/registry/blob/main/docs/download-counts.md)). No login, no proxies, no scraping of npmjs.com pages.

**Why use it instead of calling the API yourself?**

- **Outage-gap correction:** npm's downloads API reports **0 downloads for every package** on some days (processing outages). The run below found **13 such days in 120 days**, e.g. 2026-10-13…15 and 2026-10-19…21. Raw sums and week-over-week numbers built on them are wrong. This Actor detects those days and interpolates them by default (marked `estimated`). It can also exclude them or keep the raw zeros.
- **No fake zeros at the end:** npm returns `0` (not an error) for days it hasn't published yet. Each run checks the last published day first and never goes past it (`dataAvailableThrough`).
- **Bulk-aware and polite:** up to 128 unscoped packages per request, automatic chunking past npm's 365-day/18-month limits ([npm download-counts docs](https://github.com/npm/registry/blob/main/docs/download-counts.md)), scoped packages one by one, and at most 1 request per second as npm's [crawler policy](https://docs.npmjs.com/policies/crawlers) asks.

### Real run (2026-10-26, about 9:55 PM America/Chicago = 2026-10-27 02:55 UTC)

Input (`demo/INPUT.json`):

```json
{
  "packages": [
    "react",
    "vue",
    "svelte",
    "@angular/core",
    "solid-js",
    "preact",
    "htmx.org",
    "https://www.npmjs.com/package/@sveltejs/kit",
    "this-package-does-not-exist-zz9q",
    "Not A Valid Name"
  ],
  "lookbackDays": 120,
  "granularity": "weekly",
  "gapHandling": "interpolate",
  "includeSeries": true,
  "includeVersions": true,
  "includeMetadata": true
}
```

Result: 8 `ok` packages, 1 `not_found` and 1 `invalid_name`. It made 20 API requests in 19.2 s (Actor time). Data ran through 2026-10-25.

| Rank | Package | Downloads (gap-corrected) | Index 0–100 | Share % | 30 d vs prev 30 d % | Momentum |
|---|---|---|---|---|---|---|
| 1 | react | 3,127,868,076 | 100 | 72.06 | 16.08 | growing |
| 2 | preact | 579,777,786 | 18.5 | 13.36 | 23.42 | growing |
| 3 | vue | 280,931,057 | 9 | 6.47 | 16.34 | growing |
| 4 | @angular/core | 107,772,792 | 3.4 | 2.48 | 4.97 | stable |
| 5 | svelte | 105,568,572 | 3.4 | 2.43 | 23.74 | growing |
| 6 | solid-js | 85,915,463 | 2.7 | 1.98 | 70.82 | surging |
| 7 | @sveltejs/kit | 48,710,485 | 1.6 | 1.12 | 28.61 | surging |
| 8 | htmx.org | 4,389,454 | 0.1 | 0.1 | 16.1 | growing |

The run also writes `SERIES.csv` to the key-value store: one column per package, ready for Google Sheets or Excel charts.

### Output

One row per input. This is the `solid-js` row from the run above, unedited except that `series`, `suspectedGapDays` and the version lists are trimmed (`"..."`):

```json
{
  "input": "solid-js",
  "package": "solid-js",
  "packageUrl": "https://www.npmjs.com/package/solid-js",
  "startDate": "2026-06-28",
  "endDate": "2026-10-25",
  "granularity": "weekly",
  "gapHandling": "interpolate",
  "status": "ok",
  "days": 120,
  "totalDownloads": 85915463,
  "avgDailyDownloads": 715962.2,
  "medianDailyDownloads": 611085,
  "peakDownloads": 1816203,
  "peakDate": "2026-10-22",
  "minDownloads": 191119,
  "minDate": "2026-07-04",
  "firstDayDownloads": 224820,
  "lastDayDownloads": 910328,
  "trendChangePct": 99.68,
  "last7Downloads": 9197325,
  "prev7Downloads": 9227182,
  "weekOverWeekPct": -0.32,
  "last30Downloads": 36107837,
  "prev30Downloads": 21138438,
  "monthOverMonthPct": 70.82,
  "slopePctPer30d": 34.64,
  "weekendToWeekdayRatio": 0.517,
  "zeroDays": 0,
  "rawTotalDownloads": 72341363,
  "momentum": "surging",
  "suspectedGapDays": [
    "2026-07-12",
    "2026-08-14",
    "2026-09-03",
    "..."
  ],
  "peakFollowsGap": true,
  "versions": {
    "window": "npm \"last-week\" per-version preset (npm does not return its dates; it can lag the daily data)",
    "versionsTotalDownloads": 5235386,
    "versionCountWithDownloads": 523,
    "topVersions": [
      {
        "version": "1.9.17",
        "downloads": 1458725,
        "sharePct": 27.86
      },
      {
        "version": "1.9.15",
        "downloads": 1327675,
        "sharePct": 25.36
      },
      "..."
    ],
    "majorVersionShares": [
      {
        "major": "1",
        "downloads": 5067928,
        "sharePct": 96.8
      },
      {
        "major": "2",
        "downloads": 166093,
        "sharePct": 3.17
      },
      "..."
    ]
  },
  "metadata": {
    "latestVersion": "1.9.17",
    "description": "A declarative JavaScript library for building user interfaces.",
    "license": "MIT",
    "homepage": "https://solidjs.com",
    "repository": "https://github.com/solidjs/solid",
    "deprecated": null,
    "dependencyCount": 3,
    "hasTypes": true,
    "moduleType": "esm",
    "nodeEngine": null
  },
  "relativeIndex": 2.7,
  "shareOfComparisonPct": 1.98,
  "rank": 6,
  "packagesCompared": 8,
  "dataAvailableThrough": "2026-10-25",
  "lastPeriodPartial": false,
  "series": [
    {
      "period": "2026-06-22",
      "downloads": 224820,
      "days": 1,
      "estimatedDays": 0,
      "avgDailyDownloads": 224820,
      "partial": true,
      "index": 0.1
    },
    {
      "period": "2026-06-29",
      "downloads": 2643213,
      "days": 7,
      "estimatedDays": 0,
      "avgDailyDownloads": 377601.9,
      "partial": false,
      "index": 1.2
    },
    "...",
    {
      "period": "2026-10-19",
      "downloads": 9197325,
      "days": 7,
      "estimatedDays": 3,
      "avgDailyDownloads": 1313903.6,
      "partial": false,
      "index": 4.1
    }
  ]
}
```

Free rows look like this:

```json
{"input": "this-package-does-not-exist-zz9q", "package": "this-package-does-not-exist-zz9q", "packageUrl": "https://www.npmjs.com/package/this-package-does-not-exist-zz9q", "startDate": "2026-06-28", "endDate": "2026-10-25", "granularity": "weekly", "gapHandling": "interpolate", "status": "not_found", "error": "Package not found in the npm registry (or no download data for this range)."}
```

Field notes:

- `totalDownloads` is gap-corrected. `rawTotalDownloads` is npm's raw sum, which counts gap days as 0.
- `peakFollowsGap: true` means the peak is the day right after an outage. npm sometimes books part of the missed downloads on that day, so treat that peak with care. In the run above, 7 of the 8 packages "peaked" on 2026-10-22, just after the 10-19…21 outage.
- `series[].partial: true` marks a weekly or monthly bucket cut by the date range, typically the first and/or last bucket. Compare `avgDailyDownloads` rather than `downloads` for those buckets. `lastPeriodPartial` tells you whether the newest bucket is partial.
- `versions` comes from npm's per-version "last-week" endpoint. npm doesn't return which 7 days that covers, and its "last-week" presets can lag the daily data, so use it for **shares**, not exact weekly totals.
- `metadata` holds only non-personal fields from the registry (version, description, licence, repository, homepage, deprecation, dependency count, types, ESM). Author and maintainer names or emails are **never collected**.
- `weekendToWeekdayRatio` is a rough signal: low values usually mean CI and automated installs dominate. That's a heuristic, not a measurement.
- Statuses: `ok` (charged), plus free statuses: `not_found`, `invalid_name`, `skipped` (your Max cost per run was reached) and `error`.

### Input

| Field | Default | Notes |
|---|---|---|
| `packages` | `["react","vue"]` | Names (`react`, `@angular/core`, `react@18`), `npm i x` strings or npmjs.com URLs. Duplicates removed. Empty input runs the default. |
| `lookbackDays` | 90 | Used when `startDate` is empty. npm data starts 2015-01-10. |
| `startDate` / `endDate` | – / last published day | `endDate` after the last published day is clamped (logged, `endDateClamped` in SUMMARY). |
| `granularity` | `daily` | `weekly` (Monday start) or `monthly`. Metrics always use daily data. |
| `gapHandling` | `interpolate` | `exclude` or `keep`. |
| `includeSeries` / `includeVersions` / `includeMetadata` | true | The last two cost one extra request per package each. |
| `contactInfo` | – | Optional. Your contact, appended to the User-Agent. |

### Limits and tips

- Speed: about 1 request per second. 100 packages over ≤ 365 days with versions and metadata ≈ 1 bulk request + 200 extra requests ≈ 3.5 minutes (est.). Turn off versions/metadata for faster runs.
- Memory: series are held in memory to compute the cross-package index. Above about 500,000 package-days (e.g. 200 packages × 7 years daily), the run logs a warning. Use weekly/monthly granularity, fewer packages per run, or 1 GB+ memory.
- Downloads ≠ users. npm counts tarball downloads, including CI, mirrors and bots ([npm docs](https://github.com/npm/registry/blob/main/docs/download-counts.md)).
- Gap detection is heuristic. A day is a gap when it is zero for ≥ 80% of the active packages in the run, or zero amid steady traffic for a single package. Very small packages are never "corrected".
- Days before a package's first download in the window are real zeros and are never corrected. The same goes for a package's own trailing zeros (removed, renamed or abandoned). Only zero days between real downloads, and run-wide outage days, are treated as gaps, and corrected days are flagged `estimated`. `rawTotalDownloads` always keeps npm's raw sum.
- The end date is npm's latest published day, probed at run start. Only if that probe fails does the Actor fall back to two days ago and trim trailing days that are 0 for every package.

### Pricing (pay per event)

`package-trend` once per `ok` row. All other rows are free. The run stops charging at your **Max cost per run**. Packages beyond it come back as free `skipped` rows, and every input still gets a row. See `PRICING.md`.

### Legal and data notes

- Data source: npm's public downloads API and registry, used at ≤ 1 request/second with an identifying User-Agent: `DownloadTrendsBot/0.1.0 (+https://apify.com/capable_santolina/download-trends-for-npm-packages; pounderswork@mail.grokbot.com) apify-sdk/3 node/<version>`. See npm's [crawler policy](https://docs.npmjs.com/policies/crawlers) and [Open-Source Terms](https://docs.npmjs.com/policies/open-source-terms). `api.npmjs.org/robots.txt` returns 404 (no restrictions), and npmjs.com web pages are never requested.
- No personal data: maintainer and author fields are dropped.
- Trademark: "npm" is used only to describe the data source, following npm's [logo and usage policy](https://docs.npmjs.com/policies/logos-and-usage/). The Actor title deliberately leaves out the npm mark. npm is a registered trademark of npm, Inc. No npm logo is used.
- Contact / takedown: pounderswork@mail.grokbot.com.

# Actor input Schema

## `packages` (type: `array`):

npm package names such as 'react', '@angular/core' or 'react@18' (the version is ignored), or npmjs.com package URLs. Duplicates are removed. Each valid package found in the registry is one billable result. If empty, the Actor runs the example \['react', 'vue'].

## `lookbackDays` (type: `integer`):

Used when Start date is empty: how many days back from the end date to fetch. 90 by default; up to several years (the npm API has data from 2015-01-10).

## `startDate` (type: `string`):

YYYY-MM-DD. Optional; overrides Lookback. Dates before 2015-01-10 are clamped.

## `endDate` (type: `string`):

YYYY-MM-DD (inclusive, UTC). Defaults to the last day npm has published (checked at run start; usually 1-2 days ago). Later dates are clamped, never zero-filled (see dataAvailableThrough in the output).

## `granularity` (type: `string`):

How the output series is bucketed. Metrics are always computed on daily data. Weekly buckets start on Monday. Buckets cut by the date range are marked partial: true (with avgDailyDownloads for fair comparison).

## `gapHandling` (type: `string`):

The npm API sometimes reports 0 downloads for every package on a day (processing outages). 'interpolate' fills those days linearly and marks them estimated (recommended). 'exclude' drops them. 'keep' leaves the raw zeros.

## `includeSeries` (type: `boolean`):

Add the full series (with a 0-100 index per point, relative to the highest point of all packages in the run) to each result.

## `includeVersions` (type: `boolean`):

Add per-version downloads from npm's 'last-week' versions endpoint: top versions and major-version shares. npm does not say which 7 days these cover and the figure can lag the daily series, so treat them as shares, not as exact weekly totals. One extra API request per package.

## `includeMetadata` (type: `boolean`):

Add latest version, description, license, repository, homepage, deprecation and dependency count from the public registry. Author and maintainer details are never collected. One extra API request per package.

## `contactInfo` (type: `string`):

Optional. Your email or URL, added to the User-Agent next to the Actor's own contact so npm can reach whoever runs heavy jobs.

## Actor input object example

```json
{
  "packages": [
    "react",
    "vue",
    "svelte",
    "@angular/core",
    "solid-js"
  ],
  "lookbackDays": 90,
  "granularity": "daily",
  "gapHandling": "interpolate",
  "includeSeries": true,
  "includeVersions": true,
  "includeMetadata": true
}
```

# Actor output Schema

## `results` (type: `string`):

All dataset rows (one per input). Opens on the 'overview' view; switch views in Console.

## `summary` (type: `string`):

JSON run summary: date range, data-through date, ranking, fastest-growing packages, run-wide gap days, non-ok inputs, API request count and the User-Agent used.

## `seriesCsv` (type: `string`):

Wide CSV (period x package downloads) for spreadsheets; written only when at least one package returned status ok.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "packages": [
        "react",
        "vue",
        "svelte",
        "@angular/core",
        "solid-js"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("capable_santolina/download-trends-for-npm-packages").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "packages": [
        "react",
        "vue",
        "svelte",
        "@angular/core",
        "solid-js",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("capable_santolina/download-trends-for-npm-packages").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "packages": [
    "react",
    "vue",
    "svelte",
    "@angular/core",
    "solid-js"
  ]
}' |
apify call capable_santolina/download-trends-for-npm-packages --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,capable_santolina/download-trends-for-npm-packages"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/bAHWnlkTeFDikockZ/builds/hfh0xcTBVpRnloysj/openapi.json
