Lodash _.updateWith() Method

Beginner
⏱️ 9 min read
📚 Updated: Jul 2026
🎯 6 Examples + 3 Try It
Object utilities

What You’ll Learn

By the end of this tutorial, you’ll use _.updateWith() to transform nested values while controlling how missing path segments are built.

01

Core syntax

_.updateWith(object, path, updater, customizer)

02

Updater fn

Transforms current value at path.

03

Customizer

Builds intermediate containers only.

04

Object / Array

Pass constructors for nested shapes.

05

vs _.update()

When defaults vs custom path building.

06

Real patterns

Increment, toggle, create missing paths.

What Is _.updateWith()?

_.updateWith() combines _.update() with the customizer hook from _.setWith(). The updater transforms the value at a path; the optional customizer decides how missing intermediate containers are created while Lodash walks to that path.

💡
Two functions, two jobs

updater = transform the leaf value. customizer = build missing {} or [] along the way. Do not mix them up.

Use it when you need both value transformation (increment, toggle, uppercase) and explicit control over nested structure—especially array-heavy paths like rows[0].cells[1].count.

📝 Syntax

The signature adds an optional fourth argument—the customizer:

javascript
_.updateWith(object, path, updater, [customizer])

Syntax Rules

  • object — target object (mutated in place).
  • path — same formats as _.update: dot string, brackets, or key array.
  • updater(currentValue) => newValue; transforms the value at the path.
  • customizer — optional (nsValue, key, object) => container; creates missing intermediates only.
  • Return value — the same object reference.
javascript
import updateWith from "lodash/updateWith";

const nested = {
  user: { name: "John", age: 30 }
};

updateWith(nested, "user.age", (age) => age + 1);

// nested.user.age -> 31

⚡ Quick Reference

TaskCode patternResult
Increment nested_.updateWith(obj, "user.age", a => a + 1)Like plain _.update
Force plain objects_.updateWith(obj, path, fn, Object)Object constructor customizer
Array-heavy path_.updateWith(obj, "[0][1]", fn, Array)Array constructor for slots
Custom logic_.updateWith(obj, path, fn, (v, k) => ...)Pick [] vs {} per key
Default fallbackreturn undefined in customizerLodash uses built-in rules
No customizer needed_.update(obj, path, fn)Use _.update()
Customizer
Intermediates

Not the updater

Updater
Leaf value

Transforms result

Mutates?
Yes

Like _.update()

Pair with
_.update()

Simpler transforms

🧰 Parameters

Arguments to _.updateWith() and how updater vs customizer divide responsibility:

object Required

The object to modify. Lodash walks or creates the path inside this object and returns the same reference.

const state = { user: { age: 30 } };
_.updateWith(state, "user.age", a => a + 1)
path Required

Nested location: dot string, bracket notation, or array of keys—identical to _.update.

_.updateWith(o, "a[0].b.count", fn, Object)
updater Required

(currentValue) => newValue. Transforms the value at the final segment; receives undefined if missing.

(age) => age + 1
(n) => (n || 0) + 1
customizer Optional

(nsValue, key, object) => container. Creates missing intermediate objects or arrays only. Return undefined for defaults.

(nsValue, key) =>
  String(+key) === key ? [] : {}

Passing Object or Array as the customizer works because Lodash invokes them with new to construct containers—a common shorthand in Lodash docs.

Examples Gallery

Practical _.updateWith() patterns with sample output and interactive Try It Yourself labs.

📚 Getting Started

Transform existing nested values and create missing paths with updaters.

Example 1 — Increment nested age

Bump user.age by 1 using an updater—no customizer needed when the path exists.

javascript
const nested = {
  user: { name: "John", age: 30 }
};

_.updateWith(nested, "user.age", (age) => age + 1);

console.log(nested.user.age);
// -> 31
Try It Yourself

How It Works

Lodash reads 30, your updater returns 31, and that value is written back at user.age.

Example 2 — Create path with Object customizer

Initialize a missing deep counter using Object to build intermediates and an updater that starts from zero.

javascript
const object = {};

_.updateWith(object, "a[0].b.count", (n) => (n || 0) + 1, Object);

console.log(object);
// -> { a: [{ b: { count: 1 } }] }
Try It Yourself

Example 3 — Array constructor for nested indices

Increment a value at [0][1][2] using Array as the customizer.

javascript
const object = {};

_.updateWith(object, "[0][1][2]", (n) => (n || 0) + 5, Array);

console.log(object[0][1][2]);
// -> 5

Sparse arrays may show empty slots before the assigned index—that is expected when building deep array paths.

📈 Practical Patterns

Custom container logic, config toggles, and update comparisons.

Example 4 — Custom customizer (array vs object)

