npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@stackline/deepmerge

v1.0.1

Published

Secure, immutable, zero-dependency deep merge with a deepmerge-compatible API for ESM, CommonJS, TypeScript, and browsers

Readme

@stackline/deepmerge

Secure, immutable, zero-dependency deep merge for modern JavaScript, with a deepmerge-compatible API.

npm version npm downloads CI license zero dependencies

Docs and playground | npm | Security | Changelog | Issues

Why this package?

Deep merge sits on a trust boundary in configuration loaders, build tools, servers, CLIs, and browser applications. A useful replacement must be safe for untrusted object keys without forcing existing projects to rewrite every merge.

@stackline/deepmerge combines:

  • rejection of __proto__, prototype, and constructor at every depth;
  • immutable merges with cycle and shared-reference preservation;
  • configurable depth and key limits for hostile or malformed inputs;
  • the familiar deepmerge v4 default API and extension hooks;
  • ESM, callable CommonJS, TypeScript, and browser builds;
  • TypeScript compatibility tested from 3.9 through 7.0;
  • zero runtime dependencies.

Installation

Install under the package's public name:

npm install @stackline/deepmerge

Or replace deepmerge without changing application imports:

npm install deepmerge@npm:@stackline/deepmerge

Existing code can continue to use:

import merge from 'deepmerge';

const config = merge(defaults, environment);

Quick start

import merge from '@stackline/deepmerge';

const defaults = {
  server: { port: 3000, headers: { accept: 'application/json' } },
  plugins: ['core']
};

const production = {
  server: { port: 8080, headers: { authorization: 'Bearer token' } },
  plugins: ['metrics']
};

const config = merge(defaults, production);

// {
//   server: {
//     port: 8080,
//     headers: {
//       accept: 'application/json',
//       authorization: 'Bearer token'
//     }
//   },
//   plugins: ['core', 'metrics']
// }

Neither input is mutated.

Secure by default

Dangerous keys are skipped from both inputs before their values are read:

import merge from '@stackline/deepmerge';

const payload = JSON.parse(`{
  "profile": {
    "name": "Ada",
    "constructor": {
      "prototype": { "isAdmin": true }
    }
  }
}`);

const result = merge({}, payload);

console.log(result);                    // { profile: { name: 'Ada' } }
console.log(Object.prototype.isAdmin); // undefined

Use strict rejection when silent filtering is not appropriate:

merge({}, payload, { onUnsafeKey: 'throw' });
// UnsafeKeyError: Refusing to merge unsafe key constructor at
// <root>.profile.constructor

Security limits are enabled by default:

merge(target, source, {
  maxDepth: 1000,
  maxKeys: 100000
});

Set a smaller limit at an exposed API boundary. Infinity is accepted when the input is already trusted.

API compatibility

merge(target, source, options?)

Returns a new merged value. Objects merge recursively. Arrays concatenate by default. When an array and object occupy the same position, the source wins.

merge.all(objects, options?)

const config = merge.all([
  { logging: { level: 'info' } },
  { logging: { format: 'json' } },
  { region: 'ca-central-1' }
]);

Options

| Option | Default | Purpose | | :--- | :--- | :--- | | arrayMerge | concatenate | Replace or customize array behavior | | clone | true | Set false to preserve nested input references | | customMerge | none | Select a merge function for a property | | isMergeableObject | built in | Decide which values can be traversed | | onUnsafeKey | "skip" | Skip or throw on dangerous keys | | maxDepth | 1000 | Bound recursive traversal | | maxKeys | 100000 | Bound enumerable object keys per merge |

The callback options include cloneUnlessOtherwiseSpecified, matching the extension-hook shape used by deepmerge v4.

Named exports

import merge, {
  DeepMergeLimitError,
  UnsafeKeyError,
  all,
  deepmerge,
  isMergeableObject
} from '@stackline/deepmerge';

CommonJS remains callable:

const merge = require('@stackline/deepmerge');

merge({ left: true }, { right: true });
merge.all([{ one: 1 }, { two: 2 }]);

Array strategies

Overwrite arrays:

const overwrite = (_target, source) => source;
const result = merge([1, 2], [3], { arrayMerge: overwrite });
// [3]

Merge arrays by index:

const byIndex = (target, source, options) => {
  const output = target.slice();

  source.forEach((value, index) => {
    output[index] = index in output
      ? merge(output[index], value, options)
      : options.cloneUnlessOtherwiseSpecified(value, options);
  });

  return output;
};

Cycles and shared references

Circular and repeated references are preserved instead of overflowing the stack or being duplicated unexpectedly:

const shared = { enabled: true };
const source = { first: shared, second: shared };
source.self = source;

const result = merge({}, source);

result.first === result.second; // true
result.self === result;         // true

TypeScript

The package ships declaration files for modern ESM, CommonJS, and older TypeScript resolvers. Return types recursively combine the target and source.

import merge from '@stackline/deepmerge';

const result = merge(
  { service: { port: 3000 } },
  { service: { secure: true } }
);

result.service.port;   // number
result.service.secure; // boolean

The release matrix tests TypeScript 3.9, 4.7, 4.9, 5.9, 6.0, and 7.0. The JavaScript runtime supports Node.js 14.17 and newer.

Browser

Use the ESM build with a bundler, or load the small browser global directly:

<script src="https://unpkg.com/@stackline/deepmerge@1/dist/index.min.js"></script>
<script>
  const merged = StacklineDeepmerge(
    { theme: { contrast: 'normal' } },
    { theme: { motion: 'reduced' } }
  );
</script>

Migration from deepmerge

The lowest-change migration uses an npm alias:

npm uninstall deepmerge
npm install deepmerge@npm:@stackline/deepmerge

The compatibility suite covers documented options and 5,000 deterministic, JSON-compatible differential cases against [email protected].

Intentional hardening differences:

  • dangerous keys are always rejected or skipped;
  • cycles are preserved;
  • traversal limits are enabled by default;
  • invalid option values fail early with a controlled error.

See Compatibility for the full contract.

Performance

Security checks, cycle tracking, and resource limits add measurable work. The included benchmark compares this package with [email protected] on the same process:

npm run benchmark

Use benchmark results as regression signals, not universal claims. Runtime, CPU, input shape, and custom callbacks materially affect throughput.

Adoption resources

The examples are included in the npm tarball and run against the package's public exports. They cover hostile configuration input, custom array strategy, cycles, and shared references.

Trust and maintenance

  • No runtime dependencies.
  • Every release is built from the public repository.
  • CI validates behavior, types, package exports, clean installs, and supported runtimes.
  • Security reports have a dedicated private process in SECURITY.md.
  • Release history is recorded in CHANGELOG.md.

Contributing

Read CONTRIBUTING.md before opening a pull request. Changes to compatibility or security behavior require focused regression tests.

License

MIT. See LICENSE and NOTICE.

@stackline/deepmerge is an independent project and is not affiliated with or endorsed by the maintainers of the deepmerge package.