# Why findoneandupdate mongoose Returns the Old Document (And How to Fix It)

> Confused why findOneAndUpdate Mongoose returns the old document? Learn how this default behavior works and how to easily fix it to get the updated document.

- Repository: [mongodb/mongo](https://github.com/mongodb/mongo)
- Tags: how-to-guide
- Published: 2026-02-16

---

**Mongoose's `findOneAndUpdate` returns the pre-update document by default because it wraps MongoDB's `findAndModify` command, which uses `new: false` unless you explicitly set `new: true` or the modern equivalent `returnDocument: 'after'`.**

The `findoneandupdate mongoose` method is a staple for atomic update operations, yet developers frequently encounter confusion when the returned document reflects the old state rather than the updated one. This behavior stems from the underlying MongoDB server implementation in the [mongodb/mongo](https://github.com/mongodb/mongo) repository rather than a bug in Mongoose itself. Understanding how the server processes the `new` flag reveals why you must explicitly request the post-update document.

## The Root Cause: MongoDB's findAndModify Command

At the database level, Mongoose's `findOneAndUpdate` invokes MongoDB's **`findAndModify`** command. This command accepts a **`new`** boolean option that determines which version of the document the server returns:

- **`new: false`** (default): Returns the document as it was before the update.
- **`new: true`**: Returns the document after the update has been applied.

When you omit this option, the server defaults to `false`, sending back the original document even though the update succeeds.

### Server-Side Logic in find_and_modify.cpp

The MongoDB server constructs the response in [`src/mongo/db/commands/query_cmd/find_and_modify.cpp`](https://github.com/mongodb/mongo/blob/main/src/mongo/db/commands/query_cmd/find_and_modify.cpp). The `CmdFindAndModify::Invocation::typedRun` method calls `buildResponse` to generate the reply:

```cpp
write_ops::FindAndModifyCommandReply CmdFindAndModify::Invocation::typedRun(
    ...
    return buildResponse(updateResult, req.getRemove().value_or(false), docFound);

```

The `buildResponse` function uses the `new` option parsed from the request to decide whether to populate the response with the pre-update or post-update document.

### How the new Flag Is Processed in update_util.cpp

The flag is evaluated during request parsing in [`src/mongo/db/update/update_util.cpp`](https://github.com/mongodb/mongo/blob/main/src/mongo/db/update/update_util.cpp). The server sets the return behavior based on the `new` parameter:

```cpp
requestOut->setReturnDocs((request.getNew().value_or(false)) ?
                          UpdateRequest::RETURN_NEW :
                          UpdateRequest::RETURN_OLD);

```

If `request.getNew()` evaluates to `false` (the default when omitted), the server configures the operation to return the old document (`RETURN_OLD`).

## Mongoose-Specific Options for findoneandupdate

While the server controls the logic, Mongoose abstracts the option naming across different versions. You must use the correct option name for your Mongoose version to receive the updated document:

| Mongoose Version | Option to Return Updated Document |
|------------------|-----------------------------------|
| ≤ 5.x            | `{ new: true }`                   |
| ≥ 6.0            | `{ returnDocument: 'after' }` or `{ returnOriginal: false }` |

Mongoose 6.0+ aligns with the native MongoDB driver's modern API, introducing `returnDocument` and `returnOriginal` while maintaining backward compatibility with `new: true`.

### Code Examples

For Mongoose 5.x and earlier, explicitly set `new: true`:

```javascript
// Returns the updated document (Mongoose 5.x)
const doc = await Model.findOneAndUpdate(
  { _id: userId },
  { $set: { status: 'active' } },
  { new: true }  // Required to get the post-update document
);
console.log(doc.status); // 'active'

```

For Mongoose 6.0+, use the modern syntax:

```javascript
// Returns the updated document (Mongoose 6.0+)
const doc = await Model.findOneAndUpdate(
  { _id: userId },
  { $set: { status: 'active' } },
  { returnDocument: 'after' }  // 'after' returns post-update, 'before' returns pre-update
);

```

Both approaches ensure the server returns the document after the update has been applied.

## Common Pitfalls with findoneandupdate mongoose

Even with the correct syntax, several edge cases cause confusion:

**Omitting the return option** is the most frequent mistake. Without `{ new: true }` or `{ returnDocument: 'after' }`, the method resolves with the original document, making it appear as though the update failed.

**Version mismatch** occurs when developers upgrade Mongoose but continue using deprecated option names. While Mongoose 6+ accepts `new: true`, using `returnDocument` in Mongoose 5.x results in the option being ignored, defaulting to the old document.

**Upsert operations** behave differently when combined with return options. If `upsert: true` creates a new document and you omit the `new` flag (or equivalent), the method returns `null` rather than the inserted document because there was no "old" document to return.

## Summary

- `findoneandupdate mongoose` wraps MongoDB's `findAndModify` command, which defaults to returning the pre-update document.
- The server logic in [`src/mongo/db/commands/query_cmd/find_and_modify.cpp`](https://github.com/mongodb/mongo/blob/main/src/mongo/db/commands/query_cmd/find_and_modify.cpp) and [`src/mongo/db/update/update_util.cpp`](https://github.com/mongodb/mongo/blob/main/src/mongo/db/update/update_util.cpp) checks the `new` flag to determine whether to return `RETURN_OLD` or `RETURN_NEW`.
- Use `{ new: true }` for Mongoose 5.x and earlier, or `{ returnDocument: 'after' }` / `{ returnOriginal: false }` for Mongoose 6.0+.
- Always include the appropriate return option when you need the post-update document, especially during upserts.

## Frequently Asked Questions

### Why does findOneAndUpdate return null instead of the updated document?

When you perform an upsert (creating a document if it doesn't exist) and omit the `new: true` or `returnDocument: 'after'` option, `findOneAndUpdate` returns `null` because there was no existing document to return as the "old" version. Always set the return option to `'after'` or `true` when using upserts to receive the newly created or updated document.

### What is the difference between new: true and returnDocument: 'after'?

Both options achieve the same result—returning the document after the update has been applied—but they belong to different API versions. `{ new: true }` is the legacy option used in Mongoose 5.x and earlier, while `{ returnDocument: 'after' }` was introduced in Mongoose 6.0 to align with the native MongoDB driver's modern syntax. Mongoose 6+ supports both for backward compatibility.

### Does findOneAndUpdate return the updated document by default in the latest MongoDB driver?

No, neither the native MongoDB driver nor Mongoose returns the updated document by default. The underlying `findAndModify` command defaults to `new: false` (or `returnDocument: 'before'`), meaning the server returns the document state prior to applying the update. You must explicitly set `returnDocument: 'after'` (or `new: true` in older versions) to receive the post-update document.

### How can I verify that findOneAndUpdate actually performed the update if I don't get the new document back?

If you must use the default return behavior (receiving the old document), you can verify the update by checking the document in a subsequent query or by examining the command's metadata. However, the most efficient approach is to simply include the appropriate return option (`new: true` or `returnDocument: 'after'`) in your initial call, eliminating the need for a second database round-trip and immediately confirming the updated state.