TypeScript Type Inference Issue with adminClient Plugin #2493

Closed
opened 2026-03-13 09:59:03 -05:00 by GiteaMirror · 13 comments
Owner

Originally created by @wellington-vell on GitHub (Dec 9, 2025).

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

Follow https://www.better-auth.com/docs/plugins/admin#custom-permissions

Current vs. Expected behavior

Expected Behavior

TypeScript should successfully infer the types without requiring an explicit type assertion. The configuration object should match the expected parameter type of adminClient.

Actual Behavior

TypeScript reports a type error:

error TS2322: Type '{ newRole<K extends "user" | "session" | "todo">(statements: Subset<K, { readonly todo: readonly ["delete"]; readonly user: readonly ["create", "list", "set-role", "ban", "impersonate", "delete", "set-password", "get", "update"]; readonly session: readonly [...]; }>): { ...; }; statements: { ...; }; }' is not assignable to type 'AccessControl'.
  Types of property 'newRole' are incompatible.
    Type '<K extends "user" | "session" | "todo">(statements: Subset<K, { readonly todo: readonly ["delete"]; readonly user: readonly ["create", "list", "set-role", "ban", "impersonate", "delete", "set-password", "get", "update"]; readonly session: readonly [...]; }>) => { ...; }' is not assignable to type '<K extends string | number>(statements: Subset<K, Statements>) => { authorize<K_1 extends K>(request: K_1 extends infer T extends K ? { [key in T]?: Subset<K, Statements>[key] | { ...; } | undefined; } : never, connector?: "OR" | ... 1 more ... | undefined): AuthorizeResponse; statements: Subset<...>; }'.
      Types of parameters 'statements' and 'statements' are incompatible.
        Type 'Subset<K, Statements>' is missing the following properties from type 'Subset<"todo" | "session" | "user", { readonly todo: readonly ["delete"]; readonly user: readonly ["create", "list", "set-role", "ban", "impersonate", "delete", "set-password", "get", "update"]; readonly session: readonly [...]; }>': todo, session, user

What version of Better Auth are you using?

same behavior for 1.4.5 and 1.4.6

System info

{
  "system": {
    "platform": "win32",
    "arch": "x64",
    "version": "Windows 11 Pro",
    "release": "10.0.26200",
    "cpuCount": 12,
    "cpuModel": "AMD Ryzen 5 5600G with Radeon Graphics         ",
    "totalMemory": "31.30 GB",
    "freeMemory": "11.38 GB"
  },
  "node": {
    "version": "v22.20.0",
    "env": "development"
  },
  "packageManager": {
    "name": "npm",
    "version": "11.6.0"
  },
  "frameworks": null,
  "databases": null,
  "betterAuth": {
    "version": "Unknown",
    "config": null
  }
}

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

Client, Types

Auth config (if applicable)

export const authClient = createAuthClient({
  baseURL: env.VITE_SERVER_URL,
  plugins: [
    inferAdditionalFields<typeof auth>(),
    adminClient({
      ac,
      roles: {
        admin,
        user,
      },
    }),
    twoFactorClient({
      onTwoFactorRedirect: () => {
        window.location.href = "/2fa";
      },
    }),
  ],
});

export const auth = betterAuth({
  database: drizzleAdapter(db, {
    provider: "pg",
    schema,
  }),
  secret: env.BETTER_AUTH_SECRET,
  trustedOrigins: [env.CORS_ORIGIN],
  emailAndPassword: {
    enabled: true,
    requireEmailVerification: env.MAIL_ENABLED,
    minPasswordLength: 8,
    maxPasswordLength: 128,
  },
  advanced: {
    useSecureCookies: env.NODE_ENV === "production",
    defaultCookieAttributes: {
      // Use "lax" in development to allow OAuth state cookies across different ports
      // Use "strict" in production for better CSRF protection
      // See: https://better-auth.com/docs/errors/state_mismatch
      sameSite: env.NODE_ENV === "development" ? "lax" : "strict",
      secure: env.NODE_ENV === "production",
      httpOnly: true,
    },
  },
  appName: pkg.name,
  plugins: [
    adminPlugin({
      ac,
      roles: {
        admin,
        user,
      },
    }),
    twoFactorPlugin(),
  ],
});
export const statement = {
  ...defaultStatements,
  todo: ["delete"],
} as const;

