npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

npmjs-api-client

v1.10.2

Published

TypeScript client for the npm Registry REST API

Readme

npmjs-api-client

CI npm version npm downloads/week npm downloads/month Bundle size License: MIT TypeScript Node.js semantic-release

TypeScript client for the npm ecosystem. Aggregates data from multiple sources into a single, chainable API — package metadata, download stats, install size, CDN usage, file contents, and resolved dependency graphs. Works in Node.js and the browser (isomorphic). Fully typed, zero runtime dependencies.

Data sources integrated:

| Source | What it provides | | --- | --- | | registry.npmjs.org | Package metadata, versions, dist-tags, search, maintainers, authenticated user/org data | | api.npmjs.org | Download counts by period, per-day breakdown, version-level stats | | packagephobia.com | Publish size and full install size including all transitive dependencies | | data.jsdelivr.com/v1 | CDN browser usage — hits by version and file | | unpkg.com | Full file tree with individual file sizes and paths | | api.deps.dev/v3 | Fully resolved dependency graph with exact versions |


Installation

npm install npmjs-api-client

Quick start

import { NpmClient } from 'npmjs-api-client';

// Public APIs — no auth required
const npm = new NpmClient();

// Private registry with auth token
const privateNpm = new NpmClient({
  registryUrl: 'https://my-registry.example.com',
  token:       'my-auth-token',
});

// Authenticated npm registry APIs
const authenticatedNpm = new NpmClient({ token: 'npm_...' });

// Custom base URLs per data source
const customNpm = new NpmClient({
  packagephobiaUrl: 'https://packagephobia.com',
  jsdelivrUrl:      'https://data.jsdelivr.com/v1',
  unpkgUrl:         'https://unpkg.com',
  depsDevUrl:       'https://api.deps.dev/v3',
});

The token is always sent to the registry, and to the downloads API only when it shares the registry's origin or both are npm's defaults — a private registry token never reaches api.npmjs.org. It is never sent to third-party sources.


API reference

Package metadata

// Full packument — all versions, dist-tags, maintainers, readme, etc.
const pkg = await npm.package('typescript');
const pkg = await npm.package('typescript').get(); // same

// All published versions as an ordered array
const versions = await npm.package('typescript').versions();
console.log(versions.map(v => v.version).join(', '));

// Current maintainers
const maintainers = await npm.package('typescript').maintainers();
maintainers.forEach(m => console.log(m.name, m.email));

// Dist-tags
const tags = await npm.package('typescript').distTags();
// { latest: '6.0.2', beta: '6.0.0-beta', rc: '6.0.1-rc', ... }

Version manifest

// Specific version
const manifest = await npm.package('typescript').version('5.4.5');
const manifest = await npm.package('typescript').version('5.4.5').get(); // same

// Latest version (shorthand for .version('latest'))
const latest = await npm.package('typescript').latest();

console.log(manifest.version);        // '5.4.5'
console.log(manifest.license);        // 'Apache-2.0'
console.log(manifest.dist.tarball);   // tarball URL
console.log(manifest.dependencies);   // semver ranges from package.json

Download stats

// Total downloads over a period
const stats = await npm.package('typescript').downloads();             // last-month (default)
const stats = await npm.package('typescript').downloads('last-day');
const stats = await npm.package('typescript').downloads('last-week');
const stats = await npm.package('typescript').downloads('last-year');
const stats = await npm.package('typescript').downloads('2024-01-01:2024-03-31');

console.log(stats.downloads); // 163781296
console.log(stats.start);     // '2024-01-01'
console.log(stats.end);       // '2024-01-31'

// Per-day breakdown
const range = await npm.package('typescript').downloadRange('last-month');
range.downloads.forEach(d => console.log(d.day, d.downloads));

// Version-level downloads (last-week only)
const versionStats = await npm.package('typescript').version('5.4.5').downloads('last-week');
console.log(versionStats.downloads);

// Convenience methods on the client directly
const stats = await npm.downloads('last-week', 'typescript');
const range = await npm.downloadRange('last-month', 'typescript');

// Bulk downloads — unscoped packages are batched (128 per request),
// scoped packages are fetched one request each
const bulk = await npm.bulkDownloads(['react', 'vue', '@angular/core']);
const bulk = await npm.bulkDownloads(['react', 'vue'], 'last-week');

console.log(bulk['react']?.downloads);         // 18591460
console.log(bulk['vue']?.downloads);           // 4200000
console.log(bulk['@angular/core']?.downloads); // 1800000
// Packages that do not exist map to null

Quality score (deprecated)

