[GH-ISSUE #6593] Missing user's additional fields in database hooks and email verification #19195

Open
opened 2026-04-15 18:00:46 -05:00 by GiteaMirror · 4 comments
Owner

Originally created by @Hezaerd on GitHub (Dec 7, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/6593

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

  1. Create a backend (NextJS)
  2. Add additional fields (preferredLocale in my case)
  3. Try to get it inside of user create database hook and in the sendVerificationEmail() function from the emailVerification

Current vs. Expected behavior

databaseHooks: {
    user: {
      create: {
        before: async (user, ctx) => {
              // user should have inferred types from user's additional fields
        },
      },
    },
  },

Same for

emailVerification: {
    autoSignInAfterVerification: true,
    sendOnSignUp: true,
    sendVerificationEmail: async ({ user, url }) => {
              // user should also have inferred types from user's addition fields
      });
    },

What version of Better Auth are you using?

1.4.3

System info

{
  "system": {
    "platform": "linux",
    "arch": "x64",
    "version": "#1 SMP PREEMPT_DYNAMIC Thu Jun  5 18:30:46 UTC 2025",
    "release": "6.6.87.2-microsoft-standard-WSL2",
    "cpuCount": 2,
    "cpuModel": "AMD Ryzen 9 5900HS with Radeon Graphics",
    "totalMemory": "5.79 GB",
    "freeMemory": "0.71 GB"
  },
  "node": {
    "version": "v22.20.0",
    "env": "development"
  },
  "packageManager": {
    "name": "npm",
    "version": "10.9.3"
  },
  "frameworks": [
    {
      "name": "next",
      "version": "16.0.7"
    },
    {
      "name": "react",
      "version": "19.2.0"
    }
  ],
  "databases": [
    {
      "name": "drizzle",
      "version": "^0.45.0"
    },
    {
      "name": "@neondatabase/serverless",
      "version": "^1.0.2"
    }
  ],
  "betterAuth": {
    "version": "^1.4.3",
    "config": null
  }
}

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

Backend

Auth config (if applicable)

import { checkout, polar, webhooks } from "@polar-sh/better-auth";
import { render } from "@react-email/components";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { betterAuth } from "better-auth/minimal";
import { VerificationEmail } from "@/components/emails/templates/verification-email";
import type { EmailLocale } from "@/i18n/email";
import { db } from "@/lib/db";
import * as schema from "@/lib/db/schema";
import { serverEnv } from "@/lib/env/server";
import { polarClient } from "@/lib/polar";
import { onOrderPaid } from "@/lib/polar/events";
import { checkoutProducts } from "@/lib/polar/products";
import { resend } from "@/lib/resend";

export const auth = betterAuth({
  database: drizzleAdapter(db, {
    provider: "pg",
    schema: schema,
  }),
  experimental: {
    joins: true,
  },
  user: {
    additionalFields: {
      isPremium: {
        type: "boolean",
        input: false,
      },
      premiumGrantedAt: {
        type: "date",
        input: false,
      },
      preferredLocale: {
        type: "string",
        input: false,
      },
    },
  },
  session: {
    cookieCache: {
      enabled: true,
      maxAge: 60 * 2, // 2 minutes
      strategy: "compact",
      refreshCache: true,
    },
  },
  databaseHooks: {
    user: {
      create: {
        before: async (user, ctx) => {
          const acceptLanguage =
            ctx?.request?.headers?.get("accept-language") ?? "";
          const primaryLang = acceptLanguage
            .split(",")[0]
            ?.split("-")[0]
            ?.toLowerCase();

          const locale = primaryLang === "fr" ? "fr" : "en";

          return {
            data: {
              ...user,
              preferredLocale: locale,
            },
          };
        },
      },
    },
  },
  emailVerification: {
    autoSignInAfterVerification: true,
    sendOnSignUp: true,
    sendVerificationEmail: async ({ user, url }) => {
      const fullUser = user as typeof user & { preferredLocale?: string };

      void resend.emails.send({
        to: user.email,
        from: "noreply@notifications.otomai.app",
        subject: "Verify your email",
        html: await render(
          VerificationEmail({
            userName: fullUser.name,
            verificationUrl: url,
            locale: (fullUser.preferredLocale ?? "fr") as EmailLocale,
          }),
        ),
      });
    },
  },
  socialProviders: {
    discord: {
      clientId: serverEnv.DISCORD_CLIENT_ID,
      clientSecret: serverEnv.DISCORD_CLIENT_SECRET,
    },
  },
  plugins: [
    polar({
      client: polarClient,
      createCustomerOnSignUp: true,
      use: [
        checkout({
          products: checkoutProducts,
          successUrl: `${serverEnv.NEXT_PUBLIC_APP_URL}/success?checkout_id={CHECKOUT_ID}`,
          authenticatedUsersOnly: true,
        }),
        webhooks({
          secret: serverEnv.POLAR_WEBHOOK_SECRET,
          onOrderPaid: async (payload) => await onOrderPaid(payload),
        }),
      ],
    }),
  ],
});

export type Session = typeof auth.$Infer.Session;
export type User = typeof auth.$Infer.Session.user;

Additional context

No response

Originally created by @Hezaerd on GitHub (Dec 7, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/6593 ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce 1. Create a backend (NextJS) 2. Add additional fields (preferredLocale in my case) 3. Try to get it inside of user create database hook and in the sendVerificationEmail() function from the emailVerification ### Current vs. Expected behavior ```ts databaseHooks: { user: { create: { before: async (user, ctx) => { // user should have inferred types from user's additional fields }, }, }, }, ``` Same for ```ts emailVerification: { autoSignInAfterVerification: true, sendOnSignUp: true, sendVerificationEmail: async ({ user, url }) => { // user should also have inferred types from user's addition fields }); }, ``` ### What version of Better Auth are you using? 1.4.3 ### System info ```bash { "system": { "platform": "linux", "arch": "x64", "version": "#1 SMP PREEMPT_DYNAMIC Thu Jun 5 18:30:46 UTC 2025", "release": "6.6.87.2-microsoft-standard-WSL2", "cpuCount": 2, "cpuModel": "AMD Ryzen 9 5900HS with Radeon Graphics", "totalMemory": "5.79 GB", "freeMemory": "0.71 GB" }, "node": { "version": "v22.20.0", "env": "development" }, "packageManager": { "name": "npm", "version": "10.9.3" }, "frameworks": [ { "name": "next", "version": "16.0.7" }, { "name": "react", "version": "19.2.0" } ], "databases": [ { "name": "drizzle", "version": "^0.45.0" }, { "name": "@neondatabase/serverless", "version": "^1.0.2" } ], "betterAuth": { "version": "^1.4.3", "config": null } } ``` ### Which area(s) are affected? (Select all that apply) Backend ### Auth config (if applicable) ```typescript import { checkout, polar, webhooks } from "@polar-sh/better-auth"; import { render } from "@react-email/components"; import { drizzleAdapter } from "better-auth/adapters/drizzle"; import { betterAuth } from "better-auth/minimal"; import { VerificationEmail } from "@/components/emails/templates/verification-email"; import type { EmailLocale } from "@/i18n/email"; import { db } from "@/lib/db"; import * as schema from "@/lib/db/schema"; import { serverEnv } from "@/lib/env/server"; import { polarClient } from "@/lib/polar"; import { onOrderPaid } from "@/lib/polar/events"; import { checkoutProducts } from "@/lib/polar/products"; import { resend } from "@/lib/resend"; export const auth = betterAuth({ database: drizzleAdapter(db, { provider: "pg", schema: schema, }), experimental: { joins: true, }, user: { additionalFields: { isPremium: { type: "boolean", input: false, }, premiumGrantedAt: { type: "date", input: false, }, preferredLocale: { type: "string", input: false, }, }, }, session: { cookieCache: { enabled: true, maxAge: 60 * 2, // 2 minutes strategy: "compact", refreshCache: true, }, }, databaseHooks: { user: { create: { before: async (user, ctx) => { const acceptLanguage = ctx?.request?.headers?.get("accept-language") ?? ""; const primaryLang = acceptLanguage .split(",")[0] ?.split("-")[0] ?.toLowerCase(); const locale = primaryLang === "fr" ? "fr" : "en"; return { data: { ...user, preferredLocale: locale, }, }; }, }, }, }, emailVerification: { autoSignInAfterVerification: true, sendOnSignUp: true, sendVerificationEmail: async ({ user, url }) => { const fullUser = user as typeof user & { preferredLocale?: string }; void resend.emails.send({ to: user.email, from: "noreply@notifications.otomai.app", subject: "Verify your email", html: await render( VerificationEmail({ userName: fullUser.name, verificationUrl: url, locale: (fullUser.preferredLocale ?? "fr") as EmailLocale, }), ), }); }, }, socialProviders: { discord: { clientId: serverEnv.DISCORD_CLIENT_ID, clientSecret: serverEnv.DISCORD_CLIENT_SECRET, }, }, plugins: [ polar({ client: polarClient, createCustomerOnSignUp: true, use: [ checkout({ products: checkoutProducts, successUrl: `${serverEnv.NEXT_PUBLIC_APP_URL}/success?checkout_id={CHECKOUT_ID}`, authenticatedUsersOnly: true, }), webhooks({ secret: serverEnv.POLAR_WEBHOOK_SECRET, onOrderPaid: async (payload) => await onOrderPaid(payload), }), ], }), ], }); export type Session = typeof auth.$Infer.Session; export type User = typeof auth.$Infer.Session.user; ``` ### Additional context _No response_
GiteaMirror added the platformcorebug labels 2026-04-15 18:00:46 -05:00
Author
Owner

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

Hi there, thanks for opening this issue! It appears that your additional field (preferredLocale) isn’t being inferred in both your database hook and email verification functions, which is a known challenge with the current TS type inference for extended schemas. One workaround is to explicitly cast the user object to your extended type within your hooks or email verification logic. This ensures TypeScript recognizes your additional fields reliably [1]. Have you tried adding an explicit type annotation in your create hook (and in your sendVerificationEmail function) to work around this issue? Tag @better-auth-agent if you would like an answer from the Agent.

If you need more help, tag @better-auth-agent in a comment so I can respond.

DiagramDiscordGitHub

Diagram Join Star

<!-- gh-comment-id:3623718773 --> @better-auth-agent[bot] commented on GitHub (Dec 7, 2025): Hi there, thanks for opening this issue! It appears that your additional field (preferredLocale) isn’t being inferred in both your database hook and email verification functions, which is a known challenge with the current TS type inference for extended schemas. One workaround is to explicitly cast the user object to your extended type within your hooks or email verification logic. This ensures TypeScript recognizes your additional fields reliably [[1]](https://github.com/better-auth/better-auth/issues/6446). Have you tried adding an explicit type annotation in your create hook (and in your sendVerificationEmail function) to work around this issue? Tag @better-auth-agent if you would like an answer from the Agent. _If you need more help, tag @better-auth-agent in a comment so I can respond._ <!-- bot:webhook reply v1 --> [Diagram](https://repodiagrams.s3.eu-north-1.amazonaws.com/skyvern_ultra_detailed_interactive.html) • [Discord](https://discord.gg/fG2XXEuQX3) • [GitHub](https://github.com/Skyvern-AI/Skyvern) [![Diagram](https://img.shields.io/badge/Diagram-2b3137?style=flat-square)](https://repodiagrams.s3.eu-north-1.amazonaws.com/skyvern_ultra_detailed_interactive.html) [![Join](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&style=flat-square)](https://discord.gg/fG2XXEuQX3) [![Star](https://img.shields.io/badge/star-181717?logo=github&logoColor=white&style=flat-square)](https://github.com/Skyvern-AI/Skyvern)
Author
Owner

@lunaperegrina commented on GitHub (Dec 24, 2025):

I have the exact same issue

<!-- gh-comment-id:3690605450 --> @lunaperegrina commented on GitHub (Dec 24, 2025): I have the exact same issue
Author
Owner

@Hezaerd commented on GitHub (Dec 25, 2025):

Yea, the only workaround i have is to manually cast with the additional fields.. const fullUser = user as typeof user & { preferredLocale?: string };

<!-- gh-comment-id:3691613322 --> @Hezaerd commented on GitHub (Dec 25, 2025): Yea, the only workaround i have is to manually cast with the additional fields.. `const fullUser = user as typeof user & { preferredLocale?: string };`
Author
Owner

@bytaesu commented on GitHub (Feb 22, 2026):

I'm looking into this

<!-- gh-comment-id:3939803416 --> @bytaesu commented on GitHub (Feb 22, 2026): I'm looking into this
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#19195