.findOne() is different from .findOne().lean()
Problem
Prerequisites - [X] I have written a descriptive issue title - [X] I have searched existing issues to ensure the bug has not already been reported Mongoose version 6.8.2 Node.js version 16.18.0 MongoDB server version 6.0.1 Typescript version (if applicable) 4.9.4 Description related to https://github.com/Automattic/mongoose/issues/12905 nested paths are not working well when using just .findOne, but works well with lean example this breaks [code block] this do not breaks [code block] we can see nested path in mongosh and robo 3t [code block] it does not work on 6.8.2, 6.8.0 it works on 6.7.0 I will try to find the exactly version that breaks this is a regression Steps to Reproduce [code block] Expected Behavior it should resolve properly with and without .lean()
Unverified for your environment
Select your OS to check compatibility.
1 Fix
Never use .lean() without explicit .select() for every field accessed on the result
Mongoose documents (without .lean()) proxy all schema fields, so accessing an unselected field returns the schema default. Lean objects are plain JS — they only contain keys that were returned by MongoDB. Without a .select(), all fields are returned by default, so .lean() without .select() is safe. The bug occurs when .lean() is combined with a .select() that omits fields that are accessed later in the code.
Awaiting Verification
Be the first to verify this fix
- 1
If you add .lean() to an existing query, check if .select() was already present
Adding .lean() to a query that already has a .select() is the highest-risk pattern. The .select() was written when the document had Mongoose field defaulting; now with .lean() any omitted field silently becomes undefined.
- 2
Audit all field accesses on the result when .lean() and .select() are both present
Read the complete function. List every property access on the result variable. Compare against the .select() string. Any field accessed but not selected is a silent undefined bug.
typescript// Dangerous combination const user = await User.findOne({ email }).select(name email).lean() if (user.role === admin) { ... } // user.role is undefined — .select() omits it - 3
Two safe patterns to choose from
Either remove .select() to return all fields with .lean(), or ensure .select() lists all fields you will access. Never mix a selective .select() with .lean() unless you have audited all field accesses.
typescript// Option A: lean without select — returns all fields const user = await User.findOne({ email }).lean() // Option B: lean with complete select — all accessed fields listed const user = await User.findOne({ email }) .select(name email role createdAt) .lean()
Validation
No undefined field accesses on lean results. Conditional logic evaluates against real values. TypeScript strict null checks catch missing field types.
Sign in to verify this fix
Environment
Submitted by
Alex Chen
2450 rep