Admin (server & client) plugin type error #2067

Closed
opened 2026-03-13 09:24:19 -05:00 by GiteaMirror · 20 comments
Owner

Originally created by @Hezaerd on GitHub (Oct 2, 2025).

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

Try to add the admin plugin to both/either the server and client auth config

Error:

Type '{ id: "admin"; init(): { options: { databaseHooks: { user: { create: { before(user: { id: string; createdAt: Date; updatedAt: Date; email: string; emailVerified: boolean; name: string; image?: string; } & Record<...>): Promise<...>; }; }; session: { ...; }; }; }; }; ... 4 more ...; options: any; }' is not assignable to type 'BetterAuthPlugin'.
  The types returned by 'init(...)' are incompatible between these types.
    Type '{ options: { databaseHooks: { user: { create: { before(user: { id: string; createdAt: Date; updatedAt: Date; email: string; emailVerified: boolean; name: string; image?: string; } & Record<string, unknown>): Promise<...>; }; }; session: { ...; }; }; }; }' is not assignable to type 'void | Awaitable<{ context?: { adapter?: { id?: string; create?: <T extends Record<string, any>, R = T>(data: { model: string; data: Omit<T, "id">; select?: string[]; forceAllowId?: boolean; }) => Promise<R>; ... 9 more ...; options?: { ...; }; }; ... 19 more ...; publishTelemetry?: (event: TelemetryEvent) => Promis...'.
      Type '{ options: { databaseHooks: { user: { create: { before(user: { id: string; createdAt: Date; updatedAt: Date; email: string; emailVerified: boolean; name: string; image?: string; } & Record<string, unknown>): Promise<...>; }; }; session: { ...; }; }; }; }' is not assignable to type '{ context?: { adapter?: { id?: string; create?: <T extends Record<string, any>, R = T>(data: { model: string; data: Omit<T, "id">; select?: string[]; forceAllowId?: boolean; }) => Promise<R>; ... 9 more ...; options?: { ...; }; }; ... 19 more ...; publishTelemetry?: (event: TelemetryEvent) => Promise<...>; }; option...'.
        The types of 'options.databaseHooks.user.create.before' are incompatible between these types.
          Type '(user: { id: string; createdAt: Date; updatedAt: Date; email: string; emailVerified: boolean; name: string; image?: string; } & Record<string, unknown>) => Promise<{ data: { id: string; ... 6 more ...; role: string; }; }>' is not assignable to type '(user: { id: string; createdAt: Date; updatedAt: Date; emailVerified: boolean; name: string; email?: string; image?: string; } & Record<string, unknown>, context?: GenericEndpointContext) => Promise<...>'.
            Types of parameters 'user' and 'user' are incompatible.
              Type '{ id: string; createdAt: Date; updatedAt: Date; emailVerified: boolean; name: string; email?: string; image?: string; } & Record<string, unknown>' is not assignable to type '{ id: string; createdAt: Date; updatedAt: Date; email: string; emailVerified: boolean; name: string; image?: string; } & Record<string, unknown>'.
                Type '{ id: string; createdAt: Date; updatedAt: Date; emailVerified: boolean; name: string; email?: string; image?: string; } & Record<string, unknown>' is not assignable to type '{ id: string; createdAt: Date; updatedAt: Date; email: string; emailVerified: boolean; name: string; image?: string; }'.
                  Property 'email' is optional in type '{ id: string; createdAt: Date; updatedAt: Date; emailVerified: boolean; name: string; email?: string; image?: string; } & Record<string, unknown>' but required in type '{ id: string; createdAt: Date; updatedAt: Date; email: string; emailVerified: boolean; name: string; image?: string; }'.

Current vs. Expected behavior

No linting error

What version of Better Auth are you using?

1.3.23

System info

