Field is not inferring the correct type #2296

Closed
opened 2026-03-13 09:42:11 -05:00 by GiteaMirror · 5 comments
Owner

Originally created by @Yorizel on GitHub (Nov 12, 2025).

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

Image Image

Current vs. Expected behavior

not typing the correct field on the return, should include the additonal field on the type

What version of Better Auth are you using?

1.3.34

System info

{
  "system": {
    "platform": "linux",
    "arch": "x64",
    "version": "#1 SMP PREEMPT_DYNAMIC Sun, 02 Nov 2025 17:27:22 +0000",
    "release": "6.17.7-arch1-1",
    "cpuCount": 28,
    "cpuModel": "Intel(R) Xeon(R) CPU E5-2680 v4 @ 2.40GHz",
    "totalMemory": "62.64 GB",
    "freeMemory": "48.07 GB"
  },
  "node": {
    "version": "v22.21.1",
    "env": "development"
  },
  "packageManager": {
    "name": "bun",
    "version": "1.2.23"
  },
  "frameworks": null,
  "databases": null,
  "betterAuth": {
    "version": "Unknown",
    "config": null
  }
}

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

Types

Auth config (if applicable)

import type { DatabaseInstance } from "@packages/database/client";
import { findMemberByUserId } from "@packages/database/repositories/auth-repository";
import { getDomain, isProduction } from "@packages/environment/helpers";
import { serverEnv } from "@packages/environment/server";
import type { PaymentClient } from "@packages/payment/client";
import { getCustomerState } from "@packages/payment/ingestion";
import { POLAR_PLAN_SLUGS, POLAR_PLANS } from "@packages/payment/plans";
import {
   type ResendClient,
   type SendEmailOTPOptions,
   sendEmailOTP,
   sendOrganizationInvitation,
} from "@packages/transactional/client";
import { checkout, polar, portal, usage } from "@polar-sh/better-auth";
import { type BetterAuthOptions, betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import {
   admin,
   apiKey,
   emailOTP,
   openAPI,
   organization,
} from "better-auth/plugins";
export interface AuthOptions {
   db: DatabaseInstance;
   polarClient: PaymentClient;
   resendClient: ResendClient;
}

export const getAuthOptions = (
   db: DatabaseInstance,
   resendClient: ResendClient,
   polarClient: PaymentClient,
) =>
   ({
      advanced: {
         crossSubDomainCookies: {
            domain: ".contentagen.com",
            enabled: isProduction,
         },
      },
      database: drizzleAdapter(db, {
         provider: "pg",
      }),
      databaseHooks: {
         session: {
            create: {
               before: async (session) => {
                  try {
                     const member = await findMemberByUserId(
                        db,
                        session.userId,
                     );

                     if (member?.organizationId) {
                        console.log(
                           `Setting activeOrganizationId for user ${session.userId} to ${member.organizationId}`,
                        );
                        return {
                           data: {
                              ...session,
                              activeOrganizationId: member.organizationId,
                           },
                        };
                     }
                  } catch (error) {
                     console.error(
                        "Error in session create before hook:",
                        error,
                     );
                     return {
                        data: {
                           ...session,
                        },
                     };
                  }
               },
            },
         },
      },
      emailAndPassword: {
         enabled: true,
         requireEmailVerification: true,
      },
      emailVerification: {
         autoSignInAfterVerification: true,
         sendOnSignUp: true,
      },
      plugins: [
         admin(),
         polar({
            client: polarClient,
            createCustomerOnSignUp: true,
            use: [
               portal(),
               checkout({
                  authenticatedUsersOnly: true,
                  products: [
                     POLAR_PLANS[POLAR_PLAN_SLUGS.BASIC],
                     POLAR_PLANS[POLAR_PLAN_SLUGS.HOBBY],
                  ],
                  successUrl: `${getDomain()}/profile`,
               }),
               usage(),
            ],
         }),
         emailOTP({
            expiresIn: 60 * 10,
            otpLength: 6,
            sendVerificationOnSignUp: true,
            async sendVerificationOTP({
               email,
               otp,
               type,
            }: SendEmailOTPOptions) {
               await sendEmailOTP(resendClient, { email, otp, type });
            },
         }),
         openAPI(),
         organization({
            allowUserToCreateOrganization: async (user) => {
               const state = await getCustomerState(polarClient, user.id);
               return state?.activeSubscriptions.length > 0;
            },
            organizationLimit: 1,
            schema: {
               organization: {
                  additionalFields: {
                     description: {
                        defaultValue: "",
                        input: true,
                        required: false,
                        type: "string",
                     },
                  },
               },
               team: {
                  additionalFields: {
                     description: {
                        defaultValue: "",
                        input: true,
                        required: false,
                        type: "string",
                     },
                  },
               },
            },
            async sendInvitationEmail(data) {
               const inviteLink = `${getDomain()}/callback/organization/invitation/${data.id}`;
               await sendOrganizationInvitation(resendClient, {
                  email: data.email,
                  invitedByEmail: data.inviter.user.email,
                  invitedByUsername: data.inviter.user.name,
                  inviteLink,
                  teamName: data.organization.name,
               });
            },
            teams: {
               allowRemovingAllTeams: false,
               enabled: true,
               maximumMembersPerTeam: 50,
               maximumTeams: 10,
            },
         }),
         apiKey({
            apiKeyHeaders: "sdk-api-key",
            enableMetadata: true,
            enableSessionForAPIKeys: true,
            rateLimit: {
               enabled: true,
               maxRequests: 500, // 500 requests per hour
               timeWindow: 1000 * 60 * 60, // 1 hour
            },
         }),
      ],

      secret: serverEnv.BETTER_AUTH_SECRET,
      session: {
         cookieCache: {
            enabled: true,
            maxAge: 5 * 60,
         },
      },
      socialProviders: {
         google: {
            clientId: serverEnv.BETTER_AUTH_GOOGLE_CLIENT_ID as string,
            clientSecret: serverEnv.BETTER_AUTH_GOOGLE_CLIENT_SECRET as string,
            prompt: "select_account" as const,
         },
      },
      trustedOrigins: serverEnv.BETTER_AUTH_TRUSTED_ORIGINS.split(","),
   }) satisfies BetterAuthOptions;

export const createAuth = (options: AuthOptions) => {
   const authOptions = getAuthOptions(
      options.db,
      options.resendClient,
      options.polarClient,
   );
   return betterAuth(authOptions);
};
export type AuthInstance = ReturnType<typeof createAuth>;

Additional context

No response

Originally created by @Yorizel on GitHub (Nov 12, 2025). ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce <img width="2540" height="1022" alt="Image" src="https://github.com/user-attachments/assets/ab2a8387-4e0c-4b6f-8bf3-1018b41fb285" /> <img width="899" height="502" alt="Image" src="https://github.com/user-attachments/assets/f3f49509-6f6a-4872-85c0-02b9b1b6d703" /> ### Current vs. Expected behavior not typing the correct field on the return, should include the additonal field on the type ### What version of Better Auth are you using? 1.3.34 ### System info ```bash { "system": { "platform": "linux", "arch": "x64", "version": "#1 SMP PREEMPT_DYNAMIC Sun, 02 Nov 2025 17:27:22 +0000", "release": "6.17.7-arch1-1", "cpuCount": 28, "cpuModel": "Intel(R) Xeon(R) CPU E5-2680 v4 @ 2.40GHz", "totalMemory": "62.64 GB", "freeMemory": "48.07 GB" }, "node": { "version": "v22.21.1", "env": "development" }, "packageManager": { "name": "bun", "version": "1.2.23" }, "frameworks": null, "databases": null, "betterAuth": { "version": "Unknown", "config": null } } ``` ### Which area(s) are affected? (Select all that apply) Types ### Auth config (if applicable) ```typescript import type { DatabaseInstance } from "@packages/database/client"; import { findMemberByUserId } from "@packages/database/repositories/auth-repository"; import { getDomain, isProduction } from "@packages/environment/helpers"; import { serverEnv } from "@packages/environment/server"; import type { PaymentClient } from "@packages/payment/client"; import { getCustomerState } from "@packages/payment/ingestion"; import { POLAR_PLAN_SLUGS, POLAR_PLANS } from "@packages/payment/plans"; import { type ResendClient, type SendEmailOTPOptions, sendEmailOTP, sendOrganizationInvitation, } from "@packages/transactional/client"; import { checkout, polar, portal, usage } from "@polar-sh/better-auth"; import { type BetterAuthOptions, betterAuth } from "better-auth"; import { drizzleAdapter } from "better-auth/adapters/drizzle"; import { admin, apiKey, emailOTP, openAPI, organization, } from "better-auth/plugins"; export interface AuthOptions { db: DatabaseInstance; polarClient: PaymentClient; resendClient: ResendClient; } export const getAuthOptions = ( db: DatabaseInstance, resendClient: ResendClient, polarClient: PaymentClient, ) => ({ advanced: { crossSubDomainCookies: { domain: ".contentagen.com", enabled: isProduction, }, }, database: drizzleAdapter(db, { provider: "pg", }), databaseHooks: { session: { create: { before: async (session) => { try { const member = await findMemberByUserId( db, session.userId, ); if (member?.organizationId) { console.log( `Setting activeOrganizationId for user ${session.userId} to ${member.organizationId}`, ); return { data: { ...session, activeOrganizationId: member.organizationId, }, }; } } catch (error) { console.error( "Error in session create before hook:", error, ); return { data: { ...session, }, }; } }, }, }, }, emailAndPassword: { enabled: true, requireEmailVerification: true, }, emailVerification: { autoSignInAfterVerification: true, sendOnSignUp: true, }, plugins: [ admin(), polar({ client: polarClient, createCustomerOnSignUp: true, use: [ portal(), checkout({ authenticatedUsersOnly: true, products: [ POLAR_PLANS[POLAR_PLAN_SLUGS.BASIC], POLAR_PLANS[POLAR_PLAN_SLUGS.HOBBY], ], successUrl: `${getDomain()}/profile`, }), usage(), ], }), emailOTP({ expiresIn: 60 * 10, otpLength: 6, sendVerificationOnSignUp: true, async sendVerificationOTP({ email, otp, type, }: SendEmailOTPOptions) { await sendEmailOTP(resendClient, { email, otp, type }); }, }), openAPI(), organization({ allowUserToCreateOrganization: async (user) => { const state = await getCustomerState(polarClient, user.id); return state?.activeSubscriptions.length > 0; }, organizationLimit: 1, schema: { organization: { additionalFields: { description: { defaultValue: "", input: true, required: false, type: "string", }, }, }, team: { additionalFields: { description: { defaultValue: "", input: true, required: false, type: "string", }, }, }, }, async sendInvitationEmail(data) { const inviteLink = `${getDomain()}/callback/organization/invitation/${data.id}`; await sendOrganizationInvitation(resendClient, { email: data.email, invitedByEmail: data.inviter.user.email, invitedByUsername: data.inviter.user.name, inviteLink, teamName: data.organization.name, }); }, teams: { allowRemovingAllTeams: false, enabled: true, maximumMembersPerTeam: 50, maximumTeams: 10, }, }), apiKey({ apiKeyHeaders: "sdk-api-key", enableMetadata: true, enableSessionForAPIKeys: true, rateLimit: { enabled: true, maxRequests: 500, // 500 requests per hour timeWindow: 1000 * 60 * 60, // 1 hour }, }), ], secret: serverEnv.BETTER_AUTH_SECRET, session: { cookieCache: { enabled: true, maxAge: 5 * 60, }, }, socialProviders: { google: { clientId: serverEnv.BETTER_AUTH_GOOGLE_CLIENT_ID as string, clientSecret: serverEnv.BETTER_AUTH_GOOGLE_CLIENT_SECRET as string, prompt: "select_account" as const, }, }, trustedOrigins: serverEnv.BETTER_AUTH_TRUSTED_ORIGINS.split(","), }) satisfies BetterAuthOptions; export const createAuth = (options: AuthOptions) => { const authOptions = getAuthOptions( options.db, options.resendClient, options.polarClient, ); return betterAuth(authOptions); }; export type AuthInstance = ReturnType<typeof createAuth>; ``` ### Additional context _No response_
Author
Owner

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

(tag @better-auth-agent if you would like an answer from the Agent)

DiagramDiscordGitHub

Diagram Join Star

@better-auth-agent[bot] commented on GitHub (Nov 12, 2025): (tag @better-auth-agent if you would like an answer from the Agent) <!-- bot:webhook reply v1 --> [Diagram](https://repodiagrams.s3.eu-north-1.amazonaws.com/better-auth_ultra_detailed_interactive.html) • [Discord](https://discord.gg/better-auth) • [GitHub](https://github.com/better-auth/better-auth) [![Diagram](https://img.shields.io/badge/Diagram-2b3137?style=flat-square)](https://repodiagrams.s3.eu-north-1.amazonaws.com/better-auth_ultra_detailed_interactive.html) [![Join](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&style=flat-square)](https://discord.gg/better-auth) [![Star](https://img.shields.io/badge/star-181717?logo=github&logoColor=white&style=flat-square)](https://github.com/better-auth/better-auth)
Author
Owner

@dosubot[bot] commented on GitHub (Nov 12, 2025):

This is a known limitation in Better Auth v1.3.x: additionalFields defined in the organization or team schema (like description) are not automatically reflected in the inferred TypeScript types for returned objects. This means TypeScript won't recognize those fields unless you manually augment the types or use workarounds.

Workarounds include using type assertions (e.g., as any), leveraging the metadata field for extra data, or generating types from your database schema (for example, with drizzle-zod) if you need strict typing. Also, make sure your config uses satisfies BetterAuthOptions and avoid splitting config across files to help with inference. If you have exactOptionalPropertyTypes enabled in your tsconfig, try setting it to false, as it can break plugin field inference in some cases (source, source).

Full type inference for additionalFields is fixed in the 1.4 beta, but those changes aren't portable back to 1.3.x. If you can upgrade, use the inferOrgAdditionalFields utility and pass your server's auth type to the client plugin schema for correct inference (source, source).

If you need to stay on 1.3.x, manual type augmentation or type assertions are the only reliable options for now.

To reply, just mention @dosu.


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

@dosubot[bot] commented on GitHub (Nov 12, 2025): <!-- Answer --> This is a known limitation in Better Auth v1.3.x: additionalFields defined in the organization or team schema (like description) are not automatically reflected in the inferred TypeScript types for returned objects. This means TypeScript won't recognize those fields unless you manually augment the types or use workarounds. Workarounds include using type assertions (e.g., as any), leveraging the metadata field for extra data, or generating types from your database schema (for example, with drizzle-zod) if you need strict typing. Also, make sure your config uses satisfies BetterAuthOptions and avoid splitting config across files to help with inference. If you have exactOptionalPropertyTypes enabled in your tsconfig, try setting it to false, as it can break plugin field inference in some cases ([source](https://github.com/better-auth/better-auth/issues/5218), [source](https://github.com/better-auth/better-auth/issues/5122)). Full type inference for additionalFields is fixed in the 1.4 beta, but those changes aren't portable back to 1.3.x. If you can upgrade, use the inferOrgAdditionalFields utility and pass your server's auth type to the client plugin schema for correct inference ([source](https://github.com/better-auth/better-auth/issues/3686), [source](https://github.com/better-auth/better-auth/issues/3733)). If you need to stay on 1.3.x, manual type augmentation or type assertions are the only reliable options for now. <!-- 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/4615c8d8-d7ee-47ab-ac8a-7c967d6da908?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/4615c8d8-d7ee-47ab-ac8a-7c967d6da908?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/4615c8d8-d7ee-47ab-ac8a-7c967d6da908?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/4615c8d8-d7ee-47ab-ac8a-7c967d6da908?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/4615c8d8-d7ee-47ab-ac8a-7c967d6da908?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/4615c8d8-d7ee-47ab-ac8a-7c967d6da908?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/4615c8d8-d7ee-47ab-ac8a-7c967d6da908?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/5940)
Author
Owner

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

I believe we have resolved this issue in version 1.4. So I'm closing this now

@himself65 commented on GitHub (Nov 13, 2025): I believe we have resolved this issue in version 1.4. So I'm closing this now
Author
Owner

@jcourson8 commented on GitHub (Nov 15, 2025):

I believe I'm still getting this issue with 1.4.0-beta.20. On the server side, I'm defining additional fields for team:

team: { 
  additionalFields: {
    slug: {
      type: "string",
      required: true,
      unique: true,
      input: true,
    },
    description: {
      type: "string",
      required: false,
      input: true,
    },
  },
},

but, the following:

const userTeams = await auth.api.listUserTeams({
    headers: await headers(),
});

is of type:

const userTeams: {
    id: string;
    name: string;
    organizationId: string;
    createdAt: Date;
    updatedAt?: Date;
}[]
@jcourson8 commented on GitHub (Nov 15, 2025): I believe I'm still getting this issue with `1.4.0-beta.20`. On the server side, I'm defining additional fields for team: ```ts team: { additionalFields: { slug: { type: "string", required: true, unique: true, input: true, }, description: { type: "string", required: false, input: true, }, }, }, ``` but, the following: ```ts const userTeams = await auth.api.listUserTeams({ headers: await headers(), }); ``` is of type: ```ts const userTeams: { id: string; name: string; organizationId: string; createdAt: Date; updatedAt?: Date; }[] ```
Author
Owner

@Lqm1 commented on GitHub (Feb 12, 2026):

I don't think this issue has been resolved. As already pointed out, it's fixed only for listTeams, while listUserTeams and $Infer.Team are still broken.

Image Image Image
@Lqm1 commented on GitHub (Feb 12, 2026): I don't think this issue has been resolved. As already pointed out, it's fixed only for `listTeams`, while `listUserTeams` and `$Infer.Team` are still broken. <img width="632" height="286" alt="Image" src="https://github.com/user-attachments/assets/087c9476-aee8-49d7-84c0-31731a200562" /> <img width="669" height="230" alt="Image" src="https://github.com/user-attachments/assets/1e7fa424-66b7-4fdf-9667-a709e9438fcb" /> <img width="570" height="191" alt="Image" src="https://github.com/user-attachments/assets/7a2ef391-0b50-4be7-9d70-cbc01527b661" />
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#2296