[GH-ISSUE #2508] internalAdapter.updateUser does not return a user (MongoDB) #9232

Closed
opened 2026-04-13 04:39:02 -05:00 by GiteaMirror · 3 comments
Owner

Originally created by @PhillJK on GitHub (May 1, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/2508

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

Honestly, any function that calls internalAdapter.updateUser

But in my case it was /two-factor/disable api endpoint, it sends 500 error and error message is "# SERVER_ERROR: Error: Invalid id value"

Current vs. Expected behavior

Code from disableTwoFactor endpoint

// Updates database, but does not return the document
const updatedUser = await ctx.context.internalAdapter.updateUser(
            user.id,
            {
              twoFactorEnabled: false
            },
            ctx
          );

console.log("Updated user",updatedUser);

 //Throws an error
await ctx.context.adapter.delete({
  model: opts.twoFactorTable,
  where: [
    {
      field: "userId",
      value: updatedUser.id
    }
  ]
});

The above code logs the following:

Updated user {
  name: undefined,
  email: undefined,
  emailVerified: undefined,
  image: undefined,
  createdAt: undefined,
  updatedAt: undefined,
  twoFactorEnabled: undefined,
  superuser: undefined
}

What version of Better Auth are you using?

1.2.7

Provide environment information

- OS: windows
- Browser: firefox
- Framework: Next.js
- Database: MongoDB with mongodbAdapter

Which area(s) are affected? (Select all that apply)

Backend

Auth config (if applicable)

{
    database: mongodbAdapter(db),
    session: {
        fields: {
            expiresAt: "expires",
            token: "sessionToken",
        },
        additionalFields: {
            organisation: {
                type: "string",
                returned: true,
            },
            role: {
                type: "string",
                returned: true,
            },
            superuser: {
                type: "boolean",
                returned: true,
            },
        },
    },
    user: {
        modelName: "clients",
        additionalFields: {
            superuser: {
                type: "boolean",
                returned: true,
            },
        },
    },
    databaseHooks: {
        session: {
            create: {
                before: async session => {
                    // hook code
                }
            },
        },
    },
    emailAndPassword: {
        sendResetPassword: async ({ user, url, token }, request) => {
           // send reset password code
        },
        enabled: true,
        autoSignIn: false,
        password: {
            hash: hashPassword,
            verify: ({ hash, password }) => {
                return verifyPassword(password, hash);
            },
        },
        requireEmailVerification: true,
    },
    emailVerification: {
        sendVerificationEmail: async ({ user, url, token }, request) => {
            //send verification email code
        },
        autoSignInAfterVerification: true,
    },
    appName: "Test",
    plugins: [twoFactor({ issuer: "Test" })],
}

Additional context

I have tried to call internalAdapter.updateUser myself, same effect. I might have messed up better-auth configuration, but I have followed every step in the docs

Originally created by @PhillJK on GitHub (May 1, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/2508 ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce Honestly, any function that calls internalAdapter.updateUser But in my case it was /two-factor/disable api endpoint, it sends 500 error and error message is "# SERVER_ERROR: Error: Invalid id value" ### Current vs. Expected behavior Code from disableTwoFactor endpoint ```javascript // Updates database, but does not return the document const updatedUser = await ctx.context.internalAdapter.updateUser( user.id, { twoFactorEnabled: false }, ctx ); console.log("Updated user",updatedUser); //Throws an error await ctx.context.adapter.delete({ model: opts.twoFactorTable, where: [ { field: "userId", value: updatedUser.id } ] }); ``` The above code logs the following: ``` Updated user { name: undefined, email: undefined, emailVerified: undefined, image: undefined, createdAt: undefined, updatedAt: undefined, twoFactorEnabled: undefined, superuser: undefined } ``` ### What version of Better Auth are you using? 1.2.7 ### Provide environment information ```bash - OS: windows - Browser: firefox - Framework: Next.js - Database: MongoDB with mongodbAdapter ``` ### Which area(s) are affected? (Select all that apply) Backend ### Auth config (if applicable) ```typescript { database: mongodbAdapter(db), session: { fields: { expiresAt: "expires", token: "sessionToken", }, additionalFields: { organisation: { type: "string", returned: true, }, role: { type: "string", returned: true, }, superuser: { type: "boolean", returned: true, }, }, }, user: { modelName: "clients", additionalFields: { superuser: { type: "boolean", returned: true, }, }, }, databaseHooks: { session: { create: { before: async session => { // hook code } }, }, }, emailAndPassword: { sendResetPassword: async ({ user, url, token }, request) => { // send reset password code }, enabled: true, autoSignIn: false, password: { hash: hashPassword, verify: ({ hash, password }) => { return verifyPassword(password, hash); }, }, requireEmailVerification: true, }, emailVerification: { sendVerificationEmail: async ({ user, url, token }, request) => { //send verification email code }, autoSignInAfterVerification: true, }, appName: "Test", plugins: [twoFactor({ issuer: "Test" })], } ``` ### Additional context I have tried to call internalAdapter.updateUser myself, same effect. I might have messed up better-auth configuration, but I have followed every step in the docs
GiteaMirror added the locked label 2026-04-13 04:39:02 -05:00
Author
Owner

@PhillJK commented on GitHub (May 2, 2025):

After some debugging, I narrowed down the problem to mongodbAdapter's function transformOutput. The function expects to get argument data, which is the updated document returned from db.collection.findOneAndUpdate, but instead is getting the following:

{
  lastErrorObject: { n: 1, updatedExisting: true },
  value: {
     //updated document
  },
  ok: 1
}

I'm encountering inconsistent behavior with findOneAndUpdate. When updating some collections, it returns the updated document as expected. However, when updating other collections—such as the users collection—it returns a result object containing lastErrorObject, value, and ok, instead of just the updated document.

The following fixed my problem, but I do not know if it's a bug or I have messed up something:

// update method of mongodbAdapter
async update(data) {
      const { model, where, update: values } = data;
      const clause = transform.convertWhereClause(where, model);
      const transformedData = transform.transformInput(values, model, "update");
      const res = await db.collection(transform.getModelName(model)).findOneAndUpdate(
        clause,
        { $set: transformedData },
        {
          returnDocument: "after",
        }
      );
      if (!res) return null;
      return transform.transformOutput(res?.value ? res.value : res, model); //fix
    },
<!-- gh-comment-id:2847369518 --> @PhillJK commented on GitHub (May 2, 2025): After some debugging, I narrowed down the problem to mongodbAdapter's function transformOutput. The function expects to get argument **data**, which is the updated document returned from db.collection.findOneAndUpdate, but instead is getting the following: ```javascript { lastErrorObject: { n: 1, updatedExisting: true }, value: { //updated document }, ok: 1 } ``` I'm encountering inconsistent behavior with findOneAndUpdate. When updating some collections, it returns the updated document as expected. However, when updating other collections—such as the users collection—it returns a result object containing lastErrorObject, value, and ok, instead of just the updated document. The following fixed my problem, but I do not know if it's a bug or I have messed up something: ```javascript // update method of mongodbAdapter async update(data) { const { model, where, update: values } = data; const clause = transform.convertWhereClause(where, model); const transformedData = transform.transformInput(values, model, "update"); const res = await db.collection(transform.getModelName(model)).findOneAndUpdate( clause, { $set: transformedData }, { returnDocument: "after", } ); if (!res) return null; return transform.transformOutput(res?.value ? res.value : res, model); //fix }, ```
Author
Owner

@ceigey commented on GitHub (Jun 30, 2025):

findOneAndUpdate has some pretty tricky overloads:

https://mongodb.github.io/node-mongodb-native/6.9/classes/Collection.html#findOneAndUpdate

Both return types could theoretically be handled by checking if _id is present I guess, but making it consistent would be easier to reason about too.

<!-- gh-comment-id:3019659889 --> @ceigey commented on GitHub (Jun 30, 2025): findOneAndUpdate has some pretty tricky overloads: https://mongodb.github.io/node-mongodb-native/6.9/classes/Collection.html#findOneAndUpdate Both return types could theoretically be handled by checking if _id is present I guess, but making it consistent would be easier to reason about too.
Author
Owner

@PhillJK commented on GitHub (Jul 1, 2025):

By looking at the commit history I believe the issue is resolved. Therefore I'm closing the issue

<!-- gh-comment-id:3025350675 --> @PhillJK commented on GitHub (Jul 1, 2025): By looking at the commit history I believe the issue is resolved. Therefore I'm closing the issue
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#9232