{
  "system": {
    "platform": "win32",
    "arch": "x64",
    "version": "Windows 11 Home",
    "release": "10.0.26100",
    "cpuCount": 16,
    "cpuModel": "AMD Ryzen 9 5900HS with Radeon Graphics        ",
    "totalMemory": "15.41 GB",
    "freeMemory": "4.21 GB"
  },
  "node": {
    "version": "v22.19.0",
    "env": "development"
  },
  "packageManager": {
    "name": "npm",
    "version": "10.9.3"
  },
  "frameworks": [
    {
      "name": "next",
      "version": "15.5.4"
    },
    {
      "name": "react",
      "version": "19.1.0"
    }
  ],
  "databases": [
    {
      "name": "@prisma/client",
      "version": "^6.16.2"
    }
  ],
  "betterAuth": {
    "version": "^1.3.23",
    "config": {
      "account": {
        "accountLinking": {
          "enabled": true
        }
      },
      "socialProviders": {
        "discord": {
          "clientId": "[REDACTED]",
          "clientSecret": "[REDACTED]"
        }
      },
      "plugins": [
        {
          "name": "anonymous",
          "config": {
            "id": "anonymous",
            "endpoints": {},
            "hooks": {
              "after": [
                {}
              ]
            },
            "schema": {
              "user": {
                "fields": {
                  "isAnonymous": {
                    "type": "boolean",
                    "required": false
                  }
                }
              }
            },
            "$ERROR_CODES": {
              "FAILED_TO_CREATE_USER": "Failed to create user",
              "COULD_NOT_CREATE_SESSION": "Could not create session",
              "ANONYMOUS_USERS_CANNOT_SIGN_IN_AGAIN_ANONYMOUSLY": "Anonymous users cannot sign in again anonymously"
            }
          }
        },
        {
          "name": "admin",
          "config": {
            "id": "admin",
            "hooks": {
              "after": [
                {}
              ]
            },
            "endpoints": {},
            "$ERROR_CODES": {
              "FAILED_TO_CREATE_USER": "Failed to create user",
              "USER_ALREADY_EXISTS": "User already exists.",
              "USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL": "User already exists. Use another email.",
              "YOU_CANNOT_BAN_YOURSELF": "You cannot ban yourself",
              "YOU_ARE_NOT_ALLOWED_TO_CHANGE_USERS_ROLE": "You are not allowed to change users role",
              "YOU_ARE_NOT_ALLOWED_TO_CREATE_USERS": "You are not allowed to create users",
              "YOU_ARE_NOT_ALLOWED_TO_LIST_USERS": "You are not allowed to list users",
              "YOU_ARE_NOT_ALLOWED_TO_LIST_USERS_SESSIONS": "You are not allowed to list users sessions",
              "YOU_ARE_NOT_ALLOWED_TO_BAN_USERS": "You are not allowed to ban users",
              "YOU_ARE_NOT_ALLOWED_TO_IMPERSONATE_USERS": "You are not allowed to impersonate users",
              "YOU_ARE_NOT_ALLOWED_TO_REVOKE_USERS_SESSIONS": "You are not allowed to revoke users sessions",
              "YOU_ARE_NOT_ALLOWED_TO_DELETE_USERS": "You are not allowed to delete users",
              "YOU_ARE_NOT_ALLOWED_TO_SET_USERS_PASSWORD": "[REDACTED]",
              "BANNED_USER": "You have been banned from this application",
              "YOU_ARE_NOT_ALLOWED_TO_GET_USER": "You are not allowed to get user",
              "NO_DATA_TO_UPDATE": "No data to update",
              "YOU_ARE_NOT_ALLOWED_TO_UPDATE_USERS": "You are not allowed to update users",
              "YOU_CANNOT_REMOVE_YOURSELF": "You cannot remove yourself"
            },
            "schema": {
              "user": {
                "fields": {
                  "role": {
                    "type": "string",
                    "required": false,
                    "input": false
                  },
                  "banned": {
                    "type": "boolean",
                    "defaultValue": false,
                    "required": false,
                    "input": false
                  },
                  "banReason": {
                    "type": "string",
                    "required": false,
                    "input": false
                  },
                  "banExpires": {
                    "type": "date",
                    "required": false,
                    "input": false
                  }
                }
              },
              "session": {
                "fields": {
                  "impersonatedBy": {
                    "type": "string",
                    "required": false
                  }
                }
              }
            },
            "options": {
              "defaultRole": "user",
              "adminRoles": [
                "admin"
              ]
            }
          }
        }
      ]
    }
  }
}

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

Backend, Client, Types

Auth config (if applicable)

// lib/auth.ts
import { betterAuth } from "better-auth";
import { prismaAdapter } from "better-auth/adapters/prisma";
import { admin, anonymous } from "better-auth/plugins";
import { prisma } from "@/lib/prisma";

export const auth = betterAuth({
  database: prismaAdapter(prisma, {
    provider: "postgresql",
  }),
  account: {
    accountLinking: {
      enabled: true,
    },
  },
  socialProviders: {
    discord: {
      clientId: process.env.DISCORD_CLIENT_ID,
      clientSecret: process.env.DISCORD_CLIENT_SECRET,
    },
  },
  plugins: [
    anonymous(),
    admin({
      defaultRole: "user",
      adminRoles: ["admin"],
    }),
  ],
});

export type Session = typeof auth.$Infer.Session;

// lib/auth-client.ts
import { adminClient, anonymousClient } from "better-auth/client/plugins";
import { createAuthClient } from "better-auth/react";

export const authClient = createAuthClient({
  baseURL: process.env.BETTER_AUTH_URL,
  plugins: [anonymousClient(), adminClient()],
});

export const { signIn, signOut, signUp, useSession } = authClient;
export type Session = typeof authClient.$Infer.Session;

Additional context

No response

