50 Most Underrated npm Packages to Use in 2026
TL;DR
The best npm packages aren't always the most famous ones. This list features packages with <500K weekly downloads but outsized value — tiny utilities that expert developers swear by, alternatives to bloated standards, and new packages that solve old problems better. Each one is maintained, typed, and under-installed relative to its quality.
Key Takeaways
- Many expert-level picks have under 100K downloads but solve specific problems elegantly
- Zero-dep packages dominate this list — small, focused, zero supply chain risk
- TypeScript-first — every package on this list ships its own types
- Recency matters — most of these are 2022+ packages (modern APIs and patterns)
Utilities
1. defu — 160K downloads/week
// Recursive default merge: only fills in missing values (not override)
import { defu } from 'defu';
const config = defu(userConfig, {
port: 3000,
host: 'localhost',
debug: false,
});
// Unlike Object.assign or spread: defu goes DEEP
// Unlike deepmerge: defu ONLY fills in undefined, doesn't override existing
2. pathe — 800K downloads/week
// Path utilities like Node.js `path` but cross-platform (always uses /)
import { join, resolve, dirname } from 'pathe';
// No more "why does this work on Mac but not Windows?" path issues
// Used by Nuxt, Vite, many UnJS packages internally
3. ohash — 400K downloads/week
// Fast object hashing — same hash for equivalent objects regardless of key order
import { hash, objectHash } from 'ohash';
hash({ b: 2, a: 1 }) === hash({ a: 1, b: 2 }) // true
// Use for cache keys, deduplication, change detection
4. scule — 300K downloads/week
// String case conversion: camelCase, snake_case, PascalCase, kebab-case
import { camelCase, snakeCase, pascalCase, kebabCase } from 'scule';
camelCase('hello-world') // 'helloWorld'
snakeCase('helloWorld') // 'hello_world'
// 0 dependencies, tiny bundle
5. perfect-debounce — 180K downloads/week
// Async-aware debounce: if the function is async, it queues correctly
import { debounce } from 'perfect-debounce';
const search = debounce(async (query: string) => {
return await api.search(query);
}, 300);
// Handles: multiple rapid calls, awaited return values, cancellation
Data & Types
6. type-fest — 3.5M downloads/week (underrated despite size)
// Huge collection of TypeScript utility types not in the standard library
import type { Simplify, Jsonify, SetOptional, RequireAtLeastOne } from 'type-fest';
type ApiUser = Simplify<User & { token: string }>; // Flattens intersection
type SetName = SetOptional<User, 'avatar' | 'bio'>; // Make fields optional
type Config = RequireAtLeastOne<Options, 'url' | 'path'>; // One required
7. remeda — 280K downloads/week
// Type-safe Lodash alternative: full TypeScript inference, tree-shakeable
import * as R from 'remeda';
const result = R.pipe(
users,
R.filter(u => u.active),
R.map(u => u.name),
R.sort(R.ascend(R.identity)),
);
// Lodash has types, but remeda's are tighter and functional-pipeline-friendly
8. ts-reset — 400K downloads/week
// Makes TypeScript stricter in specific ways:
import '@total-typescript/ts-reset';
// - JSON.parse() returns unknown (not any)
// - Array.filter(Boolean) removes undefined from type
// - fetch().json() returns unknown
// No runtime code — just type declarations that improve type safety
9. zod-to-json-schema — 1M downloads/week
// Convert Zod schemas to JSON Schema (for OpenAPI, form libraries, etc.)
import { zodToJsonSchema } from 'zod-to-json-schema';
import { z } from 'zod';
const userSchema = z.object({ name: z.string(), age: z.number() });
const jsonSchema = zodToJsonSchema(userSchema);
// { type: 'object', properties: { name: { type: 'string' }, ... } }
// Use for: Swagger/OpenAPI docs, JSON Schema validation, tRPC meta
Node.js / Server
10. c12 — 400K downloads/week
// Config loader: reads from multiple sources (env, config files, pkg.json)
import { loadConfig } from 'c12';
const { config } = await loadConfig({
name: 'myapp', // Reads: myapp.config.ts, .myapprc, package.json#myapp, env
defaults: { port: 3000, debug: false },
});
// Used by Nuxt, Vite, and the UnJS ecosystem for config loading
11. citty — 200K downloads/week
// Modern CLI framework: type-safe, minimal, excellent autocomplete
import { defineCommand, runMain } from 'citty';
const main = defineCommand({
meta: { name: 'my-cli', description: 'A CLI tool' },
args: {
input: { type: 'string', required: true },
verbose: { type: 'boolean', default: false },
},
run({ args }) {
console.log(args.input, args.verbose);
},
});
runMain(main);
12. listhen — 200K downloads/week
// Create a local server with automatic port finding and ngrok-style tunnels
import { listen } from 'listhen';
const listener = await listen(app, {
port: 3000, // Tries 3000, then 3001, etc. if taken
public: true, // Optional: expose via tunnel
});
console.log(listener.url); // http://localhost:3000 or tunnel URL
13. hookable — 500K downloads/week
// Type-safe hook system (plugin/extension pattern)
import { createHooks } from 'hookable';
const hooks = createHooks<{
beforeRequest: (req: Request) => void;
afterResponse: (res: Response) => void;
}>();
hooks.hook('beforeRequest', (req) => {
req.headers.set('Authorization', `Bearer ${token}`);
});
// Used by Nuxt's entire extension system
14. unstorage — 600K downloads/week
// Universal key-value storage: same API for memory, filesystem, Redis, KV
import { createStorage } from 'unstorage';
import lruCacheDriver from 'unstorage/drivers/lru-cache';
import redisDriver from 'unstorage/drivers/redis';
const storage = createStorage({
driver: process.env.NODE_ENV === 'production'
? redisDriver({ url: process.env.REDIS_URL })
: lruCacheDriver({ max: 500 }),
});
await storage.setItem('user:123', { name: 'Royce' });
await storage.getItem('user:123'); // { name: 'Royce' }
React / Frontend
15. use-immer — 450K downloads/week
// Immer-powered useState: mutate state immutably
import { useImmer } from 'use-immer';
const [draft, updateDraft] = useImmer({ items: [], count: 0 });
// Instead of: setDraft(prev => ({ ...prev, items: [...prev.items, newItem], count: prev.count + 1 }))
updateDraft(draft => {
draft.items.push(newItem);
draft.count++;
}); // Much cleaner for nested state updates
16. react-aria — 1.4M downloads/week
// Adobe's headless accessible component primitives
import { useButton, useFocusRing } from 'react-aria';
// Foundation for building WCAG-compliant components
// Used by: React Aria Components (full component library built on top)
// Different from shadcn: more low-level, maximum customization
17. wouter — 500K downloads/week
// React Router alternative: 2.4KB, hooks-first, same API
import { Route, Link, useLocation } from 'wouter';
// Drop-in replacement for most React Router use cases
// Perfect for: admin panels, SPAs that don't need complex routing
18. sonner — 800K downloads/week
// Toast notifications: opinionated, beautiful, small
import { toast, Toaster } from 'sonner';
// <Toaster /> in layout
toast.promise(saveUser(), {
loading: 'Saving...',
success: 'Saved!',
error: (err) => `Error: ${err.message}`,
});
Build / Dev Tools
19. tsup — 1.8M downloads/week (underrated for its category)
// Bundle TypeScript libraries for npm: generates CJS, ESM, types
// tsup.config.ts
import { defineConfig } from 'tsup';
export default defineConfig({
entry: ['src/index.ts'],
format: ['cjs', 'esm'],
dts: true, // Generate .d.ts files
sourcemap: true,
clean: true,
});
// Run: npx tsup
// Replaces: complex rollup/webpack config for library authors
20. changelogen — 150K downloads/week
// Changelog generation from conventional commits
// npx changelogen --release
// Generates CHANGELOG.md, bumps version, creates git tag
// Follows the same format as Vite/Nuxt changelogs
Testing
21. @mswjs/data — 300K downloads/week
// Model-based API mocking: define data models, mock query/filter/sort
import { factory, primaryKey } from '@mswjs/data';
const db = factory({
user: { id: primaryKey(String), name: String, age: Number },
});
db.user.create({ id: '1', name: 'Alice', age: 28 });
db.user.findMany({ where: { age: { gte: 18 } } });
// Pairs with MSW for realistic API mocking in tests
Honorable Mentions (Brief)
// More packages worth knowing:
// - httpxy: zero-dep HTTP proxy middleware
// - rou3: Radix router for Node.js (blazing fast route matching)
// - h3: Minimal HTTP server (Nuxt/Nitro's foundation)
// - magic-string: sourcemap-aware string manipulation (used by Rollup)
// - acorn: JavaScript AST parser (used by all bundlers)
// - estree-walker: walk/transform ESTree-format ASTs
// - kolorist: tiny terminal coloring (like picocolors, 0.5KB)
// - mlly: ES module utilities for Node.js
// - std-env: detect runtime environment (Node/Bun/Deno/Edge/browser)
// - consola: Beautiful console logging with levels (used by Nuxt)
// - unenv: Universal environment compatibility (platform-agnostic code)
// - tinycolor2: Color manipulation, 0 deps
// - culori: Color science library, zero-dep
// - fuzzysort: Fast fuzzy search (Sublime Text algorithm in JS)
// - meilisearch: Full-text search client, better Algolia alternative
// - lottie-web: JSON animation library
// - floating-ui: Tooltips/popovers positioning (Radix uses this)
// - vaul: Drawer component for React
// - cmdk: Command palette for React (shadcn uses this)
// - input-otp: OTP input field for React
The Discovery Problem: Why Great Packages Stay Hidden
The npm registry has over two million packages. Discoverability is a genuine engineering problem, and the default discovery surface — the npm search page — is optimized for download counts and keyword matching rather than quality. The result is a feedback loop where widely-used packages attract more visibility and become more widely used, while high-quality smaller packages stay hidden from the developers who would benefit most from them.
The discovery patterns that work in practice are mostly out-of-band. Following respected developers on Twitter/X or Bluesky surfaces small utility packages that go viral in tight developer communities — a package with 50K weekly downloads can trend for a day in the TypeScript community and pick up thousands of new users who would never have found it through search. Reading release notes and changelogs for frameworks you already use is another underrated source: Vite's, Next.js's, and Prisma's changelogs regularly reference the packages they integrate with, and those integration packages are often excellent. Exploring the dependencies field of packages you already trust is similarly valuable — if a package you rely on depends on a utility you haven't heard of, that utility has already passed a quality bar.
Curated lists provide a more structured path: the awesome-nodejs and awesome-react GitHub repositories surface community-maintained lists with editorial judgment behind them. The signal-to-noise ratio is higher than search because a human had to decide each package was worth including.
The most underrated discovery heuristic is searching for your specific problem rather than a solution category. Searching for "npm binary search tree" rather than "npm data structure library" returns packages that are genuinely specialized for your use case rather than general-purpose utilities that happen to include the feature you need.
The clearest signal that a package is underrated despite quality: a high GitHub star-to-weekly-download ratio. Lots of stars indicates developer interest and recognition; relatively low downloads indicates that recognition has not yet translated to widespread adoption. These are the packages on the cusp of mainstream discovery — the ones most likely to look obvious in retrospect two years from now.
How to Evaluate an Underrated Package Before Using It
The risk profile of an underrated package is different from an established one. Fewer users means fewer bug reports and less community testing in edge cases. A package with 50K weekly downloads has had significantly fewer production environments stress-test its error handling than one with 5 million. That does not make lesser-known packages untrustworthy, but it does mean your evaluation needs to do work that the community would otherwise do for you.
The evaluation checklist for underrated packages: First, read the source code. Packages under 500 lines can often be fully reviewed in 15 to 20 minutes. If you can audit the entire implementation, you can trust it on its merits rather than on reputation. Second, look for tests — a package with good test coverage signals an author who cares about correctness and has documented the expected behavior of edge cases. No tests is a yellow flag for anything you plan to use in production. Third, look at the author's other packages and their GitHub activity. Do they respond to issues? Are their other packages well-maintained? An author with a track record of responsible maintenance across multiple packages is meaningfully lower risk than an anonymous one-off package. Fourth, check whether the package is used as a dependency by other packages you already trust — the reverse dependencies list on npmjs.com is a quick way to establish transitive trust. Fifth, evaluate the dependency footprint: a small package with many of its own dependencies is importing supply chain risk that is disproportionate to its size.
One rule that should not bend regardless of quality signals: for security-sensitive code — authentication logic, cryptographic operations, parsing untrusted external input — do not use underrated packages. For these boundaries, stick to audited, widely-deployed packages with formal security processes. The evaluation checklist above is for general utility packages, not for code that sits on your security perimeter.
Why Certain Utilities Stay Obscure Despite High Quality
The npm discovery problem cuts deeper than a bad search interface. Some packages are genuinely excellent and genuinely difficult to find through any conventional path, and the reasons reveal structural features of how the npm ecosystem propagates knowledge.
Namespace obscurity is the first factor. Packages in scoped namespaces — @unjs/, @total-typescript/, @mswjs/ — are invisible unless you already know to search within that namespace. A developer searching npm for "object hashing" will not find ohash because its name does not contain "hash" as a standalone word, and its npm description emphasizes speed rather than the use case. The UnJS ecosystem in particular is a trove of high-quality utility packages that almost all share this discovery problem: packages like scule, pathe, ufo, and unctx solve real problems elegantly but are named in ways that prioritize brevity over searchability.
Transitive dependency obscurity creates a second category. Many of the most-used packages in this list — magic-string, acorn, estree-walker — are consumed by millions of developers indirectly through bundlers, but those developers have never heard of them. The packages are depended on by Rollup, Vite, and similar tools, but they never appear in application-level package.json files and therefore never enter the consciousness of developers who might find them useful directly. A developer writing a custom AST transform or a code generation tool could use acorn and estree-walker directly to excellent effect, but without knowing these packages exist through the bundler context, they are invisible.
Timing and announcement channel mismatch is a third factor. A high-quality package released on a Tuesday by a developer without a significant Twitter following enters an ecosystem of two million packages with no announcement vector. Packages that get featured in newsletters like Node Weekly or JavaScript Weekly see immediate spikes in downloads; packages that do not may sit at under ten thousand weekly downloads indefinitely regardless of their quality. The correlation between package quality and announcement channel reach is essentially zero, which is why some of the best packages are undiscovered.
The metric that most reliably surfaces obscure quality is the inverse of the usual download-to-stars ratio. A package with four hundred GitHub stars and eighty thousand weekly downloads has been found useful by many more people than have consciously bookmarked it — that combination suggests organic word-of-mouth adoption from people solving real problems, rather than the trophy-star pattern where a package is starred for being clever but rarely used. Packages like defu, pathe, and perfect-debounce show this profile: professional adoption that outpaces public visibility.
The Low-Star/High-Download Signal in Practice
The most operationally useful heuristic for finding underrated packages is the star-to-download ratio, applied in reverse from the usual direction. The typical developer reads star counts as a proxy for quality: a package with twenty thousand stars seems safer than one with two thousand. This proxy breaks in a specific and predictable way for utility packages that solve concrete problems rather than introduce new paradigms.
Utility packages that solve a focused, practical problem accumulate downloads from developers who found the package by searching for a specific solution and immediately installed it. Many of those developers never star the repository — they installed the package, it worked, and they moved on. Packages that introduce exciting new concepts or paradigms attract stargazers who bookmark them for future exploration but never install them. The result is that "interesting" packages accumulate stars disproportionately relative to actual use, while "useful" packages accumulate downloads disproportionately relative to stars.
Applying this lens to the packages in this list: defu has considerably more weekly downloads than GitHub stars. pathe is depended on by dozens of major packages but its star count is modest relative to its ecosystem footprint. scule has been installed hundreds of millions of times but is unknown to most developers who would benefit from it. These packages are doing real work in real codebases at a scale their public visibility does not reflect, and that gap is exactly where underrated value hides.
The ratio threshold worth investigating: any package where weekly downloads exceed GitHub stars by a factor of fifty or more, and where the download count is above fifty thousand, is worth examining. At that ratio, the downloads are almost certainly reflecting genuine problem-solving use rather than bot traffic or one-time curiosity installs. The package is working in enough production environments to have been pulled through repeated npm install runs across many different projects. That is a more robust quality signal than any amount of social proof.
The download floor matters for filtering noise. Below ten thousand weekly downloads, the star-to-download ratio can be erratic — a viral blog post can spike downloads temporarily without reflecting sustained use. Above fifty thousand, the pattern is more stable and the ratio reliably indicates whether a package is primarily gathering social proof or primarily being actively used.
The UnJS Ecosystem: An Underrated Package Factory
The UnJS project, led by Pooya Parsa and contributors from the Nuxt team, has produced a catalogue of small, well-engineered, zero-dependency utilities that are among the most underrated packages in the entire npm ecosystem. Many of them are used internally by Nuxt, Vite, and Nitro without appearing in application-level dependencies, which means millions of developers depend on them without knowing they exist.
The shared design philosophy across UnJS packages is worth understanding because it explains why they tend to outperform alternatives discovered through standard npm search. Every UnJS package is written in TypeScript from the start, ships bundled types, has zero or near-zero runtime dependencies, and is tested comprehensively before release. The team also applies consistent cross-platform discipline — pathe exists specifically because Node's path module behaves differently on Windows, a problem that affects every developer who builds on Mac or Linux and deploys to Windows CI runners, even if they have never noticed it.
The ecosystem effect compounds across the packages. c12 uses pathe for path resolution, consola for logging, and mlly for ES module utilities — all UnJS packages. If you add one UnJS package to your project and start reading its source, you will discover several others solving adjacent problems you did not realize had clean solutions. The horizontal integration across the UnJS suite means they tend to work together without the version conflict and API mismatch problems that arise when assembling utilities from unrelated authors.
The counterargument to recommending UnJS packages broadly is that their primary testing and usage context is Nuxt and Nitro. A package that works excellently in a Nuxt application context may not handle edge cases encountered in other environments. The packages are genuinely general-purpose, but the test suite coverage for non-Nuxt usage patterns is lower than for packages with a more diverse user base. For most utility-level use cases this is a non-issue; for production-critical infrastructure code, it is worth verifying behavior in your specific environment before relying on it.
The Role of Framework Defaults in Package Obscurity
One structural reason high-quality packages stay underrated is that framework scaffolding tools make decisions for developers who never revisit them. When create-t3-app configures Prisma as the ORM, tRPC as the API layer, and Zod as the validation library, the packages in those positions are not underrated — they received adoption through the scaffold's default. The packages adjacent to those defaults — the utilities that Prisma or tRPC themselves depend on, or that solve problems just outside what the scaffold covers — remain undiscovered because developers do not need to evaluate them during setup.
Packages like unstorage, hookable, and c12 from the UnJS ecosystem are excellent examples of this adjacency problem. They are genuinely superior to their alternatives for the problems they solve — universal key-value storage, type-safe plugin hooks, and multi-source config loading respectively — but none of them appear in popular scaffold toolchains because scaffold toolchains are opinionated about the major architectural choices, not about utility-layer packages. A developer who uses Nuxt will encounter these packages automatically because Nuxt's own internals use them. A developer who uses Next.js with a different configuration will likely never encounter them despite the problems they solve being just as relevant.
The implication is that framework-hopping creates discovery gaps. A developer who has primarily worked in the React-Next.js ecosystem has seen a set of packages shaped by that ecosystem's conventions and popular scaffolds. Moving to a different framework or deploying to a different runtime exposes package gaps where the new environment has better solutions that the developer's existing mental model has no room for. The highest-value discovery moments often happen when examining how a different framework's ecosystem solves a problem you have already solved through a familiar but inferior package.
Health Scores as a Discovery Mechanism for Underrated Packages
One underused approach to finding genuinely good underrated packages is to invert the typical search order: search by health score rather than by download count. Most package discovery tools — npm search, Bundlephobia, Even Moose — sort by popularity, which creates a feedback loop that privileges already-popular packages. Starting from health metrics breaks that loop.
The packages that show up at the top of a health-sorted list in a given category tend to be well-maintained packages that have not yet captured broad mindshare. Health metrics measure what a package does, not how many people have heard of it. A package with a 94/100 health score and two hundred thousand weekly downloads is almost definitionally underrated: it has the maintenance discipline of a top-tier package and the download numbers of a niche one. The gap between those two numbers is an opportunity signal.
Download velocity trend is the second underused metric for underrated discovery. A package at one hundred thousand downloads per week growing at fifteen percent month-over-month is on a trajectory to become mainstream within twelve to eighteen months. Adopting it early means your team builds familiarity before the package hits peak complexity — when a package goes from niche to popular, breaking changes, API debates, and community fragmentation often accompany the transition. Early adoption at the health-score-confirmed phase gives you the benefits of being an informed early adopter without the risk of betting on an unproven package.
The combination that most reliably identifies underrated quality: a health score above 85, weekly downloads between fifty thousand and five hundred thousand, month-over-month growth above ten percent, and zero or one runtime dependencies. These packages are growing through organic quality signals, have earned their health score through real maintenance discipline, and have not yet accumulated the dependency weight that often accompanies popularity. Checking for packages matching this profile in a given category before defaulting to the popular incumbent is a research practice that consistently surfaces options worth evaluating.
Compare health scores and download trends for any npm package at PkgPulse.
Compare Lodash and Underscore package health on PkgPulse.
See also: AVA vs Jest and 20 Fastest-Growing npm Packages in 2026 (Data-Backed), The 20 npm Packages Losing Downloads the Fastest 2026.
See the live comparison
View lodash vs. underscore on PkgPulse →