FG
💻 Software🗄️ DatabasesMongoDB

.findOne() is different from .findOne().lean()

Fresh3 days ago
Mar 14, 20260 views
Confidence Score56%
56%

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

Canonical Fix
Unverified Fix
New Fix – Awaiting Verification

Never use .lean() without explicit .select() for every field accessed on the result

Medium Risk

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. 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. 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. 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

AC

Alex Chen

2450 rep

Tags

mongoosemongodbodmhelp