nextauthjs/next-auth · error

[updateUser] Missing id

Error message

[updateUser] Missing id

What it means

The Firebase adapter's updateUser requires the incoming partial user to carry an id, because the id is needed to resolve the Firestore document reference (C.users.doc(id)). Without it there is nothing to merge into, so it throws '[updateUser] Missing id'.

Source

Thrown at packages/adapter-firebase/src/index.ts:129

    },

    async getUserByEmail(email) {
      return await getOneDoc(C.users.where("email", "==", email))
    },

    async getUserByAccount({ provider, providerAccountId }) {
      const account = await getOneDoc(
        C.accounts
          .where("provider", "==", provider)
          .where(mapper.toDb("providerAccountId"), "==", providerAccountId)
      )
      if (!account) return null

      return await getDoc(C.users.doc(account.userId))
    },

    async updateUser(partialUser) {
      if (!partialUser.id) throw new Error("[updateUser] Missing id")

      const userRef = C.users.doc(partialUser.id)

      await userRef.set(partialUser, { merge: true })

      const user = await getDoc(userRef)
      if (!user) throw new Error("[updateUser] Failed to fetch updated user")

      return user
    },

    async deleteUser(userId) {
      await db.runTransaction(async (transaction) => {
        const accounts = await C.accounts
          .where(mapper.toDb("userId"), "==", userId)
          .get()
        const sessions = await C.sessions
          .where(mapper.toDb("userId"), "==", userId)

View on GitHub (pinned to a1a16a5a77)

Solutions

  1. Always include the document id: updateUser({ id: user.id, ...patch }).
  2. Resolve the id first via getUserByEmail or getUserByAccount when you only have the email/account.
  3. Validate incoming payloads in API routes to require id before invoking updateUser.
  4. Check any mapping layer (Firestore doc -> AdapterUser) preserves the id field.

Example fix

// before
await adapter.updateUser({ name: 'Alice' })
// after
const existing = await adapter.getUserByEmail('alice@example.com')
await adapter.updateUser({ id: existing.id, name: 'Alice' })
Defensive patterns

Strategy: validation

Validate before calling

if (!partialUser?.id) throw new Error('updateUser requires partialUser.id')
await adapter.updateUser(partialUser)

Type guard

function isUserWithId(u: Partial<AdapterUser>): u is Partial<AdapterUser> & Pick<AdapterUser, 'id'> {
  return typeof u.id === 'string' && u.id.length > 0
}

Try / catch

try {
  await adapter.updateUser(partialUser)
} catch (e) {
  if ((e as Error).message === '[updateUser] Missing id') {
    const existing = await adapter.getUserByEmail(partialUser.email!)
    if (existing) return adapter.updateUser({ ...partialUser, id: existing.id })
  }
  throw e
}

Prevention

When it happens

Trigger: Calling updateUser({ email: '...' }) or updateUser({ id: undefined, ... }) — usually when code spreads a payload whose id was stripped, or when upstream Auth.js events pass a user object that lost its id.

Common situations: Custom JWT/session callbacks that rebuild the user object and omit id; mapping between Firestore document field 'userId' and AdapterUser 'id'; API handlers accepting client JSON that lacks the id field.

Related errors


AI-assisted analysis of nextauthjs/next-auth@a1a16a5a77 (2026-08-28). Data as JSON: /api/errors/87ceef077161e13e. Report an issue: GitHub.