Choose [] for numeric keys and {} for string keys, then set a cell value via updater.

javascript
const object = {};

const customizer = (nsValue, key) => {
  if (nsValue !== undefined) return nsValue;
  return String(+key) === key ? [] : {};
};

_.updateWith(object, "rows[0].cells[1]", () => "Hello", customizer);

console.log(object.rows[0].cells[1]);
// -> "Hello"
Try It Yourself

Example 5 — Toggle config flag at nested path

Flip notifications.email.enabled in an app config object.

javascript
const config = {
  notifications: { email: { enabled: false } }
};

_.updateWith(config, "notifications.email.enabled", (v) => !v);

console.log(config.notifications.email.enabled);
// -> true

🚀 Beyond the Basics

When to prefer _.updateWith over plain _.update.

Example 6 — When _.update() is enough

For simple dot paths on existing data, _.update() already works—use _.updateWith when you need custom container logic.

javascript
const user = { profile: { name: "ada" } };

_.update(user, "profile.name", (n) => n.toUpperCase());
// Same result for existing paths without customizer:

_.updateWith(user, "profile.name", (n) => n.toUpperCase());

console.log(user.profile.name);
// -> "ADA"

🧠 How _.updateWith() Works

1

Parse path

Lodash splits the path into segments, same as _.update.

Input
2

Customizer creates containers

When a segment is missing, the customizer produces the next object or array. Return undefined for defaults.

Build
3

Updater transforms leaf

The current value at the path is passed to your updater; its return value is assigned at the leaf.

Transform
=

Path updated

The same object reference is returned with the transformed nested value.

📝 Notes

  • The updater transforms the leaf value; the customizer builds intermediates only.
  • Return undefined from the customizer to fall back to Lodash defaults (same as _.update).
  • _.updateWith mutates the target object like _.update.
  • Passing Object or Array as the customizer is a common shorthand for container creation.
  • For simple transforms on existing paths, _.update() is usually enough.
  • Pair conceptually with _.setWith()—updateWith adds an updater; setWith assigns a literal.

Conclusion

_.updateWith() merges the updater pattern from _.update() with the customizer hook from _.setWith(). Use it when you need both value transformation and explicit control over nested container creation.

Remember: updater transforms the leaf, customizer shapes the path. Next in the series: _.values().

💡 Best Practices

✅ Do

  • Use _.updateWith when you need both transform and custom containers
  • Handle undefined in updaters when paths may not exist yet
  • Return existing nsValue from the customizer when reusing containers
  • Prefer plain _.update when the path already exists and defaults suffice
  • Clone first when immutability is required

❌ Don’t

  • Expect the customizer to transform the final value—that is the updater’s job
  • Confuse updateWith customizer with mergeWith merge rules
  • Reach for updateWith when _.update already fits
  • Forget that the updater must return the new leaf value
  • Assume it is immutable like spread-based state updates

Key Takeaways

Knowledge Unlocked

Five things to remember about _.updateWith()

Use these when transforms need custom path building.

5
Core concepts
⚙️ 02

Customizer

Builds path.

Hook
🗃️ 03

Object / Array

Constructor shorthand.

Pattern
🔀 04

vs update

Defaults vs control.

Compare
⚠️ 05

Mutates

Like _.update().

Note

❓ Frequently Asked Questions

_.updateWith() reads the value at a nested path, passes it to an updater function, and writes the return value back—like _.update(). It also accepts a customizer that controls how missing intermediate containers are created along the path.
_.update() uses Lodash defaults when creating missing path segments. _.updateWith() adds an optional customizer so you can force Array, Object, or custom logic for intermediates—same relationship as set vs setWith.
It builds missing intermediate containers while walking the path: (nsValue, key, object) => container. It does not transform the final value—that is the updater's job.
The current value at the path, or undefined if the path does not exist yet. Return the new value to assign at the leaf.
Lodash falls back to default behavior: numeric index keys get arrays, other keys get plain objects—same as _.update() and _.set().
Yes. It mutates the target in place and returns the same object reference. Clone first when immutability matters.
Did you know?

_.updateWith() pairs an updater with a setWith-style customizer. The customizer never transforms the leaf—only the updater does. Some old examples incorrectly use the customizer like a value transformer; keep the two roles separate.

Practice _.updateWith() in the Live Editor

Increment nested values, create paths with Object customizers, and build grid cells.

Open Try It editor →

About the author

Mari Selvan M P
Mari Selvan M P 🔗

Developer, cloud engineer, and technical writer

  • Experience 12 years building web and cloud systems
  • Focus Full Stack Development, AWS, and Developer Education

I write practical tutorials so students and working developers can learn by doing—from databases and APIs to deployment on AWS.

6 people found this page helpful