score() is deprecated. npm no longer computes quality, popularity, or maintenance scores. Its search index now returns 1 for every metric in score.detail, and score.final is the search relevance of the package name — an unbounded number, not a 0–1 score. npms.io, which used to provide a detailed per-metric breakdown, has also been discontinued. There is no public replacement.

For popularity signals, use download counts, or the downloads and dependents fields of search results.

const score = await npm.package('react').score();

console.log(score.score.final);            // 2467.3 — search relevance, not quality
console.log(score.score.detail.quality);   // 1 — always 1
console.log(score.analyzedAt);             // '2024-01-01T00:00:00.000Z'

Install size — Packagephobia

Returns the published tarball size and the full installation footprint including all transitive dependencies.

// Latest version
const size = await npm.package('react').size();

// Specific version
const size = await npm.package('react').version('18.2.0').size();

// Publish size (the tarball itself)
console.log(size.publish.bytes);  // 12400
console.log(size.publish.files);  // 10
console.log(size.publish.pretty); // '12.4 kB'
console.log(size.publish.color);  // 'green'

// Install size (package + all transitive dependencies)
console.log(size.install.bytes);  // 307200
console.log(size.install.files);  // 35
console.log(size.install.pretty); // '307 kB'
console.log(size.install.color);  // 'green'  ('yellow' > 1 MB, 'red' > 10 MB)

CDN usage stats — jsDelivr

Measures actual browser requests from production apps — complementary to npm download counts, which reflect install-time usage.

// Package-level stats (grouped by version by default, last month)
const stats = await npm.package('react').cdnStats();

console.log(stats.rank);   // 1
console.log(stats.total);  // 1234567890

// Grouped by date instead
const byDate = await npm.package('react').cdnStats('date', 'week');

// Per-version breakdown
Object.entries(stats.versions ?? {}).forEach(([ver, data]) => {
  console.log(`${ver}: ${data.total} hits`);
});

// Version-level stats (grouped by file by default)
const versionStats = await npm.package('react').version('18.2.0').cdnStats();

console.log(versionStats.total);
Object.entries(versionStats.files ?? {}).forEach(([file, data]) => {
  console.log(`${file}: ${data.total} hits`);
});

Available periods: 'day' | 'week' | 'month' (default) | 'year'
Available groupBy at package level: 'version' (default) | 'date'
Available groupBy at version level: 'file' (default) | 'date'

File tree — unpkg

Lists every file included in a published version with individual sizes, types, and paths.

const tree = await npm.package('react').version('18.2.0').files();

console.log(tree.type); // 'directory'

// Recursively list all files
function listFiles(node: UnpkgFile, indent = 0) {
  const pad = ' '.repeat(indent);
  if (node.type === 'file') {
    console.log(`${pad}${node.path} (${node.size} bytes)`);
  } else {
    console.log(`${pad}${node.path}/`);
    node.files?.forEach(f => listFiles(f, indent + 2));
  }
}
listFiles(tree);

// Check if types are included
const hasTypes = tree.files?.some(f => f.path.endsWith('.d.ts'));

Resolved dependency graph — deps.dev

Returns exact resolved versions for every dependency in the tree — not the semver ranges from package.json, but what actually installs.

const deps = await npm.package('react').version('18.2.0').dependencies();

// All nodes in the graph
deps.nodes.forEach(n => {
  console.log(`${n.relation}: ${n.versionKey.name}@${n.versionKey.version}`);
  // 'SELF':     [email protected]
  // 'DIRECT':   [email protected]
  // 'INDIRECT': [email protected]
});

// Direct dependencies only
const direct = deps.nodes.filter(n => n.relation === 'DIRECT');

// Bundled dependencies (embedded in the tarball, not installed separately)
const bundled = deps.nodes.filter(n => n.bundled);

// Resolution errors (broken or unresolvable dependencies)
const broken = deps.nodes.filter(n => n.errors.length > 0);

// Dependency edges (who requires whom, and with which constraint)
deps.edges.forEach(e => {
  const from = deps.nodes[e.fromNode].versionKey.name;
  const to   = deps.nodes[e.toNode].versionKey.name;
  console.log(`${from} → ${to} (${e.requirement})`);
});

Security audit

Runs a security audit against the npm registry. The payload mirrors the top-level structure of package-lock.json.

const payload = {
  name: 'my-app',
  version: '1.0.0',
  requires: { lodash: '^4.17.11' },
  dependencies: {
    lodash: { version: '4.17.11', integrity: 'sha512-...' },
  },
};

// Full audit — advisory details + recommended actions
const result = await npm.audit(payload);

console.log(result.metadata.vulnerabilities);
// { info: 0, low: 0, moderate: 0, high: 1, critical: 0 }

