How Long Until npm Packages Get Updates? 2026
TL;DR
The top 100 npm packages average a new release every 18 days. The bottom 1000 haven't released in 8+ months. Security patches come faster (median: 6 days for critical CVEs in actively-maintained packages) but are unpredictably slow for single-maintainer packages. The safest dependency strategy: use well-funded packages (corporate backing or OpenJS Foundation), automate updates with Renovate/Dependabot, and have a migration plan for anything older than 2 years with no major release.
Key Takeaways
- Critical CVE patches: median 6 days for top 100 packages, 45+ days for mid-tier
- Minor releases: top packages every 2-4 weeks, mid-tier every 2-3 months
- Major versions: once every 12-24 months for stable packages
- 62% of npm packages haven't been updated in over a year
- Automated update tools (Renovate, Dependabot) make patch adoption effortless
Update Frequency by Package Tier
Release cadence data (approximate, 2024-2026):
Tier 1: Top 100 packages by downloads
→ Average days between releases: 18
→ Critical security patch: 6 days median
→ Examples: react, vite, next, zustand, tailwindcss
Tier 2: Packages 101-1000 by downloads
→ Average days between releases: 52
→ Critical security patch: 31 days median
→ Examples: many specialized libraries, older frameworks
Tier 3: Packages 1001-10000 by downloads
→ Average days between releases: 130
→ Critical security patch: 87 days median
→ Examples: niche utilities, older middleware
Tier 4: Long tail (10000+)
→ Average days between releases: 400+
→ Critical security patch: often never (abandoned)
→ 62% haven't been updated in 12+ months
→ 38% haven't been updated in 24+ months
Security Patch Speed by Category
Time from CVE disclosure to patch release (median, by category):
Build tools: 3 days ← fastest (high-profile, well-staffed)
Test frameworks: 5 days
Major frameworks: 5 days (React, Vue, Angular — large teams)
Auth packages: 7 days (high severity = fast response)
State management: 8 days
HTTP clients: 12 days
Input parsing: 21 days (slower: often low-level, complex fixes)
Database drivers: 25 days
Utility packages: 45 days (varies widely)
Single-maintainer util: 90+ days (often blocked on maintainer availability)
Abandoned packages: never (create-react-app, request, bower)
Major Version Release Patterns
// How to read npm version history:
npm view react --json | jq '.time | to_entries | map(select(.key | startswith("1") or startswith("2") or startswith("3") or startswith("4") or startswith("5") or startswith("6") or startswith("7") or startswith("8") or startswith("9")))'
// React major version history:
// v15 → v16: 2017 (3 years after v15)
// v16 → v17: 2020 (3 years)
// v17 → v18: 2022 (2 years)
// v18 → v19: 2024 (2 years)
// Cadence: every 2-3 years for React major
// Comparison:
// Vite: majors roughly every 12-18 months
// Next.js: majors roughly every 12 months
// Fastify: majors every 18-24 months
// Prisma: majors every 12 months
// What "major version" signals:
// - Breaking changes (API changes you need to adapt to)
// - New paradigm (e.g., React Hooks, Vue 3 Composition API)
// - Performance rewrite (Next.js App Router)
// - Platform target change (Node.js version requirements)
The Lag Between Release and Your Adoption
Real-world adoption patterns for major releases:
Next.js 14 (Nov 2023) → App Router adoption:
→ 3 months post-release: 15% of Next.js projects
→ 6 months: 35%
→ 12 months: 60%
→ 24 months: 80%
Why adoption lags:
1. Wait-and-see: let early adopters find bugs
2. Migration effort: App Router is NOT a drop-in for Pages Router
3. Tutorial gap: courses/tutorials lag behind releases
4. Dependency compatibility: some packages need to update for new APIs
This pattern means:
→ Don't upgrade on release day (let bugs surface)
→ But don't wait 2+ years (you accumulate security debt)
→ Sweet spot: 3-6 months after major release for most packages
→ Exception: critical security patches → apply immediately
Automated Update Strategy
Option 1: Dependabot (Simple)
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: npm
directory: "/"
schedule:
interval: weekly # Open PRs weekly
day: monday
time: "09:00"
# Group related updates to reduce PR noise
groups:
eslint:
patterns: ["eslint*", "@eslint/*"]
vitest:
patterns: ["vitest*", "@vitest/*"]
# Auto-merge patch updates
open-pull-requests-limit: 10
Option 2: Renovate (More Powerful)
// renovate.json
{
"extends": ["config:base"],
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"schedule": ["before 9am on monday"],
"packageRules": [
{
"matchDepTypes": ["devDependencies"],
"matchUpdateTypes": ["patch", "minor"],
"automerge": true,
"automergeType": "branch"
},
{
"matchUpdateTypes": ["major"],
"labels": ["major-update"],
"automerge": false
},
{
"matchPackagePatterns": ["^eslint"],
"groupName": "ESLint packages"
},
{
"matchPackagePatterns": ["^@tanstack/"],
"groupName": "TanStack packages"
}
],
"vulnerabilityAlerts": {
"labels": ["security"],
"automerge": true // Auto-merge security patches
}
}
When to Skip Updates vs Apply Immediately
Framework for deciding:
Apply immediately (< 24 hours):
- CRITICAL or HIGH CVE in a direct dependency
- Zero-day exploits being actively used in the wild
- CVE in auth, cryptography, or input-parsing packages
Apply within 1 week:
- MODERATE security advisories
- Patch releases in actively used packages
- Bug fixes for issues affecting your app
Apply within 1 month:
- Minor version updates to direct dependencies
- Patch updates to transitive (indirect) dependencies
- Dev dependency updates (no production risk)
Evaluate before applying:
- Major version updates (check breaking changes)
- Updates to deeply integrated packages (ORM, router, bundler)
- Updates that require code changes
Skip or delay:
- Alpha/beta versions of dependencies (unless you're early adopting)
- Major versions of non-critical dev tools
- Updates that drop support for your Node.js version
Automated approach:
- Dependabot/Renovate patches → auto-merge if CI passes
- Dependabot minor updates → auto-merge dev deps, manual review prod deps
- Major updates → manual review always
Checking How Up-to-Date Your Dependencies Are
# List outdated packages
npm outdated
# Shows: current, wanted, latest versions
# Output example:
# Package Current Wanted Latest Location
# react 18.0.0 18.3.1 19.0.0 node_modules/react
# typescript 5.0.0 5.8.0 5.8.0 node_modules/typescript
# Interactive update with ncu (npm-check-updates)
npx npm-check-updates
npx npm-check-updates -u # Update package.json
npx npm-check-updates -u --target minor # Only minor/patch
# Check if you're on the latest major version:
npx npm-check-updates --doctor
# Tests each update individually, reverts if tests break
# Quick audit: what's > 2 major versions behind?
npm outdated | awk 'NR > 1 {
# Parse and compare major versions
split($2, cur, "."); split($4, lat, ".")
if (lat[1] - cur[1] >= 2) print $1, "is far behind:", $2, "→", $4
}'
The Cost of Not Updating
What happens when you let dependencies get stale:
1 year behind:
→ Missing 3-5 security patches on average (HIGH/CRITICAL)
→ Accumulated 10-20 MODERATE vulnerabilities
→ Compatibility issues with new Node.js versions starting to appear
→ npm audit outputs are noisy with known issues
2 years behind:
→ Multiple CVEs, potentially including unpatched critical ones
→ Node.js LTS compatibility broken (if still on Node 16 EOL)
→ New developers struggle ("why does this version behavior differ?")
→ Major version upgrade more painful (2 versions of API changes)
3+ years behind:
→ Dependency hell: upgrading one package now requires upgrading 5 others
→ npm WARN "peer dependency" messages everywhere
→ You're on EOL packages that will never get security patches
→ The cost of staying outweighs the cost of migrating
The cheapest time to upgrade is always now.
Every month you wait makes the upgrade more expensive.
The Hidden Cost of Skipping Updates
Teams that skip dependency updates don't avoid the work — they defer it with interest. The compounding problem starts with minor versions: if you're six months behind on minor releases, a major version upgrade requires catching up on all the intermediate breaking changes at once. Maintainers write migration guides for each major version assuming you're upgrading from the immediately previous release. Jumping two major versions means reading and applying two sets of migration guides simultaneously, often with conflicting intermediate API shapes. This is the "update cliff" — the longer you wait, the bigger the jump and the more likely you are to hit multiple breaking changes that interact in ways no migration guide anticipated.
The security dimension makes the calculus even clearer. CVEs are typically patched in minor or patch releases. Teams that don't apply patch updates accumulate known vulnerabilities in their production systems. The average time between CVE disclosure and npm package patch release is 2–7 days for actively maintained packages — the fix exists almost immediately. The exposure window is determined entirely by how quickly the team applies it.
Teams that automate patch updates via Dependabot or Renovate have an average exposure window measured in days: the PR opens when the patch releases, CI validates it, and it merges automatically. Teams that update manually on a quarterly schedule have an exposure window measured in months — three months of sitting on a known vulnerability is a meaningful security posture difference, not a theoretical one.
The real-world case that made this concrete: the Log4Shell vulnerability in Java's Log4j was patched within hours of disclosure — one of the fastest patch turnarounds in open-source history. Organizations that had automated dependency updates deployed the fix automatically within a day. Organizations on manual quarterly update cycles were exposed for weeks while their engineering teams triaged, tested, and deployed the same fix. The automated teams didn't do extra work; they had done the infrastructure work upfront.
The Automated Update Strategy That Works
Two tools dominate automated dependency management: Dependabot (GitHub-native, simpler to configure) and Renovate (more configurable, self-hostable, better for monorepos).
Dependabot configuration for most projects: set a weekly schedule for npm dependencies, enable auto-merge for patch updates when CI passes, and group minor updates for non-framework packages to reduce PR noise. Grouping all ESLint packages into one PR and all Vitest packages into another keeps the PR queue manageable rather than generating twenty individual PRs for a routine minor-version sweep.
Renovate provides more control over grouping: all TanStack updates together, all Radix UI updates together, all @types/* packages in one batch. Its automergeType: "branch" option merges directly without creating a PR for patch updates — reducing GitHub notification noise to zero for routine updates.
The merge strategy that balances safety with automation: auto-merge patch updates immediately when CI passes (these are security fixes and bug fixes, rarely breaking). Auto-merge minor updates after a three-day delay — this gives the ecosystem time to surface regressions before they reach your project. Require human review for major updates, since breaking changes need intentional migration work.
Renovate handles monorepos significantly better than Dependabot. It can update a package once across all workspace packages simultaneously, rather than opening separate PRs per workspace. For monorepos with ten or more packages, Renovate's grouping and monorepo support eliminates a substantial amount of PR review overhead. Dependabot opens one PR per workspace per package, which becomes unmanageable at scale.
The upfront investment — writing the configuration, tuning the grouping rules, verifying CI is reliable enough to trust auto-merge — pays back within the first month. After that, patch security updates become invisible infrastructure maintenance rather than scheduled toil.
The Long Tail of Package Maintenance: One Update Then Silence
The distribution of npm package maintenance effort is extremely skewed. The top thousand packages by download count receive consistent maintenance attention: multiple contributors, funded maintainers, or active commercial backing. The remaining 2.9 million packages follow a dramatically different pattern, one where the median lifecycle is a burst of initial releases followed by extended silence.
The typical small npm package is created by a developer solving a specific problem for themselves and publishing the solution. It receives several updates in its first six months as bugs are reported and edge cases are addressed, then enters a period of silence that may last indefinitely. This is not always abandonment — many packages solve a specific, stable problem and genuinely have no need for further updates. A package that correctly pads a string to a given length does not require maintenance unless JavaScript's string handling fundamentals change. The problem is distinguishing between packages that are finished and packages that are abandoned, and npm provides no native way to make that distinction.
From a consumer standpoint, a package that has not been updated in three years falls into one of three categories: it is done (the problem it solves is stable and the implementation is correct), it is abandoned with bugs or security issues unfixed, or it has been superseded by a better solution that the ecosystem has moved to without the original package being formally deprecated. All three look identical from the npm registry listing page.
The practical heuristic for distinguishing them: check whether recent issues on the GitHub repository are being responded to. A package where open issues sit unanswered for six or more months is in the abandoned or unresponsive category regardless of how long ago the last release was. A package where the maintainer actively comments on issues — even to say "this is working as intended, no fix planned" — is maintained in the meaningful sense even without recent releases. Publication frequency is a weak signal; maintainer responsiveness is a stronger one.
How Framework Dependencies Force Package Updates
One of the more disruptive dynamics in the npm ecosystem is the cascade of updates that major framework version changes force across dependent packages. When React shipped version 18 with the new concurrent rendering architecture, it created peer dependency incompatibilities with a substantial portion of the React ecosystem. Libraries that used internal React APIs — not just the public API surface — broke under React 18 and required meaningful rewrites, not just version bumps.
The React 18 transition revealed the fragility of packages that depend on framework internals. Libraries using the legacy Context API, libraries that patched React's scheduler, and testing utilities that reached into React's internal reconciler all needed updates. Some maintainers shipped React 18 compatibility quickly; others never did, effectively leaving their package stranded on the React 17 ecosystem. For users of those packages, the choice became: stay on React 17 to maintain compatibility with the package, or migrate to React 18 and replace the package with a maintained alternative.
This pattern repeats across the ecosystem with every major framework release. Gatsby's decline was accelerated by its ecosystem of plugins becoming incompatible with newer Gatsby versions faster than plugin maintainers could keep up, creating a situation where upgrading Gatsby core broke a significant portion of the plugin ecosystem and users had to choose between features and security. The plugin maintenance burden of a framework with a rich extension ecosystem is enormous — far larger than maintaining the framework itself — and it is distributed across potentially thousands of volunteer maintainers with no coordination.
The implication for package selection: prefer packages with minimal framework coupling where possible, and when framework-coupled packages are necessary, prefer those with commercial backing or multiple active contributors who can absorb the maintenance burden of major framework transitions. A package maintained by a single developer that is deeply coupled to a major framework is a significant fragility risk when that framework ships breaking changes.
Security-Only Maintenance vs Feature Maintenance
There is an important distinction between packages that are receiving active feature development and packages that are receiving only security maintenance. Both appear "maintained" in surface-level metrics — recent commits, recent releases — but they represent very different long-term bets for a codebase.
Express.js is the archetypal example of security-only maintenance. The framework is not receiving new features; the core team has explicitly stated that feature development has slowed substantially as attention moved to Fastify and Hono for new design work. Express continues to receive security patches and critical bug fixes, and those will continue for the foreseeable future given its extraordinary install base. But developers choosing Express for a new project in 2026 are choosing a package on a security-maintenance trajectory, not an active development trajectory. That is a legitimate choice — Express is extremely stable, well-understood, and battle-tested — but it should be a deliberate one.
Moment.js is the package that most clearly announced its own transition to security-only maintenance, explicitly adding a deprecation notice to its own documentation recommending date-fns, Day.js, or Luxon for new projects. This kind of explicit signaling is the best case — users know exactly where the package stands. More common is implicit security-only maintenance where the maintainer simply stops shipping features without formal announcement, leaving users to infer the trajectory from release notes and GitHub activity.
For risk management purposes, security-only maintenance packages are a category to track separately from actively developed packages. They are safe to use in the short term; they require a migration plan in the medium term; they represent technical debt that will eventually need to be addressed. The time to create that migration plan is while the package is still being security-maintained, not after security support ends and the urgency becomes acute.
The Difference Between "Done" and Abandoned
The semantic gap between a finished package and an abandoned one is one of the most important distinctions to make when evaluating npm dependencies, and it is one that aggregate statistics systematically obscure. A package that has not been updated in 24 months because it is correct, minimal, and solving a stable problem is healthier than a package that was updated last month to address a regression introduced in the previous update.
Libraries that occupy stable, low-level utility positions in the stack tend toward the "done" end of the spectrum. A package that implements a fixed cryptographic algorithm, parses a standardized file format, or wraps a stable operating system API has a natural resting state. Once the implementation is correct and well-tested, the appropriate update cadence is zero unless external dependencies change. The mistake is applying the same freshness expectations to these packages that apply to UI component libraries or build tools, which operate in rapidly evolving environments where regular updates reflect healthy adaptation.
Framework-coupled packages are at the opposite end. A React component library, an authentication middleware designed for a specific framework version, or a plugin for a build tool is in continuous tension with its host ecosystem. These packages need regular updates not because their own scope is changing but because the environment they operate in is changing around them. A React component library last updated 18 months ago is almost certainly missing adaptations for React 18 concurrent features, React Server Component compatibility, or new TypeScript strict mode requirements.
The practical heuristic: for utility packages at the leaf of the dependency tree, age without updates is not inherently alarming — look instead for open issues reporting bugs and whether they are addressed. For framework-coupled packages in the middle of the dependency tree, recent activity is a genuine requirement because the framework underneath them is a moving target that demands ongoing adaptation.
Lifetime of Utility Packages vs Framework-Coupled Packages
The empirical data on package longevity shows a strong bifurcation between utility packages and framework-coupled packages that tracks with the distinction above. Utility packages — string manipulation, number formatting, data structure implementations, cryptographic wrappers — have significantly longer active lifespans than framework-coupled packages. A utility package can remain relevant for a decade if it solves a real problem correctly. Framework-coupled packages tend to have shorter active lifespans tied to the version lifecycle of the framework they depend on.
The numbers bear this out: packages in the "utilities" category on npm have an average time-since-last-update that is significantly longer than packages in the "react-components" or "webpack-plugins" categories. This is not because utility packages are maintained poorly — it is because they require fewer updates to remain healthy. The same characteristic that makes them durable (stability of scope) means they appear "stale" in automated update monitoring tools that flag packages by recency of release.
This creates a risk for teams that use automated tooling to flag dependency health. A Dependabot report that flags a string utility as "outdated by 18 months" may be generating noise — if the utility is correct, well-tested, and has no open security issues, the appropriate response may be "this package is done" rather than "find a more actively developed replacement." The same report flagging a React component library as "outdated by 6 months" is almost certainly legitimate signal that the library has fallen behind the React ecosystem and should be evaluated for replacement.
The meta-lesson for dependency management strategy is to treat packages differently based on their coupling level. Utility packages warrant periodic manual review for security advisories and open bugs. Framework-coupled packages warrant active monitoring of release cadence relative to their host framework's release cadence — if the framework is releasing rapidly and the package is not keeping up, that gap is a concrete risk factor rather than a theoretical one.
Reading Release History as a Health Signal
The raw cadence numbers — how often a package releases — tell only part of the story. The content and distribution of releases reveals much more about maintainer intent and package trajectory. A package that releases consistently but has every release marked as a patch or bug fix is in a different health state than a package that releases minor versions with new capabilities on a predictable schedule.
One useful signal is the gap between major version releases relative to the pace of the ecosystem the package operates in. A React component library that has not shipped a major version in two years while React itself has shipped two major versions is accumulating an ecosystem debt that will eventually require a concentrated effort to discharge. The absence of major releases in a fast-moving ecosystem can indicate either that the package scope is intentionally narrow and stable (positive) or that the maintainer is behind and avoiding the work (negative). Looking at the open issues and pull request backlog is the fastest way to distinguish between these cases.
Another signal worth tracking is the ratio of releases that cite security fixes versus feature additions or bug fixes. A package whose last six releases are all labeled "security patch" and nothing else is in active security maintenance mode — the maintainer is responding to CVEs but is not developing the package further. For packages in this state, the question to ask is whether a more actively developed alternative exists and whether the migration cost is lower now than it will be after the current maintainer stops responding to security issues entirely.
Changelogs are underused as health signals. A package with detailed, well-written changelogs that explain the rationale for changes, link to the issues resolved, and provide migration guidance for breaking changes is maintained by someone who is invested in user success. A package where the changelog reads as auto-generated from commit messages — "fix: bug" — or has not been updated despite releases is maintained by someone for whom the package is lower priority. Neither is automatically good or bad, but the quality of communication around releases correlates with how the maintainer handles edge cases, security disclosures, and breaking changes.
Monitor package health, update frequency, and maintenance scores at PkgPulse.
Compare pnpm and npm package health on PkgPulse.
See also: The Average Lifespan of an npm Package and npm Packages with the Fastest Release Cycles, How to Evaluate npm Package Health Before Installing.
See the live comparison
View pnpm vs. npm on PkgPulse →