deepmerge
The fastest deepmerge implementation for merging JavaScript objects without mutating the originals.
Repository Health
Technical Analysis
@fastify/deepmerge recursively merges the enumerable properties of two or more objects into a brand-new object, leaving every input untouched. It started life inside the Fastify ecosystem, where deep-merging plugin options and configuration objects at high request volume made merge performance a real bottleneck, and it now ships as a standalone, dependency-free utility that any Node.js project can use.
What sets it apart from older deepmerge implementations is speed combined with real configurability: arrays concatenate by default but a custom mergeArray function can replace, deep-merge by index, or apply any other strategy; symbol keys can optionally be merged; prototype-carrying values like streams or buffers can be handled with a cloneProtoObject callback instead of silently breaking; and an onlyDefinedProperties mode lets undefined values in a source object stop short of overwriting real values already in the target — useful for merging partial config objects without clobbering defaults.
Security gets explicit attention rather than an afterthought: __proto__, constructor, and prototype keys are filtered out of both target and source during merging, closing a class of prototype-pollution bugs that plague naive recursive-merge implementations. Dates and RegExps are treated as atomic primitives rather than being torn apart key by key, matching the behavior developers actually expect.
The project is maintained by the Fastify core team and used internally by Fastify itself, but has no framework dependency — it works equally well merging application config, request options, or arbitrary nested data in any JavaScript codebase.
What You Get
- Immutable deep merge - target and source objects are never mutated; a brand-new merged object is always returned
- Configurable array strategy - the default is to concatenate arrays, but a custom
mergeArrayfactory function can replace this with index-based merging, source-replaces-target, or any other policy - Prototype-pollution protection -
__proto__,constructor, andprototypekeys are explicitly filtered out of both target and source during merge onlyDefinedPropertiesmode -undefinedvalues in the source object are ignored instead of overwriting existing target values, useful for merging partial/optional config- Symbol key support - opt in to merging enumerable symbol-keyed properties alongside string keys via the
symbolsoption - Full TypeScript types - a hand-written
.d.tswith conditional types that compute the merged result type at compile time, including a dedicated variant foronlyDefinedPropertiessemantics - Variadic
allmode - pass{ all: true }to merge any number of objects in one call instead of exactly two
Common Use Cases
- Merging Fastify plugin options - combining a plugin’s default options with user-supplied overrides without mutating either
- Layered application configuration - merging a base config file with environment-specific and CLI-supplied overrides in the correct precedence order
- Request-option composition - deep-merging per-request options over a set of client defaults (e.g. HTTP client or database driver wrappers)
- Safe merging of untrusted input - merging JSON parsed from external sources into internal objects without risking prototype pollution
- State reducers - producing a new immutable state object by deep-merging a partial update into existing application state
Under The Hood
Architecture
The entire implementation lives in a single index.js exporting one factory function, deepmergeConstructor(options), which closes over the configured options (symbols, cloneProtoObject, isMergeableObject, onlyDefinedProperties, mergeArray, all) and returns a specialized _deepmerge (or _deepmergeAll when all: true) function. Internally, _deepmerge dispatches on type: primitives and type mismatches (array vs. object) short-circuit to a clone of the source, matching arrays go through the configurable mergeArray strategy (default concatArrays), and matching objects go through mergeObject, which iterates target keys not present in source (cloning them) and then source keys (recursively merging keys shared with target, cloning new ones), explicitly skipping constructor, prototype, and __proto__ via isNotPrototypeKey at every iteration point. This closure-based, single-file design means there is effectively one code path to reason about, and nothing outside index.js participates in the merge logic.
Tech Stack
The package has zero runtime dependencies and targets CommonJS ("type": "commonjs") with a hand-authored types/index.d.ts for TypeScript consumers — there is no build step, transpilation, or bundler in the pipeline; what ships to npm is exactly the source. Development tooling is limited to eslint with the neostandard shared config (JS + TS support), tape for unit tests, c8 for coverage enforcement, tstyche for compile-time type-assertion tests, and benchmark for the dedicated benchmark/ performance-comparison suite. CI runs through a centralized reusable Fastify GitHub Actions workflow (fastify/workflows/plugins-ci.yml) with license-checking and linting both enabled.
Code Quality
Testing is comprehensive and enforced strictly: test:unit runs c8 --check-coverage --100 over index.js against the tape-based suite in test/*.js, meaning any drop in statement/branch/function/line coverage fails CI outright, and a separate test:typescript script runs tstyche to verify the exported TypeScript types resolve correctly for representative merge scenarios. The test suite itself (adapted from the original TehShrike/deepmerge project’s tests) covers ordinary merges, array behavior, Date/RegExp handling, and several dedicated prototype-pollution attack cases (__proto__ and prototype keys in both target and source). Naming is consistent and the file is broken into small, purpose-named internal functions (cloneArray, cloneObject, concatArrays, mergeObject) despite being a single file. No linting or type-safety gaps were found.
What Makes It Unique
Unlike most deepmerge libraries, this one treats array-merge strategy, prototype-object cloning, and undefined-property handling as first-class configuration points rather than hardcoded behavior, letting consumers plug in exactly the semantics their use case needs (e.g. index-based array merging instead of concatenation) without forking the library. It also documents, tests, and defends against prototype pollution as an explicit design goal rather than a bolt-on fix, and its own benchmark suite shows an order-of-magnitude throughput advantage over the original deepmerge package and several of its competitors, which is the primary reason Fastify itself depends on it internally for merging plugin options at scale.
Used by 2 apps in this directory
nango
Developer Tools · Automation · Authentication
Build product integrations with AI using 800+ APIs — auth, proxy, and TypeScript functions on production-grade infrastructure.
OpenReplay
Analytics
Self-hosted session replay and product analytics suite that lets you see exactly what users do on your web app — without sending data to third parties.