Lodash _.mergeWith() method
What you’ll learn
- How
_.mergeWith(object, ...sources, customizer)extends _.merge(). - How to concatenate arrays while keeping Lodash’s normal recursive object merge.
- Why returning
undefinedfrom the customizer is the fallback signal. - How to write field-specific rules without accidentally breaking nested merges.
Prerequisites
You should already understand _.merge(): it is recursive, it mutates the destination, and its default array behavior is positional by index.
- Same mutation contract: the first argument is still modified and returned.
- Customizer is selective: use it only where default
_.merge()rules are not enough. undefinedmeans fallback: returning it tells Lodash to continue with normal deep merge.
Overview
_.mergeWith behaves like _.merge, but before Lodash chooses the merged value it asks your customizer. If your customizer returns a value, that value wins. If it returns undefined, Lodash uses the normal recursive merge behavior for that property.
Customize only conflicts
Great for arrays, counters, special keys, and domain-specific merge rules.
Mutates & returns
Same as _.merge: the destination absorbs the result.
Fallback via undefined
Returning undefined is intentional, not a mistake.
Syntax
_.mergeWith(object, [...sources], customizer) - object: destination object. It is mutated and returned.
- sources: one or more source objects merged left to right.
- customizer: invoked as
(objValue, srcValue, key, object, source, stack). - Return rule: return a custom value to override, or
undefinedfor Lodash’s default merge.
Concatenate arrays during a deep merge
The most common _.mergeWith use case is replacing _.merge’s positional array behavior with concatenation.
import mergeWith from "lodash/mergeWith";
const defaults = {
modules: ["core", "auth"],
theme: { colors: ["blue"], mode: "light" }
};
const project = {
modules: ["payments"],
theme: { colors: ["green"] }
};
const result = mergeWith({}, defaults, project, (objValue, srcValue) => {
if (Array.isArray(objValue)) {
return objValue.concat(srcValue);
}
return undefined; // keep Lodash's normal deep merge
});
// -> {
// modules: ["core", "auth", "payments"],
// theme: { colors: ["blue", "green"], mode: "light" }
// } Use field-specific merge rules
The customizer receives the current key, so you can treat specific fields differently while letting everything else merge normally.
import mergeWith from "lodash/mergeWith";
const current = {
stats: { views: 10, likes: 2 },
tags: ["lodash"],
status: "draft"
};
const patch = {
stats: { views: 5, likes: 4 },
tags: ["objects"],
status: "published"
};
const merged = mergeWith({}, current, patch, (objValue, srcValue, key) => {
if (key === "views" || key === "likes") {
return (objValue || 0) + (srcValue || 0);
}
if (key === "tags" && Array.isArray(objValue)) {
return Array.from(new Set(objValue.concat(srcValue)));
}
return undefined;
});
// -> {
// stats: { views: 15, likes: 6 },
// tags: ["lodash", "objects"],
// status: "published"
// } The undefined fallback rule
A customizer that always returns something disables normal deep merging for those values. Return undefined for values you do not want to customize.
import mergeWith from "lodash/mergeWith";
const left = {
options: { theme: "light", pageSize: 20 },
roles: ["reader"]
};
const right = {
options: { pageSize: 50 },
roles: ["admin"]
};
const safe = mergeWith({}, left, right, (objValue, srcValue) => {
if (Array.isArray(objValue)) {
return objValue.concat(srcValue);
}
return undefined;
});
// -> {
// options: { theme: "light", pageSize: 50 },
// roles: ["reader", "admin"]
// } 📋 _.mergeWith vs related operations
| Topic | _.mergeWith | _.merge | _.assignWith | Manual reducer |
|---|---|---|---|---|
| Depth | Recursive | Recursive | Shallow | You decide |
| Customizer | Yes | No | Yes | Manual |
| Mutates first arg | Yes | Yes | Yes | Depends on implementation |
| Array concat | Yes, with customizer | No, merges by index | No, arrays replace | You decide |
| Best use | Custom deep merge rules | Default deep merge | Custom shallow assignment | Very specialized rules |
Reach for _.mergeWith when most of your data can use normal deep merge, but a few value types or fields need special handling.
Pitfalls to avoid
Returning too much disables default merge
Only return a custom value for cases you truly handle. For everything else, return undefined.
The destination still mutates
_.mergeWith(defaults, overrides, customizer) rewrites defaults. Use {} first when the destination should be fresh.
Check both array values when needed
Most concat customizers check Array.isArray(objValue). If a source can provide arrays where the destination is missing, decide how that should behave.
Avoid untrusted merge sources
Like _.merge, this method can expose prototype pollution risks if attacker-controlled objects are merged into shared targets.
❓ FAQ
Summary
- Purpose: recursively merge sources while letting a customizer override selected merge decisions.
- Remember: it mutates the destination, and
undefinedfrom the customizer means "use Lodash's default merge." - Next: Lodash _.omit(), _.merge(), or the official Lodash docs for _.mergeWith.
_.mergeWith calls your customizer for each merge conflict. Return undefined when you want Lodash to continue with its normal recursive merge; return anything else to use your custom value.
5 people found this page helpful
