[GH-ISSUE #3408] The inferred type of this node exceeds the maximum length the compiler will serialize when using user.additionalFields with organization plugin #26916

Closed
opened 2026-04-17 17:39:39 -05:00 by GiteaMirror · 21 comments
Owner

Originally created by @Roronoa-Zoroo on GitHub (Jul 16, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/3408

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

  1. Setup a Better Auth instance with user.additionalFields and the organization() plugin.
  2. Add the following to your config:
user: {
  additionalFields: {
    personalOrganizationId: { type: "string" },
  },
},
plugins: [organization()],
  1. Export the type using export type AuthType = typeof auth;
  2. Run tsc with the following tsconfig.json settings:
{
  "compilerOptions": {
    "strict": true,
    "composite": true,
    "sourceMap": true,
    "declarationMap": true
  }
}
  1. TypeScript throws: TS7056: The inferred type of this node exceeds the maximum length the compiler will serialize. An explicit type annotation is needed.

Current vs. Expected behavior

With both user.additionalFields and the organization() plugin enabled, TypeScript fails to emit types when exporting typeof auth due to an extremely long inferred type.

Expected:
Expected Better Auth to support inferred types without hitting the TS7056 error, even when extending with additional fields and plugins.

This error does not occur:

if I remove user.additionalFields

or if I remove the organization() plugin
So it seems to be related to the interaction between the two.

What version of Better Auth are you using?

1.2.12 (Was working fine in 1.2.7)

Provide environment information

Provide environment information
OS: Windows 11

Browser: N/A (server-side issue)

Node.js: v22.x

TypeScript: 5.8.3

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

Backend

Auth config (if applicable)

import { betterAuth } from "better-auth";
import { organization } from "better-auth/plugins";
export const auth = betterAuth({
  user: {
    additionalFields: {
      personalOrganizationId: { type: "string" },
    },
  },
  plugins: [organization()],
});

Additional context

Additional context
Reproduced both in dev and prod environments.

Downgrading to 1.2.7 fixes the issue.

Appears to be a TypeScript type bloat / serialization issue.

Workaround: disable declaration generation ("declaration": false), but that’s not always an option in monorepo/lib scenarios.

Originally created by @Roronoa-Zoroo on GitHub (Jul 16, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/3408 ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce 1. Setup a Better Auth instance with user.additionalFields and the organization() plugin. 2. Add the following to your config: ```ts user: { additionalFields: { personalOrganizationId: { type: "string" }, }, }, plugins: [organization()], ``` 3. Export the type using export type AuthType = typeof auth; 4. Run tsc with the following tsconfig.json settings: ``` { "compilerOptions": { "strict": true, "composite": true, "sourceMap": true, "declarationMap": true } } ``` 5. TypeScript throws: `TS7056: The inferred type of this node exceeds the maximum length the compiler will serialize. An explicit type annotation is needed.` ### Current vs. Expected behavior With both user.additionalFields and the organization() plugin enabled, TypeScript fails to emit types when exporting typeof auth due to an extremely long inferred type. Expected: Expected Better Auth to support inferred types without hitting the TS7056 error, even when extending with additional fields and plugins. This error does not occur: if I remove user.additionalFields or if I remove the organization() plugin So it seems to be related to the interaction between the two. ### What version of Better Auth are you using? 1.2.12 (Was working fine in 1.2.7) ### Provide environment information ```bash Provide environment information OS: Windows 11 Browser: N/A (server-side issue) Node.js: v22.x TypeScript: 5.8.3 ``` ### Which area(s) are affected? (Select all that apply) Backend ### Auth config (if applicable) ```typescript import { betterAuth } from "better-auth"; import { organization } from "better-auth/plugins"; export const auth = betterAuth({ user: { additionalFields: { personalOrganizationId: { type: "string" }, }, }, plugins: [organization()], }); ``` ### Additional context Additional context Reproduced both in dev and prod environments. Downgrading to 1.2.7 fixes the issue. Appears to be a TypeScript type bloat / serialization issue. Workaround: disable declaration generation ("declaration": false), but that’s not always an option in monorepo/lib scenarios.
GiteaMirror added the locked label 2026-04-17 17:39:39 -05:00
Author
Owner

@brunowego commented on GitHub (Jul 17, 2025):

What worked for me was to adjust ./tsconfig.json:

{
  // ...
  "compilerOptions": {
    // ...
    "declaration": false,
    "declarationMap": false
  },
  // ...
}

I don't think it's a perfect solution, it's a workaround.

<!-- gh-comment-id:3084238993 --> @brunowego commented on GitHub (Jul 17, 2025): What worked for me was to adjust `./tsconfig.json`: ```json { // ... "compilerOptions": { // ... "declaration": false, "declarationMap": false }, // ... } ``` I don't think it's a perfect solution, it's a workaround.
Author
Owner

@Kinfe123 commented on GitHub (Jul 18, 2025):

@brunowego yeah this is a workaround but got a drawback. we are working to make sure ts will be fine on understanding type inference tree while working with a bit of large plugin's like org and admin.

<!-- gh-comment-id:3089613498 --> @Kinfe123 commented on GitHub (Jul 18, 2025): @brunowego yeah this is a workaround but got a drawback. we are working to make sure ts will be fine on understanding type inference tree while working with a bit of large plugin's like org and admin.
Author
Owner

@thornhill6305 commented on GitHub (Jul 20, 2025):

hey @brunowego and @Roronoa-Zoroo , i managed to solve this by following another dev's suggestion in a similar issue:
https://github.com/better-auth/better-auth/issues/3067#issuecomment-2988246817

<!-- gh-comment-id:3094588828 --> @thornhill6305 commented on GitHub (Jul 20, 2025): hey @brunowego and @Roronoa-Zoroo , i managed to solve this by following another dev's suggestion in a similar issue: https://github.com/better-auth/better-auth/issues/3067#issuecomment-2988246817
Author
Owner

@dnyg commented on GitHub (Jul 22, 2025):

hey @brunowego and @Roronoa-Zoroo , i managed to solve this by following another dev's suggestion in a similar issue: #3067 (comment)

I had this problem as well, so I used the solution in the #3067 comment which solved it for me, but now that I upgraded to 1.3.2 (without making any changes to my code) it's broken again - even with this solution in place

<!-- gh-comment-id:3101505123 --> @dnyg commented on GitHub (Jul 22, 2025): > hey [@brunowego](https://github.com/brunowego) and [@Roronoa-Zoroo](https://github.com/Roronoa-Zoroo) , i managed to solve this by following another dev's suggestion in a similar issue: [#3067 (comment)](https://github.com/better-auth/better-auth/issues/3067#issuecomment-2988246817) I had this problem as well, so I used the solution in the #3067 comment which solved it for me, but now that I upgraded to 1.3.2 (without making any changes to my code) it's broken again - even with this solution in place
Author
Owner

@Kinfe123 commented on GitHub (Jul 22, 2025):

The reason why solution works #3067 is becasue that typescript is having a hard time for figuring out the type tree so explicitly mentioning what would be the type after composing the plugin works. but we are still working on making it easier for typescript to work with complex type with out affecting the performance.

<!-- gh-comment-id:3102018807 --> @Kinfe123 commented on GitHub (Jul 22, 2025): The reason why solution works #3067 is becasue that typescript is having a hard time for figuring out the type tree so explicitly mentioning what would be the type after composing the plugin works. but we are still working on making it easier for typescript to work with complex type with out affecting the performance.
Author
Owner

@kriegcloud commented on GitHub (Aug 1, 2025):

I feel like if you exposed internal types for plugins like AdminOptions we could make ts compiler quit yelling by using satisfies on individual plugin options.

<!-- gh-comment-id:3142027785 --> @kriegcloud commented on GitHub (Aug 1, 2025): I feel like if you exposed internal types for plugins like `AdminOptions` we could make ts compiler quit yelling by using `satisfies` on individual plugin options.
Author
Owner

@artzkaizen commented on GitHub (Aug 27, 2025):

Hi, Is there any progress on this ?

<!-- gh-comment-id:3227834259 --> @artzkaizen commented on GitHub (Aug 27, 2025): Hi, Is there any progress on this ?
Author
Owner

@rjmohammad commented on GitHub (Sep 18, 2025):

Just spent a few hours on this. Here’s what worked for me:

export type Plugins = [
  ReturnType<typeof twoFactor>,
  ReturnType<typeof passkey>,
  ReturnType<typeof oneTap>,
  ReturnType<typeof sso>,
  ReturnType<typeof admin>,
  ReturnType<typeof apiKey>,
  ReturnType<typeof organization>,
  ReturnType<typeof bearer>,
  ReturnType<typeof lastLoginMethod>,
  ReturnType<typeof jwt>,
];


const plugins: Plugins = [
  twoFactor(),
  // ... other plugins
  jwt(),
];

const options = {
  // ...
  plugins,
  // ...
}

const auth = betterAuth({
    ...options,
    database: {//...},
})
  
<!-- gh-comment-id:3305533354 --> @rjmohammad commented on GitHub (Sep 18, 2025): Just spent a few hours on this. Here’s what worked for me: ``` export type Plugins = [ ReturnType<typeof twoFactor>, ReturnType<typeof passkey>, ReturnType<typeof oneTap>, ReturnType<typeof sso>, ReturnType<typeof admin>, ReturnType<typeof apiKey>, ReturnType<typeof organization>, ReturnType<typeof bearer>, ReturnType<typeof lastLoginMethod>, ReturnType<typeof jwt>, ]; const plugins: Plugins = [ twoFactor(), // ... other plugins jwt(), ]; const options = { // ... plugins, // ... } const auth = betterAuth({ ...options, database: {//...}, }) ```
Author
Owner

@artzkaizen commented on GitHub (Sep 18, 2025):

export type Plugins = [
ReturnType,
ReturnType,
ReturnType,
ReturnType,
ReturnType,
ReturnType,
ReturnType,
ReturnType,
ReturnType,
ReturnType,
];

const plugins: Plugins = [
twoFactor(),
// ... other plugins
jwt(),
];

This works also the type must follow the same order and the plugins array

<!-- gh-comment-id:3305544610 --> @artzkaizen commented on GitHub (Sep 18, 2025): > export type Plugins = [ > ReturnType<typeof twoFactor>, > ReturnType<typeof passkey>, > ReturnType<typeof oneTap>, > ReturnType<typeof sso>, > ReturnType<typeof admin>, > ReturnType<typeof apiKey>, > ReturnType<typeof organization>, > ReturnType<typeof bearer>, > ReturnType<typeof lastLoginMethod>, > ReturnType<typeof jwt>, > ]; > > > const plugins: Plugins = [ > twoFactor(), > // ... other plugins > jwt(), > ]; This works also the type must follow the same order and the plugins array
Author
Owner

@PI-Gorbo commented on GitHub (Sep 20, 2025):

Just spent a few hours on this. Here’s what worked for me:

export type Plugins = [
  ReturnType<typeof twoFactor>,
  ReturnType<typeof passkey>,
  ReturnType<typeof oneTap>,
  ReturnType<typeof sso>,
  ReturnType<typeof admin>,
  ReturnType<typeof apiKey>,
  ReturnType<typeof organization>,
  ReturnType<typeof bearer>,
  ReturnType<typeof lastLoginMethod>,
  ReturnType<typeof jwt>,
];


const plugins: Plugins = [
  twoFactor(),
  // ... other plugins
  jwt(),
];

const options = {
  // ...
  plugins,
  // ...
}

const auth = betterAuth({
    ...options,
    database: {//...},
})
  

This solution worked (SEE BELOWS EDIT)!

I was able to make minimal changes:

type Plugins = [ReturnType<typeof organization>];

const plugins: Plugins = [organization()];

betterAuth({
    ...
    user: {
        additionalFields: {
            ...
        },
    },
    plugins: plugins,
});

EDIT:
After playing around with this more, I found this solution does not work.
the type Plugins simplifies to any for some reason 😢

<!-- gh-comment-id:3314315156 --> @PI-Gorbo commented on GitHub (Sep 20, 2025): > Just spent a few hours on this. Here’s what worked for me: > > ``` > export type Plugins = [ > ReturnType<typeof twoFactor>, > ReturnType<typeof passkey>, > ReturnType<typeof oneTap>, > ReturnType<typeof sso>, > ReturnType<typeof admin>, > ReturnType<typeof apiKey>, > ReturnType<typeof organization>, > ReturnType<typeof bearer>, > ReturnType<typeof lastLoginMethod>, > ReturnType<typeof jwt>, > ]; > > > const plugins: Plugins = [ > twoFactor(), > // ... other plugins > jwt(), > ]; > > const options = { > // ... > plugins, > // ... > } > > const auth = betterAuth({ > ...options, > database: {//...}, > }) > > ``` This solution worked (SEE BELOWS EDIT)! I was able to make minimal changes: ``` type Plugins = [ReturnType<typeof organization>]; const plugins: Plugins = [organization()]; betterAuth({ ... user: { additionalFields: { ... }, }, plugins: plugins, }); ``` EDIT: After playing around with this more, I found this solution does not work. the type `Plugins` simplifies to `any` for some reason 😢
Author
Owner

@Garnet-Liu commented on GitHub (Sep 20, 2025):

我刚刚花了几个小时研究这个问题。以下是对我有用的方法:

export type Plugins = [
  ReturnType<typeof twoFactor>,
  ReturnType<typeof passkey>,
  ReturnType<typeof oneTap>,
  ReturnType<typeof sso>,
  ReturnType<typeof admin>,
  ReturnType<typeof apiKey>,
  ReturnType<typeof organization>,
  ReturnType<typeof bearer>,
  ReturnType<typeof lastLoginMethod>,
  ReturnType<typeof jwt>,
];


const plugins: Plugins = [
  twoFactor(),
  // ... other plugins
  jwt(),
];

const options = {
  // ...
  plugins,
  // ...
}

const auth = betterAuth({
    ...options,
    database: {//...},
})
  

When using customSession, this method doesn’t work. It seems I can only keep looking for another solution.

<!-- gh-comment-id:3315035461 --> @Garnet-Liu commented on GitHub (Sep 20, 2025): > 我刚刚花了几个小时研究这个问题。以下是对我有用的方法: > > ``` > export type Plugins = [ > ReturnType<typeof twoFactor>, > ReturnType<typeof passkey>, > ReturnType<typeof oneTap>, > ReturnType<typeof sso>, > ReturnType<typeof admin>, > ReturnType<typeof apiKey>, > ReturnType<typeof organization>, > ReturnType<typeof bearer>, > ReturnType<typeof lastLoginMethod>, > ReturnType<typeof jwt>, > ]; > > > const plugins: Plugins = [ > twoFactor(), > // ... other plugins > jwt(), > ]; > > const options = { > // ... > plugins, > // ... > } > > const auth = betterAuth({ > ...options, > database: {//...}, > }) > > ``` When using customSession, this method doesn’t work. It seems I can only keep looking for another solution.
Author
Owner

@PI-Gorbo commented on GitHub (Sep 20, 2025):

hey @brunowego and @Roronoa-Zoroo , i managed to solve this by following another dev's suggestion in a similar issue: #3067 (comment)

@Garnet-Liu I ultimately tried this solution, which worked well.
I adapted it to my reqs:

const authConfig = (
    db: DB,
    config: {
        corsDomains?: string[];
    },
) =>
    ({
        ...
        user: {
            additionalFields: {
                ...
            },
        },
        ...
        plugins: [
            organization({
                allowUserToCreateOrganization: false,
            }),
        ],
    }) satisfies BetterAuthOptions;

// Why is this co complicated?
// I encountered this issue: https://github.com/better-auth/better-auth/issues/3408
// And found this as a solution:
// https://github.com/better-auth/better-auth/issues/3067#issuecomment-2988246817
export default function createAuthService(
    db: DB,
    config: {
        corsDomains?: string[];
    },
) {
    return betterAuth(authConfig(db, config)) as ReturnType<
        typeof betterAuth<ReturnType<typeof authConfig>>
    >;
}
<!-- gh-comment-id:3315291118 --> @PI-Gorbo commented on GitHub (Sep 20, 2025): > hey [@brunowego](https://github.com/brunowego) and [@Roronoa-Zoroo](https://github.com/Roronoa-Zoroo) , i managed to solve this by following another dev's suggestion in a similar issue: [#3067 (comment)](https://github.com/better-auth/better-auth/issues/3067#issuecomment-2988246817) @Garnet-Liu I ultimately tried this solution, which worked well. I adapted it to my reqs: ``` const authConfig = ( db: DB, config: { corsDomains?: string[]; }, ) => ({ ... user: { additionalFields: { ... }, }, ... plugins: [ organization({ allowUserToCreateOrganization: false, }), ], }) satisfies BetterAuthOptions; // Why is this co complicated? // I encountered this issue: https://github.com/better-auth/better-auth/issues/3408 // And found this as a solution: // https://github.com/better-auth/better-auth/issues/3067#issuecomment-2988246817 export default function createAuthService( db: DB, config: { corsDomains?: string[]; }, ) { return betterAuth(authConfig(db, config)) as ReturnType< typeof betterAuth<ReturnType<typeof authConfig>> >; } ```
Author
Owner

@PI-Gorbo commented on GitHub (Sep 21, 2025):

@Garnet-Liu
I just integrated in the customSession object, and am back to having the same problem again 😢 I think im in your boat.

<!-- gh-comment-id:3315415756 --> @PI-Gorbo commented on GitHub (Sep 21, 2025): @Garnet-Liu I just integrated in the customSession object, and am back to having the same problem again 😢 I think im in your boat.
Author
Owner

@Anulo2 commented on GitHub (Sep 21, 2025):

This workaround doesn't really work, if you move for example the openAPi plugin outside of the auth config the type inference for auth.api.generateOpenAPISchema is not there anymore....

I've got something like this:


import { type BetterAuthOptions, betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import {
  admin,
  multiSession,
  openAPI,
  organization,
  username,
} from "better-auth/plugins";
import { db } from "./db";

const authConfig = {
  basePath: "/api",
  database: drizzleAdapter(db, {
    provider: "pg", // Using PostgreSQL based on the schema
  }),
  plugins: [openAPI(), username(), admin(), organization(), multiSession()],
  baseURL: process.env.BETTER_AUTH_URL || "http://localhost:3000",
  secret: process.env.BETTER_AUTH_SECRET || "your-secret-key",
  emailAndPassword: {
    enabled: true,
  },
  socialProviders: {
    // Add social providers here if needed
    // github: {
    //   clientId: process.env.GITHUB_CLIENT_ID as string,
    //   clientSecret: process.env.GITHUB_CLIENT_SECRET as string,
    // },
  },
} satisfies BetterAuthOptions;

export const auth = betterAuth(authConfig) as ReturnType<
  typeof betterAuth<typeof authConfig>
>;

let _schema: ReturnType<typeof auth.api.generateOpenAPISchema>;
// biome-ignore lint/suspicious/noAssignInExpressions: <In the docs it's done like this>
const getSchema = async () => (_schema ??= auth.api.generateOpenAPISchema());

export const OpenAPI = {
  getPaths: (prefix = "/auth/api") =>
    getSchema().then(({ paths }) => {
      const reference: typeof paths = Object.create(null);

      for (const path of Object.keys(paths)) {
        const key = prefix + path;
        reference[key] = paths[path];

        for (const method of Object.keys(paths[path])) {
          // biome-ignore lint/suspicious/noExplicitAny: <In the docs it's done like this>
          const operation = (reference[key] as any)[method];

          operation.tags = ["Better Auth"];
        }
      }

      return reference;
      // biome-ignore lint/suspicious/noExplicitAny: <In the docs it's done like this>
    }) as Promise<any>,
  // biome-ignore lint/suspicious/noExplicitAny: <In the docs it's done like this>
  components: getSchema().then(({ components }) => components) as Promise<any>,
} as const;

To then import the openapi in elysia and I definetly need the type soundnees everywhere but right now I get the error from the compiler

<!-- gh-comment-id:3315955638 --> @Anulo2 commented on GitHub (Sep 21, 2025): This workaround doesn't really work, if you move for example the openAPi plugin outside of the auth config the type inference for auth.api.generateOpenAPISchema is not there anymore.... I've got something like this: ``` import { type BetterAuthOptions, betterAuth } from "better-auth"; import { drizzleAdapter } from "better-auth/adapters/drizzle"; import { admin, multiSession, openAPI, organization, username, } from "better-auth/plugins"; import { db } from "./db"; const authConfig = { basePath: "/api", database: drizzleAdapter(db, { provider: "pg", // Using PostgreSQL based on the schema }), plugins: [openAPI(), username(), admin(), organization(), multiSession()], baseURL: process.env.BETTER_AUTH_URL || "http://localhost:3000", secret: process.env.BETTER_AUTH_SECRET || "your-secret-key", emailAndPassword: { enabled: true, }, socialProviders: { // Add social providers here if needed // github: { // clientId: process.env.GITHUB_CLIENT_ID as string, // clientSecret: process.env.GITHUB_CLIENT_SECRET as string, // }, }, } satisfies BetterAuthOptions; export const auth = betterAuth(authConfig) as ReturnType< typeof betterAuth<typeof authConfig> >; let _schema: ReturnType<typeof auth.api.generateOpenAPISchema>; // biome-ignore lint/suspicious/noAssignInExpressions: <In the docs it's done like this> const getSchema = async () => (_schema ??= auth.api.generateOpenAPISchema()); export const OpenAPI = { getPaths: (prefix = "/auth/api") => getSchema().then(({ paths }) => { const reference: typeof paths = Object.create(null); for (const path of Object.keys(paths)) { const key = prefix + path; reference[key] = paths[path]; for (const method of Object.keys(paths[path])) { // biome-ignore lint/suspicious/noExplicitAny: <In the docs it's done like this> const operation = (reference[key] as any)[method]; operation.tags = ["Better Auth"]; } } return reference; // biome-ignore lint/suspicious/noExplicitAny: <In the docs it's done like this> }) as Promise<any>, // biome-ignore lint/suspicious/noExplicitAny: <In the docs it's done like this> components: getSchema().then(({ components }) => components) as Promise<any>, } as const; ``` To then import the openapi in elysia and I definetly need the type soundnees everywhere but right now I get the error from the compiler
Author
Owner

@Garnet-Liu commented on GitHub (Sep 22, 2025):

@PI-Gorbo Inspired by you, I’m now doing it this way, and both on the client and server I can correctly infer the role I added.

import { betterAuth, BetterAuthOptions } from "better-auth";
import { prismaAdapter } from "better-auth/adapters/prisma";
import { bearer, customSession, jwt, openAPI } from "better-auth/plugins";

import { authPrisma } from "@/lib/auth-prisma";

const customSessionPlugin = customSession(
  async ({ user, session }) => {
    console.log("================= customSession after =================");
    return { user, session: { ...session, role: "USER" } };
  },
  {},
  { shouldMutateListDeviceSessionsEndpoint: true },
);

type AuthPlugins = [
  ReturnType<typeof openAPI>,
  ReturnType<typeof bearer>,
  ReturnType<typeof jwt>,
  typeof customSessionPlugin,
];

const authPlugins: AuthPlugins = [openAPI(), bearer(), jwt(), customSessionPlugin];

const authConfig = {
  trustedOrigins: (process.env.BETTER_TRUSTED_ORIGINS ?? "").split(",").filter(Boolean),
  database: prismaAdapter(authPrisma, { provider: "sqlite" }),
  plugins: authPlugins,
  emailAndPassword: { enabled: true },
  socialProviders: {
    github: {
      clientId: process.env.BETTER_GITHUB_CLIENT_ID as string,
      clientSecret: process.env.BETTER_GITHUB_CLIENT_SECRET as string,
    },
    google: {
      enabled: true,
      clientId: process.env.BETTER_GOOGLE_CLIENT_ID as string,
      clientSecret: process.env.BETTER_GOOGLE_CLIENT_SECRET as string,
    },
  },
  advanced: {
    cookiePrefix: "mewtwochips-auth",
    useSecureCookies: process.env.NODE_ENV === "production",
  },
} satisfies BetterAuthOptions;

export const auth = betterAuth(authConfig) as ReturnType<typeof betterAuth<typeof authConfig>>;

<!-- gh-comment-id:3319003031 --> @Garnet-Liu commented on GitHub (Sep 22, 2025): @PI-Gorbo Inspired by you, I’m now doing it this way, and both on the client and server I can correctly infer the role I added. ```tsx import { betterAuth, BetterAuthOptions } from "better-auth"; import { prismaAdapter } from "better-auth/adapters/prisma"; import { bearer, customSession, jwt, openAPI } from "better-auth/plugins"; import { authPrisma } from "@/lib/auth-prisma"; const customSessionPlugin = customSession( async ({ user, session }) => { console.log("================= customSession after ================="); return { user, session: { ...session, role: "USER" } }; }, {}, { shouldMutateListDeviceSessionsEndpoint: true }, ); type AuthPlugins = [ ReturnType<typeof openAPI>, ReturnType<typeof bearer>, ReturnType<typeof jwt>, typeof customSessionPlugin, ]; const authPlugins: AuthPlugins = [openAPI(), bearer(), jwt(), customSessionPlugin]; const authConfig = { trustedOrigins: (process.env.BETTER_TRUSTED_ORIGINS ?? "").split(",").filter(Boolean), database: prismaAdapter(authPrisma, { provider: "sqlite" }), plugins: authPlugins, emailAndPassword: { enabled: true }, socialProviders: { github: { clientId: process.env.BETTER_GITHUB_CLIENT_ID as string, clientSecret: process.env.BETTER_GITHUB_CLIENT_SECRET as string, }, google: { enabled: true, clientId: process.env.BETTER_GOOGLE_CLIENT_ID as string, clientSecret: process.env.BETTER_GOOGLE_CLIENT_SECRET as string, }, }, advanced: { cookiePrefix: "mewtwochips-auth", useSecureCookies: process.env.NODE_ENV === "production", }, } satisfies BetterAuthOptions; export const auth = betterAuth(authConfig) as ReturnType<typeof betterAuth<typeof authConfig>>; ```
Author
Owner

@itajenglish commented on GitHub (Sep 25, 2025):

+1 on this issue with customSession 🙃

<!-- gh-comment-id:3331122676 --> @itajenglish commented on GitHub (Sep 25, 2025): +1 on this issue with customSession 🙃
Author
Owner

@djriffel commented on GitHub (Sep 25, 2025):

@Garnet-Liu Your solution seems to fix almost every typing error I was having, but critically I am unable to reference any plugin methods from inside the config object when including the plugins from outside as in your solution. Eg:

databaseHooks: {
      user: {
        create: {
          after: async (user) => {
            await auth.api.createOrganization({
              // Line above triggers ts(2339) type error of: Property 'createOrganization' does not exist on type 'InferAPI< ...
            });
          },
        },
      },
    }

Does anybody have any ideas on how to resolve this?

It appears it can't pick up on the types of the auth object when I bring the plugins from outside the object's body (eg: plugins: authPlugins), but it works if I put them inline (eg: plugins: [...]) but of course then my typings start causing errors again (this time with The inferred type of 'authConfig' cannot be named without a reference to '.pnpm/zod@4.0.17/node_modules/zod/v4/core'. This is likely not portable. A type annotation is necessary.ts(2742))

<!-- gh-comment-id:3331353225 --> @djriffel commented on GitHub (Sep 25, 2025): @Garnet-Liu Your solution seems to fix almost every typing error I was having, but critically I am unable to reference any plugin methods from inside the config object when including the plugins from outside as in your solution. Eg: ```typescript databaseHooks: { user: { create: { after: async (user) => { await auth.api.createOrganization({ // Line above triggers ts(2339) type error of: Property 'createOrganization' does not exist on type 'InferAPI< ... }); }, }, }, } ``` Does anybody have any ideas on how to resolve this? It appears it can't pick up on the types of the `auth` object when I bring the plugins from outside the object's body (eg: `plugins: authPlugins`), but it works if I put them inline (eg: `plugins: [...]`) but of course then my typings start causing errors again (this time with `The inferred type of 'authConfig' cannot be named without a reference to '.pnpm/zod@4.0.17/node_modules/zod/v4/core'. This is likely not portable. A type annotation is necessary.ts(2742)`)
Author
Owner

@Garnet-Liu commented on GitHub (Sep 25, 2025):

@djriffel According to the official documentation, when I modify my authPlugins as shown below, I encounter the same issue as you.

const authPlugins: AuthPlugins = [
  openAPI(),
  bearer(),
  jwt(),
  organization(),
  oidcProvider({
    useJWTPlugin: true, // Enable JWT plugin integration
    loginPage: "/sign-in",
    consentPage: "/consent",
    metadata: { issuer: process.env.BETTER_AUTH_URL },
  }),
  customSessionPlugin,
];

So I tried handling it the same way as customSession.

const customSessionPlugin = customSession(
  async ({ user, session }) => {
    console.log("================= customSession after =================");
    return { user, session: { ...session, role: "USER" } };
  },
  {},
  { shouldMutateListDeviceSessionsEndpoint: true },
);

const organizationPlugin = organization()

type AuthPlugins = [
  ReturnType<typeof openAPI>,
  ReturnType<typeof bearer>,
  ReturnType<typeof jwt>,
  typeof organizationPlugin,
  ReturnType<typeof oidcProvider>,
  typeof customSessionPlugin,
];

const authPlugins: AuthPlugins = [
  openAPI(),
  bearer(),
  jwt(),
  organizationPlugin,
  oidcProvider({
    useJWTPlugin: true, // Enable JWT plugin integration
    loginPage: "/sign-in",
    consentPage: "/consent",
    metadata: { issuer: process.env.BETTER_AUTH_URL },
  }),
  customSessionPlugin,
];

Now TypeScript can correctly indicate that this method exists.

auth.api.createOrganization()
<!-- gh-comment-id:3334431986 --> @Garnet-Liu commented on GitHub (Sep 25, 2025): @djriffel According to the official documentation, when I modify my authPlugins as shown below, I encounter the same issue as you. ```tsx const authPlugins: AuthPlugins = [ openAPI(), bearer(), jwt(), organization(), oidcProvider({ useJWTPlugin: true, // Enable JWT plugin integration loginPage: "/sign-in", consentPage: "/consent", metadata: { issuer: process.env.BETTER_AUTH_URL }, }), customSessionPlugin, ]; ``` So I tried handling it the same way as customSession. ```tsx const customSessionPlugin = customSession( async ({ user, session }) => { console.log("================= customSession after ================="); return { user, session: { ...session, role: "USER" } }; }, {}, { shouldMutateListDeviceSessionsEndpoint: true }, ); const organizationPlugin = organization() type AuthPlugins = [ ReturnType<typeof openAPI>, ReturnType<typeof bearer>, ReturnType<typeof jwt>, typeof organizationPlugin, ReturnType<typeof oidcProvider>, typeof customSessionPlugin, ]; const authPlugins: AuthPlugins = [ openAPI(), bearer(), jwt(), organizationPlugin, oidcProvider({ useJWTPlugin: true, // Enable JWT plugin integration loginPage: "/sign-in", consentPage: "/consent", metadata: { issuer: process.env.BETTER_AUTH_URL }, }), customSessionPlugin, ]; ``` Now TypeScript can correctly indicate that this method exists. ```tsx auth.api.createOrganization() ```
Author
Owner

@djriffel commented on GitHub (Sep 28, 2025):

@Garnet-Liu Thanks for the info, that helped move things along. However, I then realized I had forgotten that I removed all my export statements while troubleshooting these typing issues. After re-adding some exports I started getting more issue, referring to zod portable types again.

After some more digging, I saw in another thread that somebody resolved their issues by installing versions of zod that matched the version that better-auth was running on. After doing this myself, it seems to have fixed all of my typing issues. I'm on a pnpm monorepo and I had to install the matching zod version for every package in the repo that was using zod (and I even had to restart VSCode in one of the codebases that I applied this fix to). After all that though, things seem to be better now.

<!-- gh-comment-id:3342455311 --> @djriffel commented on GitHub (Sep 28, 2025): @Garnet-Liu Thanks for the info, that helped move things along. However, I then realized I had forgotten that I removed all my export statements while troubleshooting these typing issues. After re-adding some exports I started getting more issue, referring to zod portable types again. After some more digging, I saw in another thread that somebody resolved their issues by installing versions of zod that matched the version that better-auth was running on. After doing this myself, it seems to have fixed all of my typing issues. I'm on a pnpm monorepo and I had to install the matching zod version for every package in the repo that was using zod (and I even had to restart VSCode in one of the codebases that I applied this fix to). After all that though, things seem to be better now.
Author
Owner

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

import type { BetterAuthOptions } from "better-auth";
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import {
  admin,
  magicLink,
  organization,
  twoFactor,
  username,
} from "better-auth/plugins";

import { db } from "@acme/db/client";

export function initAuth(options: {
  baseUrl: string;
  secret: string | undefined;
}) {
  const authConfig = {
    baseURL: options.baseUrl,
    secret: options.secret,
    database: drizzleAdapter(db, {
      provider: "pg",
    }),
    emailAndPassword: {
      enabled: true,
    },
    socialProviders: {
      //config
    },
    plugins: [
      admin(),
      twoFactor(),
      username(),
      organization(),
      magicLink(/*config */),
    ],
  } satisfies BetterAuthOptions;

  // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
  return betterAuth(authConfig) as ReturnType<
    typeof betterAuth<typeof authConfig>
  >;
}

export type Auth = ReturnType<typeof initAuth>;

type ExtractedType = Extract<
  Awaited<ReturnType<Auth["api"]["getSession"]>>,
  { session: unknown }
>;

export type Session = {
  session?: ExtractedType["session"];
  user?: ExtractedType["user"];
} | null;

Here's my solution (...after reading all threads concerning this issue).

Also, getSession() is returning a lot of useless typing, so there's a declaration to use the actual values returned by getSession() (If you have any suggestions to make simpler go ahead!)

Why is it that complicated to get the exact typing from BetterAuth? Typescript shenanigans? Skill issue? I'd like an official answer, the examples work until you use many plugins, which is kinda the point of BetterAuth...

Thanks!

<!-- gh-comment-id:3362114564 --> @j-fdion commented on GitHub (Oct 2, 2025): ``` import type { BetterAuthOptions } from "better-auth"; import { betterAuth } from "better-auth"; import { drizzleAdapter } from "better-auth/adapters/drizzle"; import { admin, magicLink, organization, twoFactor, username, } from "better-auth/plugins"; import { db } from "@acme/db/client"; export function initAuth(options: { baseUrl: string; secret: string | undefined; }) { const authConfig = { baseURL: options.baseUrl, secret: options.secret, database: drizzleAdapter(db, { provider: "pg", }), emailAndPassword: { enabled: true, }, socialProviders: { //config }, plugins: [ admin(), twoFactor(), username(), organization(), magicLink(/*config */), ], } satisfies BetterAuthOptions; // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion return betterAuth(authConfig) as ReturnType< typeof betterAuth<typeof authConfig> >; } export type Auth = ReturnType<typeof initAuth>; type ExtractedType = Extract< Awaited<ReturnType<Auth["api"]["getSession"]>>, { session: unknown } >; export type Session = { session?: ExtractedType["session"]; user?: ExtractedType["user"]; } | null; ``` Here's my solution (...after reading all threads concerning this issue). Also, getSession() is returning a lot of useless typing, so there's a declaration to use the actual values returned by getSession() (If you have any suggestions to make simpler go ahead!) Why is it that complicated to get the exact typing from BetterAuth? Typescript shenanigans? Skill issue? I'd like an official answer, the examples work until you use many plugins, which is kinda the point of BetterAuth... Thanks!
Author
Owner

@himself65 commented on GitHub (Nov 14, 2025):

We have fixed this in 1.4

<!-- gh-comment-id:3530433457 --> @himself65 commented on GitHub (Nov 14, 2025): We have fixed this in 1.4
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#26916