Originally created by @Hezaerd on GitHub (Oct 2, 2025). ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce Try to add the admin plugin to both/either the server and client auth config Error: ``` Type '{ id: "admin"; init(): { options: { databaseHooks: { user: { create: { before(user: { id: string; createdAt: Date; updatedAt: Date; email: string; emailVerified: boolean; name: string; image?: string; } & Record<...>): Promise<...>; }; }; session: { ...; }; }; }; }; ... 4 more ...; options: any; }' is not assignable to type 'BetterAuthPlugin'. The types returned by 'init(...)' are incompatible between these types. Type '{ options: { databaseHooks: { user: { create: { before(user: { id: string; createdAt: Date; updatedAt: Date; email: string; emailVerified: boolean; name: string; image?: string; } & Record<string, unknown>): Promise<...>; }; }; session: { ...; }; }; }; }' is not assignable to type 'void | Awaitable<{ context?: { adapter?: { id?: string; create?: <T extends Record<string, any>, R = T>(data: { model: string; data: Omit<T, "id">; select?: string[]; forceAllowId?: boolean; }) => Promise<R>; ... 9 more ...; options?: { ...; }; }; ... 19 more ...; publishTelemetry?: (event: TelemetryEvent) => Promis...'. Type '{ options: { databaseHooks: { user: { create: { before(user: { id: string; createdAt: Date; updatedAt: Date; email: string; emailVerified: boolean; name: string; image?: string; } & Record<string, unknown>): Promise<...>; }; }; session: { ...; }; }; }; }' is not assignable to type '{ context?: { adapter?: { id?: string; create?: <T extends Record<string, any>, R = T>(data: { model: string; data: Omit<T, "id">; select?: string[]; forceAllowId?: boolean; }) => Promise<R>; ... 9 more ...; options?: { ...; }; }; ... 19 more ...; publishTelemetry?: (event: TelemetryEvent) => Promise<...>; }; option...'. The types of 'options.databaseHooks.user.create.before' are incompatible between these types. Type '(user: { id: string; createdAt: Date; updatedAt: Date; email: string; emailVerified: boolean; name: string; image?: string; } & Record<string, unknown>) => Promise<{ data: { id: string; ... 6 more ...; role: string; }; }>' is not assignable to type '(user: { id: string; createdAt: Date; updatedAt: Date; emailVerified: boolean; name: string; email?: string; image?: string; } & Record<string, unknown>, context?: GenericEndpointContext) => Promise<...>'. Types of parameters 'user' and 'user' are incompatible. Type '{ id: string; createdAt: Date; updatedAt: Date; emailVerified: boolean; name: string; email?: string; image?: string; } & Record<string, unknown>' is not assignable to type '{ id: string; createdAt: Date; updatedAt: Date; email: string; emailVerified: boolean; name: string; image?: string; } & Record<string, unknown>'. Type '{ id: string; createdAt: Date; updatedAt: Date; emailVerified: boolean; name: string; email?: string; image?: string; } & Record<string, unknown>' is not assignable to type '{ id: string; createdAt: Date; updatedAt: Date; email: string; emailVerified: boolean; name: string; image?: string; }'. Property 'email' is optional in type '{ id: string; createdAt: Date; updatedAt: Date; emailVerified: boolean; name: string; email?: string; image?: string; } & Record<string, unknown>' but required in type '{ id: string; createdAt: Date; updatedAt: Date; email: string; emailVerified: boolean; name: string; image?: string; }'. ``` ### Current vs. Expected behavior No linting error ### What version of Better Auth are you using? 1.3.23 ### System info ```bash { "system": { "platform": "win32", "arch": "x64", "version": "Windows 11 Home", "release": "10.0.26100", "cpuCount": 16, "cpuModel": "AMD Ryzen 9 5900HS with Radeon Graphics ", "totalMemory": "15.41 GB", "freeMemory": "4.21 GB" }, "node": { "version": "v22.19.0", "env": "development" }, "packageManager": { "name": "npm", "version": "10.9.3" }, "frameworks": [ { "name": "next", "version": "15.5.4" }, { "name": "react", "version": "19.1.0" } ], "databases": [ { "name": "@prisma/client", "version": "^6.16.2" } ], "betterAuth": { "version": "^1.3.23", "config": { "account": { "accountLinking": { "enabled": true } }, "socialProviders": { "discord": { "clientId": "[REDACTED]", "clientSecret": "[REDACTED]" } }, "plugins": [ { "name": "anonymous", "config": { "id": "anonymous", "endpoints": {}, "hooks": { "after": [ {} ] }, "schema": { "user": { "fields": { "isAnonymous": { "type": "boolean", "required": false } } } }, "$ERROR_CODES": { "FAILED_TO_CREATE_USER": "Failed to create user", "COULD_NOT_CREATE_SESSION": "Could not create session", "ANONYMOUS_USERS_CANNOT_SIGN_IN_AGAIN_ANONYMOUSLY": "Anonymous users cannot sign in again anonymously" } } }, { "name": "admin", "config": { "id": "admin", "hooks": { "after": [ {} ] }, "endpoints": {}, "$ERROR_CODES": { "FAILED_TO_CREATE_USER": "Failed to create user", "USER_ALREADY_EXISTS": "User already exists.", "USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL": "User already exists. Use another email.", "YOU_CANNOT_BAN_YOURSELF": "You cannot ban yourself", "YOU_ARE_NOT_ALLOWED_TO_CHANGE_USERS_ROLE": "You are not allowed to change users role", "YOU_ARE_NOT_ALLOWED_TO_CREATE_USERS": "You are not allowed to create users", "YOU_ARE_NOT_ALLOWED_TO_LIST_USERS": "You are not allowed to list users", "YOU_ARE_NOT_ALLOWED_TO_LIST_USERS_SESSIONS": "You are not allowed to list users sessions", "YOU_ARE_NOT_ALLOWED_TO_BAN_USERS": "You are not allowed to ban users", "YOU_ARE_NOT_ALLOWED_TO_IMPERSONATE_USERS": "You are not allowed to impersonate users", "YOU_ARE_NOT_ALLOWED_TO_REVOKE_USERS_SESSIONS": "You are not allowed to revoke users sessions", "YOU_ARE_NOT_ALLOWED_TO_DELETE_USERS": "You are not allowed to delete users", "YOU_ARE_NOT_ALLOWED_TO_SET_USERS_PASSWORD": "[REDACTED]", "BANNED_USER": "You have been banned from this application", "YOU_ARE_NOT_ALLOWED_TO_GET_USER": "You are not allowed to get user", "NO_DATA_TO_UPDATE": "No data to update", "YOU_ARE_NOT_ALLOWED_TO_UPDATE_USERS": "You are not allowed to update users", "YOU_CANNOT_REMOVE_YOURSELF": "You cannot remove yourself" }, "schema": { "user": { "fields": { "role": { "type": "string", "required": false, "input": false }, "banned": { "type": "boolean", "defaultValue": false, "required": false, "input": false }, "banReason": { "type": "string", "required": false, "input": false }, "banExpires": { "type": "date", "required": false, "input": false } } }, "session": { "fields": { "impersonatedBy": { "type": "string", "required": false } } } }, "options": { "defaultRole": "user", "adminRoles": [ "admin" ] } } } ] } } } ``` ### Which area(s) are affected? (Select all that apply) Backend, Client, Types ### Auth config (if applicable) ```typescript // lib/auth.ts import { betterAuth } from "better-auth"; import { prismaAdapter } from "better-auth/adapters/prisma"; import { admin, anonymous } from "better-auth/plugins"; import { prisma } from "@/lib/prisma"; export const auth = betterAuth({ database: prismaAdapter(prisma, { provider: "postgresql", }), account: { accountLinking: { enabled: true, }, }, socialProviders: { discord: { clientId: process.env.DISCORD_CLIENT_ID, clientSecret: process.env.DISCORD_CLIENT_SECRET, }, }, plugins: [ anonymous(), admin({ defaultRole: "user", adminRoles: ["admin"], }), ], }); export type Session = typeof auth.$Infer.Session; // lib/auth-client.ts import { adminClient, anonymousClient } from "better-auth/client/plugins"; import { createAuthClient } from "better-auth/react"; export const authClient = createAuthClient({ baseURL: process.env.BETTER_AUTH_URL, plugins: [anonymousClient(), adminClient()], }); export const { signIn, signOut, signUp, useSession } = authClient; export type Session = typeof authClient.$Infer.Session; ``` ### Additional context _No response_
GiteaMirror added the plugin label 2026-03-13 09:24:19 -05:00
Author
Owner

