[GH-ISSUE #4830] Admin api.listUser wrong type return #10083

Closed
opened 2026-04-13 06:00:00 -05:00 by GiteaMirror · 1 comment
Owner

Originally created by @TheDevick on GitHub (Sep 23, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/4830

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

import { auth } from '@/lib/auth'
import { authClient } from '@/lib/auth/client'
import { headers } from 'next/headers'

async function fetchUsers(
  page: number,
  perPage: number,
): Promise<(typeof authClient.$Infer.Session.user)[]> {
  const result = await auth.api.listUsers({
    headers: await headers(),
    query: {
      offset: (page - 1) * perPage,
      limit: perPage,
    },
  })

  return result.users
}
Type 'never[] | UserWithRole[]' is not assignable to type '{ id: string; createdAt: Date; updatedAt: Date; email: string; emailVerified: boolean; name: string; image?: string | null | undefined; banned: boolean | null | undefined; role?: string | ... 1 more ... | undefined; banReason?: string | ... 1 more ... | undefined; banExpires?: Date | ... 1 more ... | undefined; }[]'.
  Type 'UserWithRole[]' is not assignable to type '{ id: string; createdAt: Date; updatedAt: Date; email: string; emailVerified: boolean; name: string; image?: string | null | undefined; banned: boolean | null | undefined; role?: string | ... 1 more ... | undefined; banReason?: string | ... 1 more ... | undefined; banExpires?: Date | ... 1 more ... | undefined; }[]'.
    Type 'UserWithRole' is not assignable to type '{ id: string; createdAt: Date; updatedAt: Date; email: string; emailVerified: boolean; name: string; image?: string | null | undefined; banned: boolean | null | undefined; role?: string | ... 1 more ... | undefined; banReason?: string | ... 1 more ... | undefined; banExpires?: Date | ... 1 more ... | undefined; }'.
      Property 'banned' is optional in type 'UserWithRole' but required in type '{ id: string; createdAt: Date; updatedAt: Date; email: string; emailVerified: boolean; name: string; image?: string | null | undefined; banned: boolean | null | undefined; role?: string | ... 1 more ... | undefined; banReason?: string | ... 1 more ... | undefined; banExpires?: Date | ... 1 more ... | undefined; }'.ts(2322)
No quick fixes available

Current vs. Expected behavior

The problem

When listing the users, the return type have an users property, which is an array of users, which sets the property banned as optional.

The problem is: The return type from the auth.api.listUsers() is UserWithRole, which sets the property banned as optional.

But the return type I expected typeof authClient.$Infer.Session.user sets the property banned as required.

My solution

I can get away with this, using a map in the array. But of course, it's not ideal.

import { auth } from '@/lib/auth'
import { authClient } from '@/lib/auth/client'
import { headers } from 'next/headers'

async function fetchUsers(
  page: number,
  perPage: number,
): Promise<(typeof authClient.$Infer.Session.user)[]> {
  const result = await auth.api.listUsers({
    headers: await headers(),
    query: {
      offset: (page - 1) * perPage,
      limit: perPage,
    },
  })

  return result.users.map((user) => ({
    ...user,
    banned: user.banned ?? null, // ensure banned is never undefined
  }))
}

What version of Better Auth are you using?

1.3.9

System info

{
  "system": {
    "platform": "linux",
    "arch": "x64",
    "version": "#1 SMP PREEMPT_DYNAMIC Fri, 19 Sep 2025 15:58:36 +0000",
    "release": "6.16.8-arch1-1",
    "cpuCount": 4,
    "cpuModel": "Intel(R) Core(TM) i5-7400 CPU @ 3.00GHz",
    "totalMemory": "11.63 GB",
    "freeMemory": "2.27 GB"
  },
  "node": {
    "version": "v24.6.0",
    "env": "development"
  },
  "packageManager": {
    "name": "npm",
    "version": "11.5.1"
  },
  "frameworks": [
    {
      "name": "next",
      "version": "^15.5.2"
    },
    {
      "name": "react",
      "version": "^19.1.1"
    }
  ],
  "databases": [
    {
      "name": "pg",
      "version": "^8.16.3"
    },
    {
      "name": "drizzle",
      "version": "^0.44.5"
    }
  ],
  "betterAuth": {
    "version": "^1.3.9",
    "config": null
  }
}

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

Types

Auth config (if applicable)

import { db } from '@/lib/db'
import { betterAuth } from 'better-auth'
import { drizzleAdapter } from 'better-auth/adapters/drizzle'
import { admin, bearer, openAPI } from 'better-auth/plugins'

export const auth = betterAuth({
  database: drizzleAdapter(db, {
    provider: 'pg',
    usePlural: true,
  }),
  emailAndPassword: {
    enabled: true,
    autoSignIn: false,
  },
  session: {
    expiresIn: 60 * 60 * 24 * 7,
    cookieCache: {
      enabled: true,
      maxAge: 5 * 60,
    },
  },
  plugins: [bearer(), admin(), openAPI()],
})





import { ac, roles } from '@/lib/auth/permissions'
import { getPublicEnv } from '@/lib/env/public'
import { adminClient } from 'better-auth/client/plugins'
import { createAuthClient } from 'better-auth/react'

const publicEnv = getPublicEnv()

export const authClient = createAuthClient({
  baseURL: publicEnv.baseURL,
  plugins: [
    adminClient({
      roles,
      ac,
    }),
  ],
})

Additional context

No response

Originally created by @TheDevick on GitHub (Sep 23, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/4830 ### Is this suited for github? - [ ] Yes, this is suited for github ### To Reproduce ```ts import { auth } from '@/lib/auth' import { authClient } from '@/lib/auth/client' import { headers } from 'next/headers' async function fetchUsers( page: number, perPage: number, ): Promise<(typeof authClient.$Infer.Session.user)[]> { const result = await auth.api.listUsers({ headers: await headers(), query: { offset: (page - 1) * perPage, limit: perPage, }, }) return result.users } ``` ``` Type 'never[] | UserWithRole[]' is not assignable to type '{ id: string; createdAt: Date; updatedAt: Date; email: string; emailVerified: boolean; name: string; image?: string | null | undefined; banned: boolean | null | undefined; role?: string | ... 1 more ... | undefined; banReason?: string | ... 1 more ... | undefined; banExpires?: Date | ... 1 more ... | undefined; }[]'. Type 'UserWithRole[]' is not assignable to type '{ id: string; createdAt: Date; updatedAt: Date; email: string; emailVerified: boolean; name: string; image?: string | null | undefined; banned: boolean | null | undefined; role?: string | ... 1 more ... | undefined; banReason?: string | ... 1 more ... | undefined; banExpires?: Date | ... 1 more ... | undefined; }[]'. Type 'UserWithRole' is not assignable to type '{ id: string; createdAt: Date; updatedAt: Date; email: string; emailVerified: boolean; name: string; image?: string | null | undefined; banned: boolean | null | undefined; role?: string | ... 1 more ... | undefined; banReason?: string | ... 1 more ... | undefined; banExpires?: Date | ... 1 more ... | undefined; }'. Property 'banned' is optional in type 'UserWithRole' but required in type '{ id: string; createdAt: Date; updatedAt: Date; email: string; emailVerified: boolean; name: string; image?: string | null | undefined; banned: boolean | null | undefined; role?: string | ... 1 more ... | undefined; banReason?: string | ... 1 more ... | undefined; banExpires?: Date | ... 1 more ... | undefined; }'.ts(2322) No quick fixes available ``` ### Current vs. Expected behavior ## The problem When listing the users, the return type have an `users` property, which is an array of users, which sets the property `banned` as optional. The problem is: The return type from the `auth.api.listUsers()` is `UserWithRole`, which sets the property `banned` as optional. But the return type I expected `typeof authClient.$Infer.Session.user` sets the property `banned` as required. ## My solution I can get away with this, using a map in the array. But of course, it's not ideal. ```ts import { auth } from '@/lib/auth' import { authClient } from '@/lib/auth/client' import { headers } from 'next/headers' async function fetchUsers( page: number, perPage: number, ): Promise<(typeof authClient.$Infer.Session.user)[]> { const result = await auth.api.listUsers({ headers: await headers(), query: { offset: (page - 1) * perPage, limit: perPage, }, }) return result.users.map((user) => ({ ...user, banned: user.banned ?? null, // ensure banned is never undefined })) } ``` ### What version of Better Auth are you using? 1.3.9 ### System info ```bash { "system": { "platform": "linux", "arch": "x64", "version": "#1 SMP PREEMPT_DYNAMIC Fri, 19 Sep 2025 15:58:36 +0000", "release": "6.16.8-arch1-1", "cpuCount": 4, "cpuModel": "Intel(R) Core(TM) i5-7400 CPU @ 3.00GHz", "totalMemory": "11.63 GB", "freeMemory": "2.27 GB" }, "node": { "version": "v24.6.0", "env": "development" }, "packageManager": { "name": "npm", "version": "11.5.1" }, "frameworks": [ { "name": "next", "version": "^15.5.2" }, { "name": "react", "version": "^19.1.1" } ], "databases": [ { "name": "pg", "version": "^8.16.3" }, { "name": "drizzle", "version": "^0.44.5" } ], "betterAuth": { "version": "^1.3.9", "config": null } } ``` ### Which area(s) are affected? (Select all that apply) Types ### Auth config (if applicable) ```typescript import { db } from '@/lib/db' import { betterAuth } from 'better-auth' import { drizzleAdapter } from 'better-auth/adapters/drizzle' import { admin, bearer, openAPI } from 'better-auth/plugins' export const auth = betterAuth({ database: drizzleAdapter(db, { provider: 'pg', usePlural: true, }), emailAndPassword: { enabled: true, autoSignIn: false, }, session: { expiresIn: 60 * 60 * 24 * 7, cookieCache: { enabled: true, maxAge: 5 * 60, }, }, plugins: [bearer(), admin(), openAPI()], }) import { ac, roles } from '@/lib/auth/permissions' import { getPublicEnv } from '@/lib/env/public' import { adminClient } from 'better-auth/client/plugins' import { createAuthClient } from 'better-auth/react' const publicEnv = getPublicEnv() export const authClient = createAuthClient({ baseURL: publicEnv.baseURL, plugins: [ adminClient({ roles, ac, }), ], }) ``` ### Additional context _No response_
GiteaMirror added the lockedbug labels 2026-04-13 06:00:00 -05:00
Author
Owner

@dosubot[bot] commented on GitHub (Sep 23, 2025):

Hi @TheDevick! I'm Dosu and I’m helping the better-auth team.

This type mismatch is a known issue in Better Auth: the admin API's UserWithRole type makes banned optional, while the client session user type expects it to be required. This happens because backend models often allow optional fields for flexibility, but client/session models expect a consistent schema for downstream code. There's no strong justification for banned being optional in the API response—it appears to be an oversight or a result of incomplete mapping, and maintainers have acknowledged similar type mismatches as valid issues in the past (example).

Your workaround (mapping users and setting banned: user.banned ?? null) is common, but not ideal. Aligning the API output to always include banned (even as null) or updating the type definition would resolve this and improve developer experience. Reporting this as a GitHub issue is appropriate and has precedent (example). If you do, include details about the expected vs. actual types, your workaround, and why strict typing matters for your use case.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other  Chat with Dosu Join Discord Share on X

<!-- gh-comment-id:3322391929 --> @dosubot[bot] commented on GitHub (Sep 23, 2025): <!-- Greeting --> Hi @TheDevick! I'm [Dosu](https://go.dosu.dev/dosubot) and I’m helping the better-auth team. <!-- Answer --> This type mismatch is a known issue in Better Auth: the admin API's `UserWithRole` type makes `banned` optional, while the client session user type expects it to be required. This happens because backend models often allow optional fields for flexibility, but client/session models expect a consistent schema for downstream code. There's no strong justification for `banned` being optional in the API response—it appears to be an oversight or a result of incomplete mapping, and maintainers have acknowledged similar type mismatches as valid issues in the past ([example](https://github.com/better-auth/better-auth/issues/443)). Your workaround (mapping users and setting `banned: user.banned ?? null`) is common, but not ideal. Aligning the API output to always include `banned` (even as `null`) or updating the type definition would resolve this and improve developer experience. Reporting this as a GitHub issue is appropriate and has precedent ([example](https://github.com/better-auth/better-auth/issues/2288)). If you do, include details about the expected vs. actual types, your workaround, and why strict typing matters for your use case. <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/b1c573c9-6f88-4fb6-8ea5-6217bb5aa69e?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/b1c573c9-6f88-4fb6-8ea5-6217bb5aa69e?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/b1c573c9-6f88-4fb6-8ea5-6217bb5aa69e?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/b1c573c9-6f88-4fb6-8ea5-6217bb5aa69e?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/b1c573c9-6f88-4fb6-8ea5-6217bb5aa69e?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/b1c573c9-6f88-4fb6-8ea5-6217bb5aa69e?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/b1c573c9-6f88-4fb6-8ea5-6217bb5aa69e?feedback_type=other)</sup>&nbsp;&nbsp;[![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/better-auth/better-auth/issues/4830)
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#10083