Object.values(result.advisories).forEach(a => {
  console.log(`[${a.severity}] ${a.title}`);
  console.log(`  module: ${a.module_name}@${a.vulnerable_versions}`);
  console.log(`  fix:    upgrade to ${a.patched_versions}`);
  console.log(`  url:    ${a.url}`);
});

result.actions.forEach(action => {
  console.log(`${action.action}: ${action.module} → ${action.target}`);
});

// Quick audit — counts only, no advisory details (faster)
const quick = await npm.auditQuick(payload);

const { high, critical } = quick.metadata.vulnerabilities;
if (high + critical > 0) {
  console.error(`${high} high and ${critical} critical vulnerabilities found`);
}

Both methods support AbortSignal and emit a request event with method: 'POST'.

Search

const results = await npm.search({ text: 'typescript client' });
const results = await npm.search({ text: 'react hooks', size: 10, from: 0 });

results.objects.forEach(o => {
  console.log(
    o.package.name,
    o.package.version,
    o.downloads?.weekly,  // downloads in the last 7 days
    o.dependents,         // number of dependent packages, as a string: '216179'
  );
});
console.log(results.total); // total matches

// Rank the results by popularity yourself
const byDownloads = [...results.objects].sort(
  (a, b) => (b.downloads?.weekly ?? 0) - (a.downloads?.weekly ?? 0),
);

// Top package shortcuts
const top = await npm.topPackages(10);
const types = await npm.topByKeyword('typescript', 10);
const scoped = await npm.topByScope('@types', 10);

npm ranks search results by text relevance only. The quality, popularity, and maintenance weights of search() and maintainer().packages() are ignored by npm and are deprecated. topByPopularity(), topByQuality(), and topByMaintenance() are deprecated too: they return the same results as topPackages().

Maintainer

// Public profile
const user = await npm.maintainer('sindresorhus').info();
console.log(user.name, user.email);

// Avatar URL derived from the maintainer's public email
const avatarUrl = await npm.maintainer('sindresorhus').avatar();
// 'https://www.gravatar.com/avatar/...'

// All packages maintained (paginated)
const result = await npm.maintainer('sindresorhus').packages();
console.log(`${result.total} packages`);
result.objects.forEach(o => console.log(o.package.name, o.package.version));

// Second page
const page2 = await npm.maintainer('sindresorhus').packages({ size: 25, from: 25 });

User

Authenticated user endpoints require an npm registry token.

const npm = new NpmClient({ token: 'npm_...' });

// Validate a token and discover which account it belongs to
const { username } = await npm.whoami();
console.log(username); // 'pilmee'

// Registry user profile document
const profile = await npm.user('pilmee').get();
console.log(profile.name, profile.email);

// Package names associated with the user
const packages = await npm.user('pilmee').packages();
packages.forEach(name => console.log(name));

// Paginate when the registry supports it
const page2 = await npm.user('pilmee').packages({ size: 25, from: 25 });

Organization

const npm = new NpmClient({ token: 'npm_...' });

// Packages the org can access
const packages = await npm.org('npmcli').packages();
console.log(packages['@npmcli/arborist']); // 'read-write'

// Teams in the org
const teams = await npm.org('npmcli').teams();
teams.forEach(team => console.log(team)); // 'npmcli:developers'

// Org members and roles
const members = await npm.org('npmcli').members();
console.log(members.isaacs); // 'owner'

// Members in a specific team
const developers = await npm.org('npmcli').teamMembers('developers');
developers.forEach(username => console.log(username));

Chainable package resource pattern

Package resources implement PromiseLike, so you can await them directly or chain methods:

// Await directly → fetches the packument
const pkg = await npm.package('typescript');

// Chain → fetches the version manifest
const manifest = await npm.package('typescript').version('5.4.5');

// Chain → latest version
const latest = await npm.package('typescript').latest();

Cancelling requests

Pass an AbortSignal to any method to cancel the in-flight request:

const controller = new AbortController();
setTimeout(() => controller.abort(), 3000);

await npm.package('react').get(controller.signal);
await npm.package('react').score(controller.signal);
await npm.package('react').size(controller.signal);
await npm.package('react').cdnStats('version', 'month', controller.signal);
await npm.package('react').version('18.2.0').files(controller.signal);
await npm.package('react').version('18.2.0').dependencies(controller.signal);
await npm.package('react').downloads('last-week', controller.signal);
await npm.search({ text: 'react' }, controller.signal);
await npm.maintainer('sindresorhus').packages({}, controller.signal);
await npm.user('pilmee').get(controller.signal);
await npm.user('pilmee').packages({}, controller.signal);
await npm.bulkDownloads(['react', 'vue'], 'last-week', controller.signal);
await npm.audit(payload, controller.signal);
await npm.auditQuick(payload, controller.signal);
await npm.whoami(controller.signal);

