[GH-ISSUE #2818] Type inference issue with customSession plugin - server-side types missing custom data #9361

Closed
opened 2026-04-13 04:47:56 -05:00 by GiteaMirror · 7 comments
Owner

Originally created by @jamesindeed on GitHub (May 28, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/2818

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

  1. Set up Better Auth with customSession plugin that adds custom data to the user object
  2. Configure client with customSessionClient and inferAdditionalFields plugins
  3. Try to infer server-side types using typeof auth.$Infer.Session.user
  4. Custom properties added in customSession are missing from the inferred type
  5. Client-side useSession() correctly includes the custom properties

Server setup:

const options = { /* auth config */ } satisfies BetterAuthOptions;

export const auth = betterAuth({
  ...options,
  plugins: [
    customSession(async ({ user, session }) => {
      const userWithCompany = await prisma.user.findUnique({
        where: { id: user.id },
        include: { company: true },
      })
      return {
        user: { ...user, company: userWithCompany?.company || null },
        session,
      }
    }, options),
  ],
})

Client setup:

plugins: [customSessionClient<typeof auth>(), inferAdditionalFields<typeof auth>()]

Attempting to infer types in server auth.ts:

export type User = typeof auth.$Infer.Session.user
// The 'company' property is missing from this type

Current vs. Expected behavior

Expected behavior:
Server-side type inference should include custom properties added in the customSession plugin:

export type User = typeof auth.$Infer.Session.user
// Should include the 'company' property: User.company should be available

Current behavior:

  • Client-side useSession() correctly returns full type including custom company property
  • Server-side typeof auth.$Infer.Session.user does not include the company property added in customSession. This only includes the user.additionalFields.

This forces the use of workarounds like in the auth-client.ts file:

type UseSessionReturn = ReturnType<typeof useSession>
type SessionData = NonNullable<UseSessionReturn['data']>
export type User = SessionData['user']

The custom session data should be available in server-side type inference, not just after calling useSession() on the client.

What version of Better Auth are you using?

1.2.8

Provide environment information

- OS: macOS 15.3
- Browser: Arc
- Node.js version: 23.7.0
- TypeScript version: 5
- Framework: Next.js 15.2.3 (Pages Router)

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

Types

Auth config (if applicable)

const options = { /* auth config */ } satisfies BetterAuthOptions;

export const auth = betterAuth({
  ...options,
  plugins: [
    customSession(async ({ user, session }) => {
      const userWithCompany = await prisma.user.findUnique({
        where: { id: user.id },
        include: { company: true },
      })
      return {
        user: { ...user, company: userWithCompany?.company || null },
        session,
      }
    }, options),
  ],
})

Additional context

No response

Originally created by @jamesindeed on GitHub (May 28, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/2818 ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce 1. Set up Better Auth with `customSession` plugin that adds custom data to the user object 2. Configure client with `customSessionClient` and `inferAdditionalFields` plugins 3. Try to infer server-side types using `typeof auth.$Infer.Session.user` 4. Custom properties added in `customSession` are missing from the inferred type 5. Client-side `useSession()` correctly includes the custom properties **Server setup:** ```typescript const options = { /* auth config */ } satisfies BetterAuthOptions; export const auth = betterAuth({ ...options, plugins: [ customSession(async ({ user, session }) => { const userWithCompany = await prisma.user.findUnique({ where: { id: user.id }, include: { company: true }, }) return { user: { ...user, company: userWithCompany?.company || null }, session, } }, options), ], }) ``` **Client setup:** ```typescript plugins: [customSessionClient<typeof auth>(), inferAdditionalFields<typeof auth>()] ``` **Attempting to infer types in server auth.ts:** ```typescript export type User = typeof auth.$Infer.Session.user // The 'company' property is missing from this type ``` ### Current vs. Expected behavior **Expected behavior:** Server-side type inference should include custom properties added in the `customSession` plugin: ```typescript export type User = typeof auth.$Infer.Session.user // Should include the 'company' property: User.company should be available ``` **Current behavior:** - ✅ Client-side `useSession()` correctly returns full type including custom `company` property - ❌ Server-side `typeof auth.$Infer.Session.user` does not include the `company` property added in `customSession`. This only includes the user.additionalFields. This forces the use of workarounds like in the auth-client.ts file: ```typescript type UseSessionReturn = ReturnType<typeof useSession> type SessionData = NonNullable<UseSessionReturn['data']> export type User = SessionData['user'] ``` The custom session data should be available in server-side type inference, not just after calling `useSession()` on the client. ### What version of Better Auth are you using? 1.2.8 ### Provide environment information ```bash - OS: macOS 15.3 - Browser: Arc - Node.js version: 23.7.0 - TypeScript version: 5 - Framework: Next.js 15.2.3 (Pages Router) ``` ### Which area(s) are affected? (Select all that apply) Types ### Auth config (if applicable) ```typescript const options = { /* auth config */ } satisfies BetterAuthOptions; export const auth = betterAuth({ ...options, plugins: [ customSession(async ({ user, session }) => { const userWithCompany = await prisma.user.findUnique({ where: { id: user.id }, include: { company: true }, }) return { user: { ...user, company: userWithCompany?.company || null }, session, } }, options), ], }) ``` ### Additional context _No response_
GiteaMirror added the locked label 2026-04-13 04:47:56 -05:00
Author
Owner

@RDeluxe commented on GitHub (Jun 11, 2025):

As a workaround on the server side :

// export type BetterAuthSession = ReturnType<typeof createAuth>['$Infer']['Session']

// My workaround to get the session type
export type BetterAuthSession = Awaited<ReturnType<ReturnType<typeof createAuth>['api']['getSession']>>

createAuth being my function to create the auth object, but you should be able to use you auth object.

<!-- gh-comment-id:2962304273 --> @RDeluxe commented on GitHub (Jun 11, 2025): As a workaround on the server side : ``` // export type BetterAuthSession = ReturnType<typeof createAuth>['$Infer']['Session'] // My workaround to get the session type export type BetterAuthSession = Awaited<ReturnType<ReturnType<typeof createAuth>['api']['getSession']>> ``` `createAuth` being my function to create the auth object, but you should be able to use you `auth` object.
Author
Owner

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

Hi, @jamesindeed. I'm Dosu, and I'm helping the better-auth team manage their backlog and am marking this issue as stale.

Issue Summary:

  • You reported that server-side type inference with the customSession plugin did not include custom user properties like "company."
  • Client-side useSession() types worked correctly in version 1.2.8.
  • You expected server-side types (typeof auth.$Infer.Session.user) to reflect custom fields without extra workarounds.
  • Maintainer RDeluxe provided a workaround using Awaited and ReturnType on the getSession method to infer the session type on the server side.

Next Steps:

  • Please let me know if this workaround is still relevant or if the issue persists with the latest version of better-auth by commenting here.
  • Otherwise, this issue will be automatically closed in 7 days.

Thank you for your understanding and contribution!

<!-- gh-comment-id:3275593194 --> @dosubot[bot] commented on GitHub (Sep 10, 2025): Hi, @jamesindeed. I'm [Dosu](https://dosu.dev), and I'm helping the better-auth team manage their backlog and am marking this issue as stale. **Issue Summary:** - You reported that server-side type inference with the customSession plugin did not include custom user properties like "company." - Client-side useSession() types worked correctly in version 1.2.8. - You expected server-side types (typeof auth.$Infer.Session.user) to reflect custom fields without extra workarounds. - Maintainer RDeluxe provided a workaround using Awaited and ReturnType on the getSession method to infer the session type on the server side. **Next Steps:** - Please let me know if this workaround is still relevant or if the issue persists with the latest version of better-auth by commenting here. - Otherwise, this issue will be automatically closed in 7 days. Thank you for your understanding and contribution!
Author
Owner

@RDeluxe commented on GitHub (Sep 11, 2025):

Hello!

The workaround is not fully functionnal, as adding more plugins breaks TS type inference.

We added the "super admin" plugin, and TS is complaining that the type inference is too large.

(I'm not a maintainer)

<!-- gh-comment-id:3279084071 --> @RDeluxe commented on GitHub (Sep 11, 2025): Hello! The workaround is not fully functionnal, as adding more plugins breaks TS type inference. We added the "super admin" plugin, and TS is complaining that the type inference is too large. (I'm not a maintainer)
Author
Owner

@j-fdion commented on GitHub (Sep 23, 2025):

As a workaround on the server side :

// export type BetterAuthSession = ReturnType<typeof createAuth>['$Infer']['Session']

// My workaround to get the session type
export type BetterAuthSession = Awaited<ReturnType<ReturnType<typeof createAuth>['api']['getSession']>>

createAuth being my function to create the auth object, but you should be able to use you auth object.

This worked for me. But I'd also like to see an official way and an example on how to set it up properly.

<!-- gh-comment-id:3325255724 --> @j-fdion commented on GitHub (Sep 23, 2025): > As a workaround on the server side : > > ``` > // export type BetterAuthSession = ReturnType<typeof createAuth>['$Infer']['Session'] > > // My workaround to get the session type > export type BetterAuthSession = Awaited<ReturnType<ReturnType<typeof createAuth>['api']['getSession']>> > ``` > > `createAuth` being my function to create the auth object, but you should be able to use you `auth` object. This worked for me. But I'd also like to see an official way and an example on how to set it up properly.
Author
Owner

@jayarjo commented on GitHub (Sep 27, 2025):

this is such an inherent functionality, lacking proper solution damages credibility of better-auth, IMO.

<!-- gh-comment-id:3341320268 --> @jayarjo commented on GitHub (Sep 27, 2025): this is such an inherent functionality, lacking proper solution damages credibility of better-auth, IMO.
Author
Owner

@j-fdion commented on GitHub (Oct 2, 2025):

As a workaround on the server side :

// export type BetterAuthSession = ReturnType<typeof createAuth>['$Infer']['Session']

// My workaround to get the session type
export type BetterAuthSession = Awaited<ReturnType<ReturnType<typeof createAuth>['api']['getSession']>>

createAuth being my function to create the auth object, but you should be able to use you auth object.

This worked for me. But I'd also like to see an official way and an example on how to set it up properly.

#3408
https://github.com/better-auth/better-auth/issues/3408#issuecomment-3362114564

Full solution over here, the previous one wasn't fully working...

<!-- gh-comment-id:3362433557 --> @j-fdion commented on GitHub (Oct 2, 2025): > > As a workaround on the server side : > > ``` > > // export type BetterAuthSession = ReturnType<typeof createAuth>['$Infer']['Session'] > > > > // My workaround to get the session type > > export type BetterAuthSession = Awaited<ReturnType<ReturnType<typeof createAuth>['api']['getSession']>> > > ``` > > > > > > > > > > > > > > > > > > > > > > > > `createAuth` being my function to create the auth object, but you should be able to use you `auth` object. > > This worked for me. But I'd also like to see an official way and an example on how to set it up properly. #3408 [https://github.com/better-auth/better-auth/issues/3408#issuecomment-3362114564](https://github.com/better-auth/better-auth/issues/3408#issuecomment-3362114564) Full solution over here, the previous one wasn't fully working...
Author
Owner

@j-fdion commented on GitHub (Oct 3, 2025):

@Bekacru Thank you!!

<!-- gh-comment-id:3365812519 --> @j-fdion commented on GitHub (Oct 3, 2025): @Bekacru 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#9361