export const ac = createAccessControl(statement);

export const user = ac.newRole({
  ...userAc.statements,
});

export const admin = ac.newRole({
  ...adminAc.statements,
  todo: ["delete"],
});

Additional context

The error occurs only when running the type check using tsc --noEmit; the IDE does not report it.

Workaround

adminClient({
  ac,
  roles: {
    admin,
    user,
  },
} as Parameters<typeof adminClient>[0])
Originally created by @wellington-vell on GitHub (Dec 9, 2025). ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce Follow https://www.better-auth.com/docs/plugins/admin#custom-permissions ### Current vs. Expected behavior ## Expected Behavior TypeScript should successfully infer the types without requiring an explicit type assertion. The configuration object should match the expected parameter type of `adminClient`. ## Actual Behavior TypeScript reports a type error: ``` error TS2322: Type '{ newRole<K extends "user" | "session" | "todo">(statements: Subset<K, { readonly todo: readonly ["delete"]; readonly user: readonly ["create", "list", "set-role", "ban", "impersonate", "delete", "set-password", "get", "update"]; readonly session: readonly [...]; }>): { ...; }; statements: { ...; }; }' is not assignable to type 'AccessControl'. Types of property 'newRole' are incompatible. Type '<K extends "user" | "session" | "todo">(statements: Subset<K, { readonly todo: readonly ["delete"]; readonly user: readonly ["create", "list", "set-role", "ban", "impersonate", "delete", "set-password", "get", "update"]; readonly session: readonly [...]; }>) => { ...; }' is not assignable to type '<K extends string | number>(statements: Subset<K, Statements>) => { authorize<K_1 extends K>(request: K_1 extends infer T extends K ? { [key in T]?: Subset<K, Statements>[key] | { ...; } | undefined; } : never, connector?: "OR" | ... 1 more ... | undefined): AuthorizeResponse; statements: Subset<...>; }'. Types of parameters 'statements' and 'statements' are incompatible. Type 'Subset<K, Statements>' is missing the following properties from type 'Subset<"todo" | "session" | "user", { readonly todo: readonly ["delete"]; readonly user: readonly ["create", "list", "set-role", "ban", "impersonate", "delete", "set-password", "get", "update"]; readonly session: readonly [...]; }>': todo, session, user ``` ### What version of Better Auth are you using? same behavior for 1.4.5 and 1.4.6 ### System info ```bash { "system": { "platform": "win32", "arch": "x64", "version": "Windows 11 Pro", "release": "10.0.26200", "cpuCount": 12, "cpuModel": "AMD Ryzen 5 5600G with Radeon Graphics ", "totalMemory": "31.30 GB", "freeMemory": "11.38 GB" }, "node": { "version": "v22.20.0", "env": "development" }, "packageManager": { "name": "npm", "version": "11.6.0" }, "frameworks": null, "databases": null, "betterAuth": { "version": "Unknown", "config": null } } ``` ### Which area(s) are affected? (Select all that apply) Client, Types ### Auth config (if applicable) ```typescript export const authClient = createAuthClient({ baseURL: env.VITE_SERVER_URL, plugins: [ inferAdditionalFields<typeof auth>(), adminClient({ ac, roles: { admin, user, }, }), twoFactorClient({ onTwoFactorRedirect: () => { window.location.href = "/2fa"; }, }), ], }); ``` ```typescript export const auth = betterAuth({ database: drizzleAdapter(db, { provider: "pg", schema, }), secret: env.BETTER_AUTH_SECRET, trustedOrigins: [env.CORS_ORIGIN], emailAndPassword: { enabled: true, requireEmailVerification: env.MAIL_ENABLED, minPasswordLength: 8, maxPasswordLength: 128, }, advanced: { useSecureCookies: env.NODE_ENV === "production", defaultCookieAttributes: { // Use "lax" in development to allow OAuth state cookies across different ports // Use "strict" in production for better CSRF protection // See: https://better-auth.com/docs/errors/state_mismatch sameSite: env.NODE_ENV === "development" ? "lax" : "strict", secure: env.NODE_ENV === "production", httpOnly: true, }, }, appName: pkg.name, plugins: [ adminPlugin({ ac, roles: { admin, user, }, }), twoFactorPlugin(), ], }); ``` ```typescript export const statement = { ...defaultStatements, todo: ["delete"], } as const; export const ac = createAccessControl(statement); export const user = ac.newRole({ ...userAc.statements, }); export const admin = ac.newRole({ ...adminAc.statements, todo: ["delete"], }); ``` ### Additional context The error occurs only when running the type check using `tsc --noEmit`; the IDE does not report it. ## Workaround ```typescript adminClient({ ac, roles: { admin, user, }, } as Parameters<typeof adminClient>[0]) ```
GiteaMirror added the plugin label 2026-03-13 09:59:03 -05:00
Author
Owner

