Skip to main content

The 50 Most Underrated npm Packages in 2026

·PkgPulse Team

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

Compare health scores and download trends for any npm package at PkgPulse.

Comments

Stay Updated

Get the latest package insights, npm trends, and tooling tips delivered to your inbox.