@dosubot[bot] commented on GitHub (Oct 2, 2025):

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

The type error comes from a mismatch between the user object shape expected by the admin plugin's databaseHooks and the core BetterAuthPlugin types—specifically, whether the email property is required or optional in the user object passed to hooks like user.create.before. This is a known issue and has been reported by others using the admin plugin in similar setups "source".

A recent fix was merged that exports all type definitions from the admin plugin, which helps with type compatibility, but this landed after your version (v1.3.23) "source". Upgrading to the latest version of Better Auth should resolve the type error if the fix is included.

If you can't upgrade right now, you can work around the type error by using a type assertion (e.g., as any) in your hook implementations or by extending/casting the user type locally to satisfy TypeScript "source". Also, avoid force-casting the admin plugin to BetterAuthPlugin—let TypeScript infer the type naturally "source".

If you recently changed your schema or downgraded, clear node_modules and your lockfile, then reinstall to avoid mismatched or cached dependencies "source".

Finally, check your tsconfig.json: set declaration to false and be aware that enabling exactOptionalPropertyTypes can cause stricter type errors with plugins "source".

Let me know if you want a code snippet for a specific workaround!

To reply, just mention @dosu.


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

@dosubot[bot] commented on GitHub (Oct 2, 2025): <!-- Greeting --> Hi @Hezaerd! I'm [Dosu](https://go.dosu.dev/dosubot) and I’m helping the better-auth team. <!-- Answer --> The type error comes from a mismatch between the user object shape expected by the admin plugin's databaseHooks and the core BetterAuthPlugin types—specifically, whether the email property is required or optional in the user object passed to hooks like user.create.before. This is a known issue and has been reported by others using the admin plugin in similar setups ["source"](https://github.com/better-auth/better-auth/issues/3513). A recent fix was merged that exports all type definitions from the admin plugin, which helps with type compatibility, but this landed after your version (v1.3.23) ["source"](https://github.com/better-auth/better-auth/pull/3628). Upgrading to the latest version of Better Auth should resolve the type error if the fix is included. If you can't upgrade right now, you can work around the type error by using a type assertion (e.g., as any) in your hook implementations or by extending/casting the user type locally to satisfy TypeScript ["source"](https://github.com/better-auth/better-auth/issues/3033). Also, avoid force-casting the admin plugin to BetterAuthPlugin—let TypeScript infer the type naturally ["source"](https://github.com/better-auth/better-auth/issues/3015#issuecomment-2971195464). If you recently changed your schema or downgraded, clear node_modules and your lockfile, then reinstall to avoid mismatched or cached dependencies ["source"](https://github.com/better-auth/better-auth/issues/3513#issuecomment-3096148679). Finally, check your tsconfig.json: set declaration to false and be aware that enabling exactOptionalPropertyTypes can cause stricter type errors with plugins ["source"](https://github.com/better-auth/better-auth/issues/682). Let me know if you want a code snippet for a specific workaround! <!-- 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/07a78f8a-cf2c-4d85-b157-c7b5631e27cb?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/07a78f8a-cf2c-4d85-b157-c7b5631e27cb?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/07a78f8a-cf2c-4d85-b157-c7b5631e27cb?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/07a78f8a-cf2c-4d85-b157-c7b5631e27cb?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/07a78f8a-cf2c-4d85-b157-c7b5631e27cb?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/07a78f8a-cf2c-4d85-b157-c7b5631e27cb?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/07a78f8a-cf2c-4d85-b157-c7b5631e27cb?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/5047)
Author
Owner

@Hezaerd commented on GitHub (Oct 3, 2025):

@dosu
I've updated to better-auth 1.3.25, cleared node_modules/lockfile, updated tsconfig.json with declaration: false and exactOptionalPropertyTypes to false but I still have the issue

@Hezaerd commented on GitHub (Oct 3, 2025): @dosu I've updated to better-auth 1.3.25, cleared node_modules/lockfile, updated tsconfig.json with declaration: false and exactOptionalPropertyTypes to false but I still have the issue
Author
Owner

@dosubot[bot] commented on GitHub (Oct 3, 2025):

The type error is still happening because the admin plugin's hooks expect the user object to have a required email property, but in some contexts Better Auth treats email as optional. This mismatch hasn't been fully resolved in 1.3.25, even after the recent type export fixes and schema refactor "source".

To work around this, explicitly type your exported auth object using the satisfies keyword and ReturnType<typeof betterAuth>—this resolves most plugin type errors "source":

const authConfig = { /* ... */ } satisfies BetterAuthOptions;
export const auth = betterAuth(authConfig) as ReturnType<typeof betterAuth<typeof authConfig>>;

Also, make sure you aren't force-casting plugins (like admin() as unknown as BetterAuthPlugin), as that causes type errors "source".

If you still see the error, check for duplicate or mismatched better-auth packages in your node_modules (npm ls better-auth) and ensure all related packages are on the exact same version "source".

If none of these work, you can use a type assertion (e.g., as any) as a last resort for the problematic hook, or wait for a future release that fully aligns the user type shape across core and plugins. Let me know if you want a code snippet for a specific workaround!

To reply, just mention @dosu.


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

@dosubot[bot] commented on GitHub (Oct 3, 2025): <!-- Answer --> The type error is still happening because the admin plugin's hooks expect the user object to have a required `email` property, but in some contexts Better Auth treats `email` as optional. This mismatch hasn't been fully resolved in 1.3.25, even after the recent type export fixes and schema refactor ["source"](https://github.com/better-auth/better-auth/issues/3513). To work around this, explicitly type your exported `auth` object using the `satisfies` keyword and `ReturnType<typeof betterAuth>`—this resolves most plugin type errors ["source"](https://github.com/better-auth/better-auth/issues/3493): ```ts const authConfig = { /* ... */ } satisfies BetterAuthOptions; export const auth = betterAuth(authConfig) as ReturnType<typeof betterAuth<typeof authConfig>>; ``` Also, make sure you aren't force-casting plugins (like `admin() as unknown as BetterAuthPlugin`), as that causes type errors ["source"](https://github.com/better-auth/better-auth/issues/3015#issuecomment-2971195464). If you still see the error, check for duplicate or mismatched `better-auth` packages in your node_modules (`npm ls better-auth`) and ensure all related packages are on the exact same version ["source"](https://github.com/better-auth/better-auth/issues/4556). If none of these work, you can use a type assertion (e.g., `as any`) as a last resort for the problematic hook, or wait for a future release that fully aligns the user type shape across core and plugins. Let me know if you want a code snippet for a specific workaround! <!-- 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/c3ba1fcf-e9e6-4cf1-ba70-7c0bd686296e?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/c3ba1fcf-e9e6-4cf1-ba70-7c0bd686296e?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/c3ba1fcf-e9e6-4cf1-ba70-7c0bd686296e?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/c3ba1fcf-e9e6-4cf1-ba70-7c0bd686296e?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/c3ba1fcf-e9e6-4cf1-ba70-7c0bd686296e?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/c3ba1fcf-e9e6-4cf1-ba70-7c0bd686296e?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/c3ba1fcf-e9e6-4cf1-ba70-7c0bd686296e?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/5047)
Author
Owner

@rumeshudash commented on GitHub (Nov 13, 2025):

@Hezaerd I also came upon this issue, there seems to be some issue with z.transform on inference of userSchema (User), email field was set optional. But in admin plugin User object type was explicitly defined on build dts file with email field required.

My temporary solution was to set strict: false in my project tsconfig.json file, which bypass this check.

Waiting for #5962 to be verified and merged (Actual solution).

Image Image
@rumeshudash commented on GitHub (Nov 13, 2025): @Hezaerd I also came upon this issue, there seems to be some issue with `z.transform` on inference of `userSchema (User)`, `email` field was set optional. But in `admin` plugin `User` object type was explicitly defined on build dts file with `email` field `required`. ### My temporary solution was to set `strict: false` in my project `tsconfig.json` file, which bypass this check. Waiting for #5962 to be verified and merged _(Actual solution)_. <img width="688" height="335" alt="Image" src="https://github.com/user-attachments/assets/9aa232a9-6542-42ee-8c75-ee96c32ea49f" /> <img width="565" height="352" alt="Image" src="https://github.com/user-attachments/assets/3dcefde1-89df-4279-801c-90d7e6c238ed" />
Author
Owner

@Jussinevavuori commented on GitHub (Nov 24, 2025):

Still occurring in 1.4.1. Also happening for nextCookies().

@Jussinevavuori commented on GitHub (Nov 24, 2025): Still occurring in `1.4.1`. Also happening for `nextCookies()`.
Author
Owner

@parabrola commented on GitHub (Dec 28, 2025):

Still happening - any news on this?

@parabrola commented on GitHub (Dec 28, 2025): Still happening - any news on this?
Author
Owner

@bytaesu commented on GitHub (Jan 28, 2026):

I tested it myself, and this issue seems to be resolved in the current version.
Feel free to reopen or mention me if this issue persists!

@bytaesu commented on GitHub (Jan 28, 2026): I tested it myself, and this issue seems to be resolved in the current version. Feel free to reopen or mention me if this issue persists!
Author
Owner

@parabrola commented on GitHub (Feb 5, 2026):

@bytaesu still happening

@parabrola commented on GitHub (Feb 5, 2026): @bytaesu still happening
Author
Owner

@Prince-Mendiratta commented on GitHub (Feb 18, 2026):

@bytaesu ++ still happening on v1.4.5

@Prince-Mendiratta commented on GitHub (Feb 18, 2026): @bytaesu ++ still happening on v1.4.5
Author
Owner

@bytaesu commented on GitHub (Feb 18, 2026):

Does this occur in v1.4.18? If so, could you share minimal reproducible repo with me?

@bytaesu commented on GitHub (Feb 18, 2026): Does this occur in v1.4.18? If so, could you share minimal reproducible repo with me?
Author
Owner

@Prince-Mendiratta commented on GitHub (Feb 18, 2026):

@bytaesu yeah, happening on v1.4.18 and v1.5.0-beta.15

@Prince-Mendiratta commented on GitHub (Feb 18, 2026): @bytaesu yeah, happening on v1.4.18 and v1.5.0-beta.15
Author
Owner

@Prince-Mendiratta commented on GitHub (Feb 18, 2026):

This is my auth config:

import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { db } from "@/db";
import { admin, magicLink, twoFactor } from "better-auth/plugins";
import { GuestoResendAPI } from "@/guesto/resend";
import { ac, admin as adminRole, investor, founder } from "./permissions";

const resend = new GuestoResendAPI({
  apiKey: process.env.RESEND_API_KEY,
});

export const auth = betterAuth({
  database: drizzleAdapter(db, {
    provider: "pg",
  }),

  // Application name for TOTP issuer
  appName: "Purple Venture Studios",

  emailAndPassword: {
    enabled: true,
    sendResetPassword: async ({ user, url, token }, request) => {
      await resend.sendEmail({
        from: process.env.RESEND_FROM_EMAIL || "noreply@yourdomain.com",
        to: user.email,
        subject: "Reset your password",
        html: `
          <h1>Reset Your Password</h1>
          <p>Hi ${user.name},</p>
          <p>Click the link below to reset your password:</p>
          <a href="${url}" style="display: inline-block; padding: 12px 24px; background-color: #007bff; color: white; text-decoration: none; border-radius: 4px;">Reset Password</a>
          <p>This link will expire in 1 hour.</p>
          <p>If you didn't request this, you can safely ignore this email.</p>
        `,
        text: `Click this link to reset your password: ${url}`,
      });
    },
  },

  socialProviders: {
    google: {
      clientId: process.env.GOOGLE_CLIENT_ID as string,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
      prompt: "select_account", // Always show account selection
      accessType: "offline", // Get refresh token for API access
    },
  },

  plugins: [
    // Magic Link - Founders only (optional authentication method)
    magicLink({
      sendMagicLink: async ({ email, url, token }, request) => {
        await resend.sendEmail({
          from: process.env.RESEND_FROM_EMAIL || "noreply@yourdomain.com",
          to: email,
          subject: "Your magic link to sign in",
          html: `
            <h1>Sign In to Your Account</h1>
            <p>Click the link below to sign in:</p>
            <a href="${url}" style="display: inline-block; padding: 12px 24px; background-color: #007bff; color: white; text-decoration: none; border-radius: 4px;">Sign In</a>
            <p>This link will expire in 5 minutes.</p>
            <p>If you didn't request this, you can safely ignore this email.</p>
          `,
          text: `Click this link to sign in: ${url}`,
        });
      },
    }),

    // Two-Factor Authentication (TOTP)
    twoFactor({
      issuer: "Purple Venture Studio",
    }),

    admin(),
  ],

});

@Prince-Mendiratta commented on GitHub (Feb 18, 2026): This is my auth config: ``` import { betterAuth } from "better-auth"; import { drizzleAdapter } from "better-auth/adapters/drizzle"; import { db } from "@/db"; import { admin, magicLink, twoFactor } from "better-auth/plugins"; import { GuestoResendAPI } from "@/guesto/resend"; import { ac, admin as adminRole, investor, founder } from "./permissions"; const resend = new GuestoResendAPI({ apiKey: process.env.RESEND_API_KEY, }); export const auth = betterAuth({ database: drizzleAdapter(db, { provider: "pg", }), // Application name for TOTP issuer appName: "Purple Venture Studios", emailAndPassword: { enabled: true, sendResetPassword: async ({ user, url, token }, request) => { await resend.sendEmail({ from: process.env.RESEND_FROM_EMAIL || "noreply@yourdomain.com", to: user.email, subject: "Reset your password", html: ` <h1>Reset Your Password</h1> <p>Hi ${user.name},</p> <p>Click the link below to reset your password:</p> <a href="${url}" style="display: inline-block; padding: 12px 24px; background-color: #007bff; color: white; text-decoration: none; border-radius: 4px;">Reset Password</a> <p>This link will expire in 1 hour.</p> <p>If you didn't request this, you can safely ignore this email.</p> `, text: `Click this link to reset your password: ${url}`, }); }, }, socialProviders: { google: { clientId: process.env.GOOGLE_CLIENT_ID as string, clientSecret: process.env.GOOGLE_CLIENT_SECRET as string, prompt: "select_account", // Always show account selection accessType: "offline", // Get refresh token for API access }, }, plugins: [ // Magic Link - Founders only (optional authentication method) magicLink({ sendMagicLink: async ({ email, url, token }, request) => { await resend.sendEmail({ from: process.env.RESEND_FROM_EMAIL || "noreply@yourdomain.com", to: email, subject: "Your magic link to sign in", html: ` <h1>Sign In to Your Account</h1> <p>Click the link below to sign in:</p> <a href="${url}" style="display: inline-block; padding: 12px 24px; background-color: #007bff; color: white; text-decoration: none; border-radius: 4px;">Sign In</a> <p>This link will expire in 5 minutes.</p> <p>If you didn't request this, you can safely ignore this email.</p> `, text: `Click this link to sign in: ${url}`, }); }, }), // Two-Factor Authentication (TOTP) twoFactor({ issuer: "Purple Venture Studio", }), admin(), ], }); ```
Author
Owner

@Prince-Mendiratta commented on GitHub (Feb 18, 2026):

@bytaesu another interesting thing i found:

If I go to node_modules/better-auth/dist/plugins/admin/admin.d.mts and change the before hook’s email type to email?: string;, the error goes away.

If I don’t do this, I get multiple other errors, such as:

Property 'role' does not exist on type '{ id: string; createdAt: Date; updatedAt: Date; email: string; emailVerified: boolean; name: string; image?: string; }' when trying to access session.user.role.

Property 'userHasPermission' does not exist on type 'InferAPI<...>' when trying to call auth.api.userHasPermission(...).

@Prince-Mendiratta commented on GitHub (Feb 18, 2026): @bytaesu another interesting thing i found: If I go to node_modules/better-auth/dist/plugins/admin/admin.d.mts and change the before hook’s email type to email?: string;, the error goes away. If I don’t do this, I get multiple other errors, such as: Property 'role' does not exist on type '{ id: string; createdAt: Date; updatedAt: Date; email: string; emailVerified: boolean; name: string; image?: string; }' when trying to access session.user.role. ​ Property 'userHasPermission' does not exist on type 'InferAPI<...>' when trying to call auth.api.userHasPermission(...).
Author
Owner

@bytaesu commented on GitHub (Feb 18, 2026):

Hi @Prince-Mendiratta,

I'll try to reproduce this today. email need to be required by the way 🤔

@bytaesu commented on GitHub (Feb 18, 2026): Hi @Prince-Mendiratta, I'll try to reproduce this today. `email` need to be required by the way 🤔
Author
Owner

@Prince-Mendiratta commented on GitHub (Feb 18, 2026):

Thank you for taking this up, appreciate it! Please let me know if I can help in any manner.

email need to be required by the way

Yeah, agreed. Not sure why this comes up, the DB schema is untouched, generated by the CLI, which has the notNull() clause.

@Prince-Mendiratta commented on GitHub (Feb 18, 2026): Thank you for taking this up, appreciate it! Please let me know if I can help in any manner. > email need to be required by the way Yeah, agreed. Not sure why this comes up, the DB schema is untouched, generated by the CLI, which has the notNull() clause.
Author
Owner

@parabrola commented on GitHub (Feb 19, 2026):

maybe the issue should be reopened?

@parabrola commented on GitHub (Feb 19, 2026): maybe the issue should be reopened?
Author
Owner

@bytaesu commented on GitHub (Feb 19, 2026):

@parabrola @Prince-Mendiratta

As I checked earlier, there's no issue on the latest version.

I'd recommend deleting node_modules and trying again, or restarting the TS server. If the problem still persists, could you share a minimal reproducible repo with me? If I can reproduce it, I'll reopen the issue and fix it right away.

https://github.com/user-attachments/assets/740c664d-9b45-4659-badb-faca97ac34f3

https://github.com/user-attachments/assets/800dbff2-9a5c-482b-893a-449c4f416a19

@bytaesu commented on GitHub (Feb 19, 2026): @parabrola @Prince-Mendiratta As I checked earlier, there's no issue on the latest version. I'd recommend deleting `node_modules` and trying again, or restarting the TS server. If the problem still persists, could you share a minimal reproducible repo with me? If I can reproduce it, I'll reopen the issue and fix it right away. https://github.com/user-attachments/assets/740c664d-9b45-4659-badb-faca97ac34f3 https://github.com/user-attachments/assets/800dbff2-9a5c-482b-893a-449c4f416a19
Author
Owner

@parabrola commented on GitHub (Feb 27, 2026):

@bytaesu here you go: https://github.com/parabrola/reproduce-better-auth-type-error-for-admin

Make sure to delete this line to see the error:

https://github.com/parabrola/reproduce-better-auth-type-error-for-admin/blob/main/src/auth.ts#L108

@parabrola commented on GitHub (Feb 27, 2026): @bytaesu here you go: https://github.com/parabrola/reproduce-better-auth-type-error-for-admin Make sure to delete this line to see the error: https://github.com/parabrola/reproduce-better-auth-type-error-for-admin/blob/main/src/auth.ts#L108
Author
Owner

@bytaesu commented on GitHub (Feb 27, 2026):

Hi @parabrola,

Could you remove strictNullChecks option?

https://github.com/parabrola/reproduce-better-auth-type-error-for-admin/blob/main/tsconfig.json#L11

When strict is set to true in tsconfig, strictNullChecks is also enabled. If it’s false, type checking won’t work properly and can lead to issues.

https://www.typescriptlang.org/tsconfig/#strict

I recommend keeping strict enabled. You can refer to the documentation below for details. I’ll also improve the docs to clarify this further 🙂

https://www.better-auth.com/docs/concepts/typescript#typescript-config

@bytaesu commented on GitHub (Feb 27, 2026): Hi @parabrola, Could you remove `strictNullChecks` option? > https://github.com/parabrola/reproduce-better-auth-type-error-for-admin/blob/main/tsconfig.json#L11 When `strict` is set to true in tsconfig, `strictNullChecks` is also enabled. If it’s false, type checking won’t work properly and can lead to issues. > https://www.typescriptlang.org/tsconfig/#strict I recommend keeping `strict` enabled. You can refer to the documentation below for details. I’ll also improve the docs to clarify this further 🙂 > https://www.better-auth.com/docs/concepts/typescript#typescript-config
Author
Owner

@parabrola commented on GitHub (Mar 2, 2026):

@bytaesu That helped! Thank you

@parabrola commented on GitHub (Mar 2, 2026): @bytaesu That helped! Thank you
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#2067