@better-auth-agent[bot] commented on GitHub (Dec 9, 2025):

It looks like the issue is caused by a mismatch between the expected type signature for the newRole function in the access control configuration and what’s actually provided. One recommended workaround is to use an explicit type assertion when passing the configuration to the adminClient so that TypeScript can correctly infer the types [1].

Have you already tried applying the type assertion workaround as shown in the issue description? Tag @better-auth-agent if you would like an answer from the Agent.

If you need more help, tag @better-auth-agent in a comment so I can respond.

DiagramDiscordGitHub

Diagram Join Star

@better-auth-agent[bot] commented on GitHub (Dec 9, 2025): It looks like the issue is caused by a mismatch between the expected type signature for the newRole function in the access control configuration and what’s actually provided. One recommended workaround is to use an explicit type assertion when passing the configuration to the adminClient so that TypeScript can correctly infer the types [[1]](https://github.com/better-auth/better-auth/issues/6446). Have you already tried applying the type assertion workaround as shown in the issue description? Tag @better-auth-agent if you would like an answer from the Agent. _If you need more help, tag @better-auth-agent in a comment so I can respond._ <!-- bot:webhook reply v1 --> [Diagram](https://repodiagrams.s3.eu-north-1.amazonaws.com/skyvern_ultra_detailed_interactive.html) • [Discord](https://discord.gg/fG2XXEuQX3) • [GitHub](https://github.com/Skyvern-AI/Skyvern) [![Diagram](https://img.shields.io/badge/Diagram-2b3137?style=flat-square)](https://repodiagrams.s3.eu-north-1.amazonaws.com/skyvern_ultra_detailed_interactive.html) [![Join](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&style=flat-square)](https://discord.gg/fG2XXEuQX3) [![Star](https://img.shields.io/badge/star-181717?logo=github&logoColor=white&style=flat-square)](https://github.com/Skyvern-AI/Skyvern)
Author
Owner

@itzmealvin commented on GitHub (Dec 10, 2025):

I can confirm that I am having the same issue with OP, however mine is about setRole functions.

Current vs. Expected behavior

Expected Behavior

TypeScript should successfully infer the types without requiring an explicit type assertion. The configuration object should match the expected parameter type of adminClient.

Actual Behavior

TypeScript reports a type error:

const { error } = await authClient.admin.setRole({
  userId: user.id,
  role: "customRole",
});
error TS2322: Type '"customRole"' is not assignable to type '"user" | "admin" | ("user" | "admin")[]'.

103         role: selectedRole,
            ~~~~

What version of Better Auth are you using?

1.4.6 (latest version)

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

admin plugin

Auth config (if applicable)

auth-client.ts

export const authClient = createAuthClient({
  plugins: [
    adminClient({
      ac,
      roles: {
        admin,
        user,
        customRole,
      },
    }),
  ],
});

auth.ts

export const auth = betterAuth({
  // omitted
  plugins: [
    adminPlugin({
      ac,
      roles: { admin, user, customRole },
    }),
  ],
});

permission.ts

const actions = ["readSystem"];

const statement = {
  ...defaultStatements,
  project: actions,
} as const;

export const ac = createAccessControl(statement);

export const admin = ac.newRole({
  ...adminAc.statements,
  project: actions,
});
export const user = ac.newRole({ ...userAc.statements });
export const customRole = ac.newRole({
  project: ["readSystem"],
});

Additional context

  • The error occurs only when running the type check using tsc --noEmit or when running next build (which invoke tsc -b).

  • This error is not presented when using better-auth v1.3.34

  • Adding this workaround making the issue to appear in many more functions, beside setRole like checkRolePermission, etc...

adminClient({
  ac,
  roles: {
    admin,
    user,
  },
} as Parameters<typeof adminClient>[0]);
@itzmealvin commented on GitHub (Dec 10, 2025): I can confirm that I am having the same issue with OP, however mine is about `setRole` functions. ### Current vs. Expected behavior ## Expected Behavior TypeScript should successfully infer the types without requiring an explicit type assertion. The configuration object should match the expected parameter type of `adminClient`. ## Actual Behavior TypeScript reports a type error: ```typescript const { error } = await authClient.admin.setRole({ userId: user.id, role: "customRole", }); ``` ```sh error TS2322: Type '"customRole"' is not assignable to type '"user" | "admin" | ("user" | "admin")[]'. 103 role: selectedRole, ~~~~ ``` ### What version of Better Auth are you using? 1.4.6 (latest version) ### Which area(s) are affected? (Select all that apply) `admin` plugin ### Auth config (if applicable) auth-client.ts ```typescript export const authClient = createAuthClient({ plugins: [ adminClient({ ac, roles: { admin, user, customRole, }, }), ], }); ``` auth.ts ```typescript export const auth = betterAuth({ // omitted plugins: [ adminPlugin({ ac, roles: { admin, user, customRole }, }), ], }); ``` permission.ts ```typescript const actions = ["readSystem"]; const statement = { ...defaultStatements, project: actions, } as const; export const ac = createAccessControl(statement); export const admin = ac.newRole({ ...adminAc.statements, project: actions, }); export const user = ac.newRole({ ...userAc.statements }); export const customRole = ac.newRole({ project: ["readSystem"], }); ``` ### Additional context - The error occurs only when running the type check using `tsc --noEmit` or when running `next build` (which invoke `tsc -b`). - This error is not presented when using better-auth v1.3.34 - Adding this workaround making the issue to appear in many more functions, beside `setRole` like `checkRolePermission`, etc... ```typescript adminClient({ ac, roles: { admin, user, }, } as Parameters<typeof adminClient>[0]); ```
Author
Owner

@devWithKD commented on GitHub (Dec 13, 2025):

I am facing the same issue. type of role is not infered correctly.

Solved by downgrading to 1.4.5

@devWithKD commented on GitHub (Dec 13, 2025): I am facing the same issue. type of role is not infered correctly. Solved by downgrading to 1.4.5
Author
Owner

@AndyClausen commented on GitHub (Dec 19, 2025):

For me, it was auth.api.createUser that started throwing type errors because I had custom roles. Also worked in 1.4.5.

@AndyClausen commented on GitHub (Dec 19, 2025): For me, it was `auth.api.createUser` that started throwing type errors because I had custom roles. Also worked in 1.4.5.
Author
Owner

@Chust3r commented on GitHub (Dec 19, 2025):

I was using version 1.4.7, but since type inference is not working and it’s a known issue, I had to downgrade to version 1.4.5 of better-auth to resolve the problem.

@Chust3r commented on GitHub (Dec 19, 2025): I was using version 1.4.7, but since type inference is not working and it’s a known issue, I had to downgrade to version 1.4.5 of better-auth to resolve the problem.
Author
Owner

@ozgurozalp commented on GitHub (Dec 27, 2025):

I’m really tired of these TypeScript errors from better-auth. I wish they would export the interfaces instead of relying on auto inference, so we could use it while suppressing the errors.

@ozgurozalp commented on GitHub (Dec 27, 2025): I’m really tired of these TypeScript errors from better-auth. I wish they would export the interfaces instead of relying on auto inference, so we could use it while suppressing the errors.
Author
Owner

@bytaesu commented on GitHub (Dec 27, 2025):

Hello everyone! This PR might have fixed the issue.

@bytaesu commented on GitHub (Dec 27, 2025): Hello everyone! This PR might have fixed the issue. - https://github.com/better-auth/better-auth/pull/6997
Author
Owner

@bytaesu commented on GitHub (Dec 27, 2025):

I’m really tired of these TypeScript errors from better-auth. I wish they would export the interfaces instead of relying on auto inference, so we could use it while suppressing the errors.

@ozgurozalp, Sorry for the inconvenience. we'll try to make it more solid 💪

@bytaesu commented on GitHub (Dec 27, 2025): > I’m really tired of these TypeScript errors from better-auth. I wish they would export the interfaces instead of relying on auto inference, so we could use it while suppressing the errors. @ozgurozalp, Sorry for the inconvenience. we'll try to make it more solid 💪
Author
Owner

@wellington-vell commented on GitHub (Dec 27, 2025):

Yes, I closed it because I saw it. If the problem isn't resolved in my end, I'll open it again. Thanks 👍

@wellington-vell commented on GitHub (Dec 27, 2025): Yes, I closed it because I saw it. If the problem isn't resolved in my end, I'll open it again. Thanks 👍
Author
Owner

@wellington-vell commented on GitHub (Dec 28, 2025):

hey @bytaesu i still have the type error, i am basically using this setup Better-T-Stack, if necessary i can create a repo for reproduction

@wellington-vell commented on GitHub (Dec 28, 2025): hey @bytaesu i still have the type error, i am basically using this setup [Better-T-Stack](https://www.better-t-stack.dev/new?be=fastify&rt=node&api=orpc&db=postgres&pm=pnpm&ex=todo&git=false&i=false), if necessary i can create a repo for reproduction
Author
Owner

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

hey @bytaesu i still have the type error, i am basically using this setup Better-T-Stack, if necessary i can create a repo for reproduction

The new version with the changes hasn't been released yet 🙏

@bytaesu commented on GitHub (Dec 28, 2025): > hey @bytaesu i still have the type error, i am basically using this setup [Better-T-Stack](https://www.better-t-stack.dev/new?be=fastify&rt=node&api=orpc&db=postgres&pm=pnpm&ex=todo&git=false&i=false), if necessary i can create a repo for reproduction The new version with the changes hasn't been released yet 🙏
Author
Owner

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

Hi @wellington-vell, You can use these versions temporarily until the next release 🙏

https://github.com/better-auth/better-auth/pull/6997#issuecomment-3691603678

@bytaesu commented on GitHub (Dec 28, 2025): Hi @wellington-vell, You can use these versions temporarily until the next release 🙏 https://github.com/better-auth/better-auth/pull/6997#issuecomment-3691603678
Author
Owner

@itsrekhab commented on GitHub (Mar 11, 2026):

it's version 1.5.0 now, and I've now ran into the same type inference issue, but with the organizationClient instead. the client plugin is expecting a broader string type from statements while the access controller exports a literal union. I'm using the same workaround as in the original post for now, but is there a more permanent solution?

@itsrekhab commented on GitHub (Mar 11, 2026): it's version 1.5.0 now, and I've now ran into the same type inference issue, but with the `organizationClient` instead. the client plugin is expecting a broader string type from statements while the access controller exports a literal union. I'm using the same workaround as in the original post for now, but is there a more permanent solution?
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#2493