defu

Recursively assign default properties to objects with smart array concatenation and full TypeScript inference.

Library
npm
v6.1.7
1,359stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
55/100Fair
Development Activity52
Maintenance20
Community48
Maturity60
Momentum40

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
68/100Good
Architecture68
Code Quality85
Innovation75
Learning Curve45

defu is a lightweight utility for recursively merging a source object with one or more sets of defaults, filling in missing or nullish values without mutating the inputs. It concatenates array values by default, skips dangerous proto/constructor keys to prevent prototype pollution, and preserves special object types like Date, RegExp, and class instances instead of naively deep-merging their properties.

Beyond the core defu function, the library ships createDefu for building custom merge strategies (e.g. summing numeric values), defuFn for treating user-supplied functions as value transformers instead of merge targets, and defuArrayFn for the same behavior scoped to array defaults. Its TypeScript types compute the exact merged shape across an arbitrary chain of default objects, making it a common building block for JS/TS libraries — most notably several packages in the UnJS ecosystem — that need to merge user config with fallback defaults.

What You Get

  • Recursive default-merging via defu(object, …defaults), with leftmost arguments taking priority
  • createDefu(merger) for defining a fully custom merge strategy per key
  • defuFn and defuArrayFn variants that let default values be functions computing or transforming the final value
  • Full TypeScript inference of the merged result type across an arbitrary chain of default objects, exported as the Defu type helper
  • Built-in protection against prototype-pollution attacks via proto/constructor key skipping
  • Zero runtime dependencies and a tiny bundle size, with dual ESM/CJS output built by unbuild

Common Use Cases

  • Resolving a library’s or CLI’s user config against a fallback defaults object without losing nested overrides
  • Merging environment-specific config layers (base config + environment overrides + CLI flags) with predictable array-concatenation semantics
  • Powering framework-level config resolution in UnJS projects (Nuxt, Nitro, unbuild, etc.) where multiple config sources must combine into one
  • Building a custom merge strategy, such as numeric summation or namespaced array joins, via createDefu for domain-specific merge rules
  • Safely merging untrusted or JSON-parsed input with defaults without risking prototype pollution

Under The Hood

Architecture The core is a single recursive function, _defu, in src/defu.ts that walks a destination object’s keys against a defaults object, skipping proto/constructor and nullish values, concatenating arrays, and recursing into nested plain objects; createDefu wraps it in a variadic reduce so defu, defuFn, and defuArrayFn are all thin configurations of the same base function with a different merger callback. A separate module, src/_utils.ts, isolates the isPlainObject predicate (forked from is-plain-obj) that decides whether a value is treated as a mergeable object versus an atomic leaf like a Date, RegExp, or class instance. The notable architectural risk is that src/types.ts maintains a fully independent compile-time mirror of the same merge algorithm (MergeObjects, Merge, Defu) so the TypeScript types stay accurate — any change to the runtime merge rules has to be manually re-implemented at the type level to keep them in sync.

Tech Stack defu has zero runtime dependencies and is written in strict TypeScript, built with unbuild into dual ESM (dist/defu.mjs) and CJS (lib/defu.cjs) outputs with separate .d.mts/.d.cts type declarations. Its dev tooling favors the newer Oxc toolchain (oxlint, oxfmt) over ESLint/Prettier, uses the native tsgo/@typescript/native-preview compiler for fast type-checking, vitest for unit tests, and changelogen for changelog and release automation.

Code Quality Test coverage is thorough for the package’s size: test/defu.test.ts and test/utils.test.ts exercise both runtime behavior with vitest and compile-time type correctness with expect-type’s expectTypeOf, covering edge cases like prototype-pollution attempts, inherited enumerable properties, custom constructors, and mixed array item types. The test:types script gates the main test run behind a full type-check, and CI runs via a GitHub Actions workflow. Naming is direct and functions are small and single-purpose, with no error handling needed since the merge logic never throws.

What Makes It Unique defu’s main differentiation from similar default-merging utilities is its combination of array concatenation (rather than array overwrite) with a pluggable merger callback and TypeScript types that mirror the runtime merge semantics exactly, so the inferred result type reflects real per-key merge behavior rather than falling back to a generic intersection or any. That type-level fidelity, paired with explicit prototype-pollution protection, is the main technical differentiator versus alternatives like lodash’s defaultsDeep.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search