nextauthjs/next-auth · error

No user id.

Error message

No user id.

What it means

The Drizzle PostgreSQL adapter's updateUser requires a user id to build its WHERE clause. If the AdapterUser passed in has no id (falsy/undefined), the adapter refuses to issue an UPDATE that would otherwise match every row, and throws this error instead. It is a defensive input-validation guard inside packages/adapter-drizzle/src/lib/pg.ts updateUser.

Source

Thrown at packages/adapter-drizzle/src/lib/pg.ts:193

        .then((res) => res[0])
    },
    async getSessionAndUser(sessionToken: string) {
      return client
        .select({
          session: sessionsTable,
          user: usersTable,
        })
        .from(sessionsTable)
        .where(eq(sessionsTable.sessionToken, sessionToken))
        .innerJoin(usersTable, eq(usersTable.id, sessionsTable.userId))
        .then((res) => (res.length > 0 ? res[0] : null)) as Awaitable<{
        session: AdapterSession
        user: AdapterUser
      } | null>
    },
    async updateUser(data: Partial<AdapterUser> & Pick<AdapterUser, "id">) {
      if (!data.id) {
        throw new Error("No user id.")
      }

      const [result] = await client
        .update(usersTable)
        .set(data)
        .where(eq(usersTable.id, data.id))
        .returning()

      if (!result) {
        throw new Error("No user found.")
      }

      return result as Awaitable<AdapterUser>
    },
    async updateSession(
      data: Partial<AdapterSession> & Pick<AdapterSession, "sessionToken">
    ) {
      return client

View on GitHub (pinned to a1a16a5a77)

Solutions

  1. Ensure the object passed to updateUser includes a valid id: updateUser({ id: user.id, name: 'New Name' }).
  2. Check that the user object originates from getUser/getUserByAccount/getUserByEmail so the id is populated by the adapter.
  3. Add an early check in your code: if (!user.id) throw new Error('id required') before calling updateUser.
  4. If id exists but is falsy at runtime, verify your custom users table id column mapping in the Drizzle schema (e.g. uuid default).

Example fix

// before
await adapter.updateUser({ name: 'Alice' })
// after
await adapter.updateUser({ id: existingUser.id, name: 'Alice' })
Defensive patterns

Strategy: validation

Validate before calling

if (!user || !user.id) throw new Error('updateUser requires a valid user.id')
await adapter.updateUser({ id: user.id, ...patch })

Type guard

function hasId(u: Partial<AdapterUser> | undefined | null): u is AdapterUser & { id: string } {
  return typeof u?.id === 'string' && u.id.length > 0
}

Try / catch

try {
  await adapter.updateUser(patch)
} catch (e) {
  if ((e as Error).message === 'No user id.') {
    throw new Error('Caller bug: user id missing before updateUser')
  }
  throw e
}

Prevention

When it happens

Trigger: Calling updateUser({}) or updateUser({ name: 'x' }) without an id property, or with id: undefined / id: '' — typically from a custom flow that builds the partial user manually instead of receiving it from Auth.js.

Common situations: Custom user-migration or admin scripts that construct AdapterUser objects by hand; spreading an object whose id field was renamed (e.g. userId instead of id); typing the argument loosely as Partial<AdapterUser> so TS doesn't catch the missing id at compile time.

Related errors


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