When aborted, fetch throws a DOMException with name === 'AbortError'. The request event is still emitted with the error attached.


Request events

Subscribe to every HTTP request for logging, monitoring, or debugging:

npm.on('request', (event) => {
  console.log(`[${event.method}] ${event.url} → ${event.statusCode} (${event.durationMs}ms)`);
  if (event.error) {
    console.error('Request failed:', event.error.message);
  }
});

| Field | Type | Description | | --- | --- | --- | | url | string | Full URL that was requested | | method | 'GET' \| 'POST' | HTTP method used | | startedAt | Date | When the request started | | finishedAt | Date | When the request finished | | durationMs | number | Duration in milliseconds | | statusCode | number \| undefined | HTTP status code, if a response was received | | error | Error \| undefined | Present only if the request failed |

Remove a listener with off(), passing the same function given to on():

const log = (event: RequestEvent) => console.log(event.url);
npm.on('request', log);
npm.off('request', log);

Multiple listeners can be registered. Listener exceptions and rejected promises are isolated: they do not change the request result or prevent other listeners from running. Async listeners are not awaited. Each listener is responsible for logging or handling its own errors. The event is always emitted after the request completes, whether it succeeded or failed. Events are emitted for all data sources — registry, downloads, packagephobia, jsDelivr, unpkg, and deps.dev.


Error handling

Non-2xx responses throw an NpmApiError with the HTTP status code, status text, and the response body. When the body explains the failure, detail holds that reason and it is appended to message:

import { NpmApiError } from 'npmjs-api-client';

try {
  await npm.search({ text: 'a' });
} catch (err) {
  if (err instanceof NpmApiError) {
    console.log(err.status);     // 400
    console.log(err.statusText); // '' — often empty, see below
    console.log(err.detail);     // "The 'text' parameter must be between 2 and 64 characters"
    console.log(err.body);       // { error: "The 'text' parameter ...", code: 'ERR_TEXT_LENGTH' }
    console.log(err.message);    // "npm API error: 400 — The 'text' parameter ..."
  }
}

| Field | Type | Description | | --- | --- | --- | | status | number | HTTP status code | | statusText | string | HTTP status text; often empty, since many servers (and HTTP/2) do not send one | | body | unknown | Parsed JSON body, or raw text when the body is not JSON | | detail | string \| undefined | Reason extracted from the body, when there is one |


TypeScript types

All domain types are exported:

import type {
  // Client
  NpmClientOptions, RequestEvent, NpmClientEvents,

  // Package
  NpmPackument, NpmPerson, NpmRepository,
  NpmPackageVersion, NpmDist,
  NpmDistTags,

  // Search
  NpmSearchResult, NpmSearchObject, NpmSearchPackage,
  NpmSearchParams, NpmScore, NpmScoreDetail, NpmPackageLinks,

  // User
  NpmUser, NpmAuthenticatedUser, NpmUserPackages, NpmUserPackagesParams, NpmWhoami,

  // Downloads
  NpmDownloadPoint, NpmDownloadRange, NpmDownloadDay, NpmDownloadPeriod,
  NpmVersionDownloadPeriod, NpmVersionDownloadPoint, NpmBulkDownloads,

  // Score (evaluation types are deprecated — npms.io was discontinued)
  NpmsScore, NpmsScoreDetail, NpmsEvaluation,
  NpmsQualityEvaluation, NpmsPopularityEvaluation, NpmsMaintenanceEvaluation,

  // Packagephobia
  PackagephobiaSize, PackagephobiaSizeInfo,

  // jsDelivr
  JsdelivrStats, JsdelivrVersionEntry, JsdelivrPeriod, JsdelivrGroupBy,

  // unpkg
  UnpkgFile,

  // deps.dev
  DepsDevDependencies, DepsDevDependencyNode, DepsDevDependencyEdge, DepsDevVersionKey,

  // Audit
  NpmAuditPayload, NpmAuditDependency,
  NpmAuditResult, NpmAuditQuickResult,
  NpmAuditAdvisory, NpmAuditAction, NpmAuditFinding,
  NpmAuditMetadata, NpmAuditVulnerabilityCounts, NpmAuditSeverity,
} from 'npmjs-api-client';

Documentation

Full API documentation is published at: https://eljijuna.github.io/npmjs-api-client


Contributing

See CONTRIBUTING.md.


License

MIT