[GH-ISSUE #3328] Lack of types for useSession hook if user model is extended #9571

Closed
opened 2026-04-13 05:05:40 -05:00 by GiteaMirror · 4 comments
Owner

Originally created by @korczivo on GitHub (Jul 10, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/3328

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

Hi, in order to docs i see

if you add additional fields to a user or session table, the types will be inferred automatically on getSession

but it's in the plugin section, so probably I'm doing it wrong, any ideas for this TS errors:

Property 'paymentStatus' does not exist on type '{ id: string; name: string; emailVerified: boolean; email: string; createdAt: Date; updatedAt: Date; image?: string | null | undefined; }'.ts(2339)

layout.tsx

const { data: session, isPending } = useSession();

 if(session?.user?.paymentStatus !== "PAID") {
    return <PricingSection />
  }

api.middleware.ts

if (session.user.paymentStatus !== 'PAID') {
    return NextResponse.json(
      { error: 'Payment required' },
      { status: 402 }
    );
}
(property) user: {
    id: string;
    name: string;
    emailVerified: boolean;
    email: string;
    createdAt: Date;
    updatedAt: Date;
    image?: string | null | undefined | undefined;
    paymentStatus: string | null | undefined;
    expiresAt?: Date | ... 1 more ... | undefined;
    ... 4 more ...;
    paidAt?: Date | ... 1 more ... | undefined;
}

Current vs. Expected behavior

I can't get the correct types for the session on the client side.

What version of Better Auth are you using?

^1.2.12

Provide environment information

- Browser

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

Client

Auth config (if applicable)

import { betterAuth } from "better-auth";
import { prismaAdapter } from "better-auth/adapters/prisma";
import { env } from "@/env";
import { magicLink } from "better-auth/plugins";
import { Resend } from "resend";
import { prisma } from "@/lib/prisma";

const resend = new Resend(env.AUTH_RESEND_KEY);

export const auth = betterAuth({
  database: prismaAdapter(prisma, {
    provider: "mongodb",
  }),
  user: {
    additionalFields: {
      stripeCustomerId: {
        type: "string",
        required: false,
        input: false,
      },
      stripeSubscriptionId: {
        type: "string",
        required: false,
        input: false,
      },
      paymentStatus: {
        type: "string",
        required: false,
        defaultValue: "UNPAID",
        input: false,
      },
      tierId: {
        type: "string",
        required: false,
        input: false,
      },
      tierName: {
        type: "string",
        required: false,
        input: false,
      },
      paidAt: {
        type: "date",
        required: false,
        input: false,
      },
      expiresAt: {
        type: "date",
        required: false,
        input: false,
      },
    },
  },
  socialProviders: { 
    github: { 
       clientId: env.GITHUB_CLIENT_ID as string, 
       clientSecret: env.GITHUB_CLIENT_SECRET as string, 
    }, 
  }, 
  plugins: [
    magicLink({
        sendMagicLink: async ({ email, token, url }, request) => {
         
        }
    })
]
});
Originally created by @korczivo on GitHub (Jul 10, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/3328 ### Is this suited for github? - [ ] Yes, this is suited for github ### To Reproduce Hi, in order to docs i see > if you add additional fields to a user or session table, the types will be inferred automatically on getSession but it's in the plugin section, so probably I'm doing it wrong, any ideas for this TS errors: ``` Property 'paymentStatus' does not exist on type '{ id: string; name: string; emailVerified: boolean; email: string; createdAt: Date; updatedAt: Date; image?: string | null | undefined; }'.ts(2339) ``` layout.tsx ``` const { data: session, isPending } = useSession(); if(session?.user?.paymentStatus !== "PAID") { return <PricingSection /> } ``` api.middleware.ts ``` if (session.user.paymentStatus !== 'PAID') { return NextResponse.json( { error: 'Payment required' }, { status: 402 } ); } ``` ``` (property) user: { id: string; name: string; emailVerified: boolean; email: string; createdAt: Date; updatedAt: Date; image?: string | null | undefined | undefined; paymentStatus: string | null | undefined; expiresAt?: Date | ... 1 more ... | undefined; ... 4 more ...; paidAt?: Date | ... 1 more ... | undefined; } ``` ### Current vs. Expected behavior I can't get the correct types for the session on the client side. ### What version of Better Auth are you using? ^1.2.12 ### Provide environment information ```bash - Browser ``` ### Which area(s) are affected? (Select all that apply) Client ### Auth config (if applicable) ```typescript import { betterAuth } from "better-auth"; import { prismaAdapter } from "better-auth/adapters/prisma"; import { env } from "@/env"; import { magicLink } from "better-auth/plugins"; import { Resend } from "resend"; import { prisma } from "@/lib/prisma"; const resend = new Resend(env.AUTH_RESEND_KEY); export const auth = betterAuth({ database: prismaAdapter(prisma, { provider: "mongodb", }), user: { additionalFields: { stripeCustomerId: { type: "string", required: false, input: false, }, stripeSubscriptionId: { type: "string", required: false, input: false, }, paymentStatus: { type: "string", required: false, defaultValue: "UNPAID", input: false, }, tierId: { type: "string", required: false, input: false, }, tierName: { type: "string", required: false, input: false, }, paidAt: { type: "date", required: false, input: false, }, expiresAt: { type: "date", required: false, input: false, }, }, }, socialProviders: { github: { clientId: env.GITHUB_CLIENT_ID as string, clientSecret: env.GITHUB_CLIENT_SECRET as string, }, }, plugins: [ magicLink({ sendMagicLink: async ({ email, token, url }, request) => { } }) ] }); ```
GiteaMirror added the locked label 2026-04-13 05:05:40 -05:00
Author
Owner

@dosubot[bot] commented on GitHub (Jul 10, 2025):

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

This is a known limitation with Better Auth: TypeScript doesn't always infer extra user fields (like paymentStatus) on the client side with useSession, even if you've added them via additionalFields. This happens because the type inference for session/user objects isn't always propagated to client hooks automatically, especially when plugins or custom fields are involved [1] [2].

To fix this, you can use the inferAdditionalFields plugin in your client auth configuration. Here’s how:

  1. On the server, get the type of your auth config:

    // auth.ts
    export const auth = betterAuth({ ... });
    export type Auth = ReturnType<typeof auth>;
    
  2. On the client, set up your auth client with the plugin:

    import { createAuthClient, inferAdditionalFields } from "better-auth/client";
    import type { Auth } from "../path/to/auth";
    
    export const authClient = createAuthClient({
      plugins: [inferAdditionalFields<Auth>()],
    });
    

This should make useSession return the correct types, including your custom fields like paymentStatus [3].

If you’re still seeing issues, double-check plugin order and consider using the satisfies operator or explicit type annotations as a workaround [4] [5]. For more details and examples, see the session management docs.

Let me know if you need a more specific code example!

To reply, just mention @dosu.


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

<!-- gh-comment-id:3055628173 --> @dosubot[bot] commented on GitHub (Jul 10, 2025): <!-- Greeting --> Hi @korczivo! I'm [Dosu](https://go.dosu.dev/dosubot) and I’m helping the better-auth team. <!-- Answer --> This is a known limitation with Better Auth: TypeScript doesn't always infer extra user fields (like paymentStatus) on the client side with useSession, even if you've added them via additionalFields. This happens because the type inference for session/user objects isn't always propagated to client hooks automatically, especially when plugins or custom fields are involved [[1]](https://github.com/better-auth/better-auth/issues/2789) [[2]](https://github.com/better-auth/better-auth/issues/3233). To fix this, you can use the inferAdditionalFields plugin in your client auth configuration. Here’s how: 1. On the server, get the type of your auth config: ```ts // auth.ts export const auth = betterAuth({ ... }); export type Auth = ReturnType<typeof auth>; ``` 2. On the client, set up your auth client with the plugin: ```ts import { createAuthClient, inferAdditionalFields } from "better-auth/client"; import type { Auth } from "../path/to/auth"; export const authClient = createAuthClient({ plugins: [inferAdditionalFields<Auth>()], }); ``` This should make useSession return the correct types, including your custom fields like paymentStatus [[3]](https://github.com/better-auth/better-auth/issues/2944). If you’re still seeing issues, double-check plugin order and consider using the satisfies operator or explicit type annotations as a workaround [[4]](https://github.com/better-auth/better-auth/issues/2789) [[5]](https://github.com/better-auth/better-auth/issues/3233). For more details and examples, see the [session management docs](https://github.com/better-auth/better-auth/blob/12e15cce41fceb36ddefcfc39fc66e082b8af699/docs/content/docs/concepts/session-management.mdx). Let me know if you need a more specific code example! <!-- 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/ec6b10d9-2266-45ce-8e4d-f7b1834571f9?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/ec6b10d9-2266-45ce-8e4d-f7b1834571f9?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/ec6b10d9-2266-45ce-8e4d-f7b1834571f9?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/ec6b10d9-2266-45ce-8e4d-f7b1834571f9?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/ec6b10d9-2266-45ce-8e4d-f7b1834571f9?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/ec6b10d9-2266-45ce-8e4d-f7b1834571f9?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/ec6b10d9-2266-45ce-8e4d-f7b1834571f9?feedback_type=other)</sup>&nbsp;&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/3328)
Author
Owner

@korczivo commented on GitHub (Jul 10, 2025):

@dosu unfortunately, it doesn't work

import { createAuthClient } from "better-auth/react"
import { customSessionClient } from "better-auth/client/plugins"
import { magicLinkClient } from "better-auth/client/plugins";
import type { auth } from "@/lib/auth";

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

export const { signIn, signUp, useSession, signOut } = authClient

layout.tsx -> lack of paymentStatus in user model

  if(session?.user?.paymentStatus !== "PAID") {
    return <PricingSection />
  }

auth.ts

import { betterAuth } from "better-auth";
import { prismaAdapter } from "better-auth/adapters/prisma";
import { env } from "@/env";
import { magicLink } from "better-auth/plugins";
import { Resend } from "resend";
import { prisma } from "@/lib/prisma";

const resend = new Resend(env.AUTH_RESEND_KEY);

export const auth = betterAuth({
  database: prismaAdapter(prisma, {
    provider: "mongodb",
  }),
  user: {
    additionalFields: {
      stripeCustomerId: {
        type: "string",
        required: false,
        input: false,
      },
      stripeSubscriptionId: {
        type: "string",
        required: false,
        input: false,
      },
      paymentStatus: {
        type: "string",
        required: false,
        defaultValue: "UNPAID",
        input: false,
      },
      tierId: {
        type: "string",
        required: false,
        input: false,
      },
      tierName: {
        type: "string",
        required: false,
        input: false,
      },
      paidAt: {
        type: "date",
        required: false,
        input: false,
      },
      expiresAt: {
        type: "date",
        required: false,
        input: false,
      },
    },
  },
  socialProviders: { 
    github: { 
       clientId: env.GITHUB_CLIENT_ID as string, 
       clientSecret: env.GITHUB_CLIENT_SECRET as string, 
    }, 
  }, 
  plugins: [
    magicLink({
        sendMagicLink: async ({ email, token, url }, request) => {
            
           
    })
]
});
<!-- gh-comment-id:3055651847 --> @korczivo commented on GitHub (Jul 10, 2025): @dosu unfortunately, it doesn't work ```ts import { createAuthClient } from "better-auth/react" import { customSessionClient } from "better-auth/client/plugins" import { magicLinkClient } from "better-auth/client/plugins"; import type { auth } from "@/lib/auth"; export const authClient = createAuthClient({ plugins: [ magicLinkClient(), customSessionClient<typeof auth>() ] }) export const { signIn, signUp, useSession, signOut } = authClient ``` layout.tsx -> lack of paymentStatus in user model ``` if(session?.user?.paymentStatus !== "PAID") { return <PricingSection /> } ``` auth.ts ``` import { betterAuth } from "better-auth"; import { prismaAdapter } from "better-auth/adapters/prisma"; import { env } from "@/env"; import { magicLink } from "better-auth/plugins"; import { Resend } from "resend"; import { prisma } from "@/lib/prisma"; const resend = new Resend(env.AUTH_RESEND_KEY); export const auth = betterAuth({ database: prismaAdapter(prisma, { provider: "mongodb", }), user: { additionalFields: { stripeCustomerId: { type: "string", required: false, input: false, }, stripeSubscriptionId: { type: "string", required: false, input: false, }, paymentStatus: { type: "string", required: false, defaultValue: "UNPAID", input: false, }, tierId: { type: "string", required: false, input: false, }, tierName: { type: "string", required: false, input: false, }, paidAt: { type: "date", required: false, input: false, }, expiresAt: { type: "date", required: false, input: false, }, }, }, socialProviders: { github: { clientId: env.GITHUB_CLIENT_ID as string, clientSecret: env.GITHUB_CLIENT_SECRET as string, }, }, plugins: [ magicLink({ sendMagicLink: async ({ email, token, url }, request) => { }) ] }); ```
Author
Owner

@dosubot[bot] commented on GitHub (Jul 10, 2025):

This is a known type inference issue in Better Auth when using custom fields and plugins like customSessionClient and magicLinkClient. The main causes are plugin order and how types are propagated from your server config to the client.

Here’s what you should try:

  1. Plugin Order: Place customSessionClient before magicLinkClient in your client plugins array. The order matters—if customSessionClient comes after, TypeScript may not infer the extra fields correctly [source].

    export const authClient = createAuthClient({
      plugins: [
        customSessionClient<typeof auth>(),
        magicLinkClient(),
      ]
    });
    
  2. Type Import: Make sure you are importing the type of your server-side auth instance (typeof auth) and passing it to customSessionClient. If you can't import the type directly, type inference will not work [source].

  3. Alternative: inferAdditionalFields: If the above doesn't work, try using the inferAdditionalFields plugin on the client instead of customSessionClient. Example:

    import { createAuthClient, inferAdditionalFields } from "better-auth/client";
    import type { auth } from "@/lib/auth";
    
    export const authClient = createAuthClient({
      plugins: [inferAdditionalFields<typeof auth>()],
    });
    
  4. Workaround: If you still can't get the types, you can manually cast the session type in your component as a last resort.

If you have a complex plugin setup or need to ensure all plugin-added fields are included, define your Better Auth options separately (using satisfies BetterAuthOptions), then pass those options to your plugins and to customSession [source].

Let me know if you want a minimal working example or if you still see issues after trying these steps.

To reply, just mention @dosu.


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

<!-- gh-comment-id:3055677453 --> @dosubot[bot] commented on GitHub (Jul 10, 2025): <!-- Answer --> This is a known type inference issue in Better Auth when using custom fields and plugins like customSessionClient and magicLinkClient. The main causes are plugin order and how types are propagated from your server config to the client. Here’s what you should try: 1. **Plugin Order**: Place customSessionClient before magicLinkClient in your client plugins array. The order matters—if customSessionClient comes after, TypeScript may not infer the extra fields correctly [[source](https://github.com/better-auth/better-auth/issues/3233)]. ```ts export const authClient = createAuthClient({ plugins: [ customSessionClient<typeof auth>(), magicLinkClient(), ] }); ``` 2. **Type Import**: Make sure you are importing the type of your server-side auth instance (`typeof auth`) and passing it to customSessionClient. If you can't import the type directly, type inference will not work [[source](https://github.com/better-auth/better-auth/blob/b766b22865895f5cdd7dc575b0cc2e023e795740/docs/content/docs/concepts/session-management.mdx)]. 3. **Alternative: inferAdditionalFields**: If the above doesn't work, try using the `inferAdditionalFields` plugin on the client instead of customSessionClient. Example: ```ts import { createAuthClient, inferAdditionalFields } from "better-auth/client"; import type { auth } from "@/lib/auth"; export const authClient = createAuthClient({ plugins: [inferAdditionalFields<typeof auth>()], }); ``` 4. **Workaround**: If you still can't get the types, you can manually cast the session type in your component as a last resort. If you have a complex plugin setup or need to ensure all plugin-added fields are included, define your Better Auth options separately (using `satisfies BetterAuthOptions`), then pass those options to your plugins and to customSession [[source](https://github.com/better-auth/better-auth/issues/3233)]. Let me know if you want a minimal working example or if you still see issues after trying these steps. <!-- 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/d33178b0-70e4-4f46-b312-fe4838eca2fc?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/d33178b0-70e4-4f46-b312-fe4838eca2fc?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/d33178b0-70e4-4f46-b312-fe4838eca2fc?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/d33178b0-70e4-4f46-b312-fe4838eca2fc?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/d33178b0-70e4-4f46-b312-fe4838eca2fc?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/d33178b0-70e4-4f46-b312-fe4838eca2fc?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/d33178b0-70e4-4f46-b312-fe4838eca2fc?feedback_type=other)</sup>&nbsp;&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/3328)
Author
Owner

@korczivo commented on GitHub (Jul 10, 2025):

Point 3 helped.

<!-- gh-comment-id:3055708458 --> @korczivo commented on GitHub (Jul 10, 2025): Point 3 helped.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#9571