[GH-ISSUE #5159] username() plugin fields not reflected in TypeScript session/user types #27500

Closed
opened 2026-04-17 18:33:15 -05:00 by GiteaMirror · 9 comments
Owner

Originally created by @rohityadav-sas on GitHub (Oct 8, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/5159

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

  1. Create a backend using better-auth with mongodbAdapter.
  2. Add the username() plugin in the plugins array.
  3. Export Session type via typeof auth.$Infer.Session.
  4. Hover over Session.user in TypeScript / VSCode.

Current vs. Expected behavior

The username and displayUsername fields added by the plugin do not appear in the TypeScript type.

Manual casts like as BetterAuthPlugin either remove inference or cause TS errors if removed.

Image

What version of Better Auth are you using?

1.3.27

System info

{
  "system": {
    "platform": "win32",
    "arch": "x64",
    "version": "Windows 11 Home Single Language",
    "release": "10.0.26100",
    "cpuCount": 12,
    "cpuModel": "12th Gen Intel(R) Core(TM) i7-1255U",
    "totalMemory": "15.69 GB",
    "freeMemory": "5.83 GB"
  },
  "node": {
    "version": "v24.9.0",
    "env": "development"
  },
  "packageManager": {
    "name": "npm",
    "version": "11.6.1"
  },
  "frameworks": [
    {
      "name": "express",
      "version": "^5.1.0"
    }
  ],
  "databases": [
    {
      "name": "mongodb",
      "version": "^6.20.0"
    }
  ],
  "betterAuth": {
    "version": "^1.3.27",
    "config": null
  }
}

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

Types

Auth config (if applicable)

import { betterAuth, type BetterAuthPlugin } from "better-auth";
import { MongoClient } from "mongodb";
import { mongodbAdapter } from "better-auth/adapters/mongodb";
import { username } from "better-auth/plugins";

const client = new MongoClient(process.env.MONGO_URI!);
const db = client.db();

// Minimal backend setup with username plugin
export const auth = betterAuth({
  database: mongodbAdapter(db, { client }),
  plugins: [
    // Keeping the cast removes type inference, removing it causes TS errors
    username() as BetterAuthPlugin
  ]
});

// Export the inferred session type
export type Session = typeof auth.$Infer.Session;

// Example usage
async function testSessionTypes() {
  const session: Session = {
    session: {
      id: "s1",
      userId: "u1",
      token: "token",
      createdAt: new Date(),
      updatedAt: new Date(),
      expiresAt: new Date()
    },
    user: {
      id: "u1",
      createdAt: new Date(),
      updatedAt: new Date(),
      email: "test@example.com",
      emailVerified: true,
      name: "Test User",
      image: null
      // username field does not appear here in TS hover, which is the bug
    }
  };

  console.log(session.user);
}

Additional context

  • Tried:
    1. Removing as BetterAuthPlugin (TypeScript compilation errors)
    2. Using satisfies BetterAuthPlugin (TS errors)
    3. Upgrading better-auth (latest version still shows same behavior)
  • The issue occurs locally, in TypeScript projects using strict mode.
  • The runtime works fine; this is purely a type inference / TS typings issue.
Originally created by @rohityadav-sas on GitHub (Oct 8, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/5159 ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce 1. Create a backend using better-auth with mongodbAdapter. 2. Add the username() plugin in the plugins array. 3. Export Session type via typeof auth.$Infer.Session. 4. Hover over Session.user in TypeScript / VSCode. ### Current vs. Expected behavior The **username** and **displayUsername** fields added by the plugin do not appear in the TypeScript type. Manual casts like as **BetterAuthPlugin** either remove inference or cause TS errors if removed. <img width="922" height="320" alt="Image" src="https://github.com/user-attachments/assets/6148e6a7-ff8c-4698-bdbc-b3f4e321abb3" /> ### What version of Better Auth are you using? 1.3.27 ### System info ```bash { "system": { "platform": "win32", "arch": "x64", "version": "Windows 11 Home Single Language", "release": "10.0.26100", "cpuCount": 12, "cpuModel": "12th Gen Intel(R) Core(TM) i7-1255U", "totalMemory": "15.69 GB", "freeMemory": "5.83 GB" }, "node": { "version": "v24.9.0", "env": "development" }, "packageManager": { "name": "npm", "version": "11.6.1" }, "frameworks": [ { "name": "express", "version": "^5.1.0" } ], "databases": [ { "name": "mongodb", "version": "^6.20.0" } ], "betterAuth": { "version": "^1.3.27", "config": null } } ``` ### Which area(s) are affected? (Select all that apply) Types ### Auth config (if applicable) ```typescript import { betterAuth, type BetterAuthPlugin } from "better-auth"; import { MongoClient } from "mongodb"; import { mongodbAdapter } from "better-auth/adapters/mongodb"; import { username } from "better-auth/plugins"; const client = new MongoClient(process.env.MONGO_URI!); const db = client.db(); // Minimal backend setup with username plugin export const auth = betterAuth({ database: mongodbAdapter(db, { client }), plugins: [ // Keeping the cast removes type inference, removing it causes TS errors username() as BetterAuthPlugin ] }); // Export the inferred session type export type Session = typeof auth.$Infer.Session; // Example usage async function testSessionTypes() { const session: Session = { session: { id: "s1", userId: "u1", token: "token", createdAt: new Date(), updatedAt: new Date(), expiresAt: new Date() }, user: { id: "u1", createdAt: new Date(), updatedAt: new Date(), email: "test@example.com", emailVerified: true, name: "Test User", image: null // username field does not appear here in TS hover, which is the bug } }; console.log(session.user); } ``` ### Additional context - Tried: 1. Removing as BetterAuthPlugin (TypeScript compilation errors) 2. Using satisfies BetterAuthPlugin (TS errors) 3. Upgrading better-auth (latest version still shows same behavior) - The issue occurs locally, in TypeScript projects using strict mode. - The runtime works fine; this is purely a type inference / TS typings issue.
GiteaMirror added the lockedbug labels 2026-04-17 18:33:15 -05:00
Author
Owner

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

This is a known limitation in better-auth: plugin-added fields like username and displayUsername from the username() plugin are not automatically reflected in the inferred TypeScript types for Session.user, because the plugin system extends the user object at runtime but does not augment the static types used by $Infer. Manual casts (as BetterAuthPlugin) suppress type inference, and removing them causes TS errors due to type incompatibility in strict mode. This gap is not well documented and has caused confusion for others as well (source).

A common workaround is to explicitly declare these fields in your user schema using additionalFields on the server, and then use the inferAdditionalFields plugin on the client to fix type inference for hooks like useSession. For example:

Server:

export const auth = betterAuth({
  // ...other config
  user: {
    additionalFields: {
      username: { type: "string", required: true },
      displayUsername: { type: "string", required: false },
    },
  },
  plugins: [username()],
});

Client:

import { createAuthClient, inferAdditionalFields } from "better-auth/client";
import type { auth } from "@/lib/auth";

export const authClient = createAuthClient({
  plugins: [inferAdditionalFields<typeof auth>()],
});

This approach is documented in the Better Auth docs and confirmed by maintainers (source, source).

Note: The returned property in additionalFields only affects TypeScript types and Zod schemas. For the fields to actually appear in the runtime session/user object, they must be present in your database and returned by your adapter (source).

Recent PRs have improved plugin-aware type inference, but if you are on v1.3.27 and still see this issue after upgrading, the workaround above is the most reliable way to ensure type safety for plugin-added fields.

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:3380886072 --> @dosubot[bot] commented on GitHub (Oct 8, 2025): <!-- Answer --> This is a known limitation in better-auth: plugin-added fields like username and displayUsername from the username() plugin are not automatically reflected in the inferred TypeScript types for Session.user, because the plugin system extends the user object at runtime but does not augment the static types used by $Infer. Manual casts (as BetterAuthPlugin) suppress type inference, and removing them causes TS errors due to type incompatibility in strict mode. This gap is not well documented and has caused confusion for others as well ([source](https://github.com/better-auth/better-auth/issues/3888#issuecomment-3168790764)). A common workaround is to explicitly declare these fields in your user schema using additionalFields on the server, and then use the inferAdditionalFields plugin on the client to fix type inference for hooks like useSession. For example: **Server:** ```ts export const auth = betterAuth({ // ...other config user: { additionalFields: { username: { type: "string", required: true }, displayUsername: { type: "string", required: false }, }, }, plugins: [username()], }); ``` **Client:** ```ts import { createAuthClient, inferAdditionalFields } from "better-auth/client"; import type { auth } from "@/lib/auth"; export const authClient = createAuthClient({ plugins: [inferAdditionalFields<typeof auth>()], }); ``` This approach is documented in the [Better Auth docs](https://www.better-auth.com/docs/concepts/typescript#additional-fields) and confirmed by maintainers ([source](https://github.com/better-auth/better-auth/issues/1245#issuecomment-2681313846), [source](https://github.com/better-auth/better-auth/issues/3328)). Note: The returned property in additionalFields only affects TypeScript types and Zod schemas. For the fields to actually appear in the runtime session/user object, they must be present in your database and returned by your adapter ([source](https://github.com/better-auth/better-auth/issues/3888#issuecomment-3168790764)). Recent PRs have improved plugin-aware type inference, but if you are on v1.3.27 and still see this issue after upgrading, the workaround above is the most reliable way to ensure type safety for plugin-added fields. <!-- 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/df8647fb-9167-4f26-9b31-d364ce7036f4?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/df8647fb-9167-4f26-9b31-d364ce7036f4?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/df8647fb-9167-4f26-9b31-d364ce7036f4?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/df8647fb-9167-4f26-9b31-d364ce7036f4?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/df8647fb-9167-4f26-9b31-d364ce7036f4?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/df8647fb-9167-4f26-9b31-d364ce7036f4?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/df8647fb-9167-4f26-9b31-d364ce7036f4?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/5159)
Author
Owner

@ping-maxwell commented on GitHub (Oct 8, 2025):

Hey, can you provide your tsconfig?

<!-- gh-comment-id:3381480149 --> @ping-maxwell commented on GitHub (Oct 8, 2025): Hey, can you provide your tsconfig?
Author
Owner

@rohityadav-sas commented on GitHub (Oct 8, 2025):

Hey, can you provide your tsconfig?

{
  "compilerOptions": {
    "rootDir": "./src",
    "outDir": "./dist",
    "module": "nodenext",
    "target": "esnext",
    "lib": [
      "esnext"
    ],
    "types": [
      "node"
    ],
    "noUncheckedIndexedAccess": true,
    "exactOptionalPropertyTypes": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "strict": true,
    "jsx": "react-jsx",
    "verbatimModuleSyntax": true,
    "isolatedModules": true,
    "noUncheckedSideEffectImports": true,
    "moduleDetection": "force",
    "skipLibCheck": true,
  }
}
<!-- gh-comment-id:3381637072 --> @rohityadav-sas commented on GitHub (Oct 8, 2025): > Hey, can you provide your tsconfig? ``` { "compilerOptions": { "rootDir": "./src", "outDir": "./dist", "module": "nodenext", "target": "esnext", "lib": [ "esnext" ], "types": [ "node" ], "noUncheckedIndexedAccess": true, "exactOptionalPropertyTypes": true, "noUnusedLocals": true, "noUnusedParameters": true, "strict": true, "jsx": "react-jsx", "verbatimModuleSyntax": true, "isolatedModules": true, "noUncheckedSideEffectImports": true, "moduleDetection": "force", "skipLibCheck": true, } } ```
Author
Owner

@rohityadav-sas commented on GitHub (Oct 8, 2025):

This is a known limitation in better-auth: plugin-added fields like username and displayUsername from the username() plugin are not automatically reflected in the inferred TypeScript types for Session.user, because the plugin system extends the user object at runtime but does not augment the static types used by $Infer. Manual casts (as BetterAuthPlugin) suppress type inference, and removing them causes TS errors due to type incompatibility in strict mode. This gap is not well documented and has caused confusion for others as well (source).

A common workaround is to explicitly declare these fields in your user schema using additionalFields on the server, and then use the inferAdditionalFields plugin on the client to fix type inference for hooks like useSession. For example:

Server:

export const auth = betterAuth({
// ...other config
user: {
additionalFields: {
username: { type: "string", required: true },
displayUsername: { type: "string", required: false },
},
},
plugins: [username()],
});
Client:

import { createAuthClient, inferAdditionalFields } from "better-auth/client";
import type { auth } from "@/lib/auth";

export const authClient = createAuthClient({
plugins: [inferAdditionalFields()],
});
This approach is documented in the Better Auth docs and confirmed by maintainers (source, source).

Note: The returned property in additionalFields only affects TypeScript types and Zod schemas. For the fields to actually appear in the runtime session/user object, they must be present in your database and returned by your adapter (source).

Recent PRs have improved plugin-aware type inference, but if you are on v1.3.27 and still see this issue after upgrading, the workaround above is the most reliable way to ensure type safety for plugin-added fields.

To reply, just mention @dosu.

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

Explicitly declaring these fields in the user schema is causing unexpected errors, and the data is not being stored in the database. However, everything works fine if the fields are not declared in additionalFields.

The main issue is that the required fields are not being inferred in the Session type when using:

export type Session = typeof auth.$Infer.Session

Interestingly, the phoneNumber plugin works as expected, the required fields like phoneNumber and phoneNumberVerified are correctly inferred in the Session type.
However, this is not the case with the username plugin, where the required fields fail to be inferred.

Image
<!-- gh-comment-id:3381667900 --> @rohityadav-sas commented on GitHub (Oct 8, 2025): > This is a known limitation in better-auth: plugin-added fields like username and displayUsername from the username() plugin are not automatically reflected in the inferred TypeScript types for Session.user, because the plugin system extends the user object at runtime but does not augment the static types used by $Infer. Manual casts (as BetterAuthPlugin) suppress type inference, and removing them causes TS errors due to type incompatibility in strict mode. This gap is not well documented and has caused confusion for others as well ([source](https://github.com/better-auth/better-auth/issues/3888#issuecomment-3168790764)). > > A common workaround is to explicitly declare these fields in your user schema using additionalFields on the server, and then use the inferAdditionalFields plugin on the client to fix type inference for hooks like useSession. For example: > > **Server:** > > export const auth = betterAuth({ > // ...other config > user: { > additionalFields: { > username: { type: "string", required: true }, > displayUsername: { type: "string", required: false }, > }, > }, > plugins: [username()], > }); > **Client:** > > import { createAuthClient, inferAdditionalFields } from "better-auth/client"; > import type { auth } from "@/lib/auth"; > > export const authClient = createAuthClient({ > plugins: [inferAdditionalFields<typeof auth>()], > }); > This approach is documented in the [Better Auth docs](https://www.better-auth.com/docs/concepts/typescript#additional-fields) and confirmed by maintainers ([source](https://github.com/better-auth/better-auth/issues/1245#issuecomment-2681313846), [source](https://github.com/better-auth/better-auth/issues/3328)). > > Note: The returned property in additionalFields only affects TypeScript types and Zod schemas. For the fields to actually appear in the runtime session/user object, they must be present in your database and returned by your adapter ([source](https://github.com/better-auth/better-auth/issues/3888#issuecomment-3168790764)). > > Recent PRs have improved plugin-aware type inference, but if you are on v1.3.27 and still see this issue after upgrading, the workaround above is the most reliable way to ensure type safety for plugin-added fields. > > _To reply, just mention [@dosu](https://go.dosu.dev/dosubot)._ > > How did I do? [Good](https://app.dosu.dev/response-feedback/df8647fb-9167-4f26-9b31-d364ce7036f4?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/df8647fb-9167-4f26-9b31-d364ce7036f4?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/df8647fb-9167-4f26-9b31-d364ce7036f4?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/df8647fb-9167-4f26-9b31-d364ce7036f4?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/df8647fb-9167-4f26-9b31-d364ce7036f4?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/df8647fb-9167-4f26-9b31-d364ce7036f4?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/df8647fb-9167-4f26-9b31-d364ce7036f4?feedback_type=other)  [![Chat with Dosu](https://camo.githubusercontent.com/d9ffebb979729c9ef0fc4e3090dbae5b17cabbfec8afe378ada6b81913d6ad8a/68747470733a2f2f646f73752e6465762f646f73752d636861742d62616467652e737667)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github) [![Join Discord](https://camo.githubusercontent.com/d562c2df5e4567ea41d37f1d91a4da0839d8341037045b103b2da268998de17f/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6a6f696e2d3538363546323f6c6f676f3d646973636f7264266c6f676f436f6c6f723d7768697465266c6162656c3d)](https://go.dosu.dev/discord-bot) [![Share on X](https://camo.githubusercontent.com/4649f705e2396d6f328dbc8e800a0509c9c17b3b20253b36706c393df124b93d/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f582d73686172652d626c61636b)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/better-auth/better-auth/issues/5159) Explicitly declaring these fields in the user schema is causing unexpected errors, and the data is not being stored in the database. However, everything works fine if the fields are **not** declared in `additionalFields`. The main issue is that the required fields are **not being inferred** in the `Session` type when using: ```export type Session = typeof auth.$Infer.Session``` Interestingly, the **phoneNumber** plugin works as expected, the required fields like `phoneNumber` and `phoneNumberVerified` are correctly inferred in the `Session` type. However, this is **not the case** with the **username** plugin, where the required fields fail to be inferred. <img width="809" height="390" alt="Image" src="https://github.com/user-attachments/assets/73137b1c-27fd-488a-8416-fb3652cdff55" />
Author
Owner

@ping-maxwell commented on GitHub (Oct 8, 2025):

We're working on a fix to prevent issues like these out of the box, but as of now certain tsconfig flags will cause type issues with Better-Auth, one of those is exactOptionalPropertyTypes needing to be disabled.

<!-- gh-comment-id:3381724323 --> @ping-maxwell commented on GitHub (Oct 8, 2025): We're working on a fix to prevent issues like these out of the box, but as of now certain tsconfig flags will cause type issues with Better-Auth, one of those is `exactOptionalPropertyTypes` needing to be disabled.
Author
Owner

@ping-maxwell commented on GitHub (Oct 8, 2025):

The main issue is that the required fields are not being inferred in the Session type when using:

export type Session = typeof auth.$Infer.Session

This is a separate but active issue as well.

Interestingly, the phoneNumber plugin works as expected, the required fields like phoneNumber and phoneNumberVerified are correctly inferred in the Session type.

I'm pretty sure plugin fields can be inferred, just not fields defined in the auth config.

<!-- gh-comment-id:3381729607 --> @ping-maxwell commented on GitHub (Oct 8, 2025): > The main issue is that the required fields are not being inferred in the Session type when using: > > export type Session = typeof auth.$Infer.Session This is a separate but active issue as well. > Interestingly, the phoneNumber plugin works as expected, the required fields like phoneNumber and phoneNumberVerified are correctly inferred in the Session type. I'm pretty sure plugin fields can be inferred, just not fields defined in the auth config.
Author
Owner

@ping-maxwell commented on GitHub (Oct 8, 2025):

However, this is not the case with the username plugin, where the required fields fail to be inferred.

It's able to infer for me. 🤔

Image
<!-- gh-comment-id:3381736882 --> @ping-maxwell commented on GitHub (Oct 8, 2025): > However, this is not the case with the username plugin, where the required fields fail to be inferred. It's able to infer for me. 🤔 <img width="479" height="309" alt="Image" src="https://github.com/user-attachments/assets/34a3785d-ce43-46ac-8a67-d66acf79edba" />
Author
Owner

@ping-maxwell commented on GitHub (Oct 8, 2025):

Please update your tsconfig and let me know if there are still ts issues

<!-- gh-comment-id:3381738040 --> @ping-maxwell commented on GitHub (Oct 8, 2025): Please update your tsconfig and let me know if there are still ts issues
Author
Owner

@rohityadav-sas commented on GitHub (Oct 8, 2025):

one of those is exactOptionalPropertyTypes needing to be disabled.

This solution worked. Thank you so much!

Image
<!-- gh-comment-id:3381758825 --> @rohityadav-sas commented on GitHub (Oct 8, 2025): > one of those is `exactOptionalPropertyTypes` needing to be disabled. This solution worked. Thank you so much! <img width="740" height="390" alt="Image" src="https://github.com/user-attachments/assets/c1702c6b-b4c4-4a70-9d6c-f1dc9c3616c9" />
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#27500