defining the user.update.before hook will cause a TypeError #2259

Closed
opened 2026-03-13 09:39:14 -05:00 by GiteaMirror · 4 comments
Owner

Originally created by @michidk on GitHub (Nov 5, 2025).

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

Define

    databaseHooks: {
        user: {
            update: {
                before: async (user) => {
                },
            },
        },
    },

set overrideUserInfoOnSignIn: true

and you will get

2025-11-05T09:22:06.028Z ERROR [Better Auth]: TypeError TypeError: Cannot read properties of undefined (reading 'name')
    at ignore-listed frames
# SERVER_ERROR:  TypeError: Cannot read properties of undefined (reading 'name')
    at ignore-listed frames

Current vs. Expected behavior

I expected no error to happen as no logic is executed

What version of Better Auth are you using?

1.3.34

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": 16,
    "cpuModel": "12th Gen Intel(R) Core(TM) i5-12500H",
    "totalMemory": "15.46 GB",
    "freeMemory": "6.29 GB"
  },
  "node": {
    "version": "v22.19.0",
    "env": "development"
  },
  "packageManager": {
    "name": "bun",
    "version": "1.3.0"
  },
  "frameworks": [
    {
      "name": "next",
      "version": "16.0.1"
    },
    {
      "name": "react",
      "version": "19.2.0"
    }
  ],
  "databases": [
    {
      "name": "drizzle",
      "version": "0.44.7"
    },
    {
      "name": "@neondatabase/serverless",
      "version": "1.0.2"
    }
  ],
  "betterAuth": {
    "version": "1.3.34",
    "config": null
  }
}

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

Backend

Auth config (if applicable)

import "server-only";

import { authSchemaTables } from "@stabl-energy/sbc-device-common";
import { type BetterAuthOptions, betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { nextCookies } from "better-auth/next-js";
import { admin, organization } from "better-auth/plugins";
import siteConfig from "@/config/client";
import { db } from "@/drizzle";
import { env } from "@/env/server.mjs";
import { getServerUrl } from "@/lib/utils";
import { syncCognitoUser } from "../cognito";
import { orgAc, orgRoles } from "../permissions/organization";
import { userAc, userRoles } from "../permissions/user";
import { oAuthProxy as customOAuthProxy } from "../plugins/oauth-proxy";
import { cookiePrefix } from "./shared";

const SECONDS_TO_DAYS = 60 * 60 * 24;
const SESSION_DURATION = SECONDS_TO_DAYS * 5;

export const config = {
	appName: siteConfig.siteName,
	secret: env.BETTER_AUTH_SECRET,
	baseURL: getServerUrl(),
	trustedOrigins: [
		"http://localhost:3000",
		"<redacted>"
	],
	database: drizzleAdapter(db, {
		provider: "pg",
		schema: {
			...authSchemaTables,
		},
		usePlural: true,
	}),
	session: {
		expiresIn: SESSION_DURATION,
		updateAge: SECONDS_TO_DAYS * 1, // 1 day
		// caches for `authClient.getSession()` calls
		cookieCache: {
			enabled: true,
			maxAge: SESSION_DURATION, // should match `expiresIn`
		},
	},
	advanced: {
		cookiePrefix,
	},
	socialProviders: {
		cognito: {
			clientId: env.COGNITO_CLIENT_ID,
			clientSecret: env.COGNITO_CLIENT_SECRET,
			domain: env.COGNITO_DOMAIN,
			region: env.AWS_REGION,
			userPoolId: env.COGNITO_USERPOOL_ID,
			redirectURI: `${env.AUTH_REDIRECT_PROXY_BASE_URL || getServerUrl()}/api/auth/callback/cognito`,
			scope: ["openid", "email", "profile"],
			overrideUserInfoOnSignIn: true,
			mapProfileToUser: async (profile) => {
				console.log("mapProfileToUser");
				// Create a clean copy of userInfo without mapped fields and token claims
				const userInfo = { ...profile } as Record<string, unknown>;

				// Remove fields that are already mapped to user schema
				delete userInfo.sub;
				delete userInfo.email;
				delete userInfo.email_verified;
				delete userInfo.emailVerified;

				// Remove token claims — not useful to persist
				delete userInfo.at_hash;
				delete userInfo.iss;
				delete userInfo.aud;
				delete userInfo.token_use;
				delete userInfo.auth_time;
				delete userInfo.exp;
				delete userInfo.iat;
				delete userInfo.jti;
				delete userInfo.origin_jti;

				return {
					id: profile.sub,
					email: profile.email,
					emailVerified: true,
					image: null,
					name:
						profile.name || profile.given_name || profile.preferred_username,
					userInfo,
				};
			},
		},
	},
	user: {
		additionalFields: {
			userInfo: { type: "json", required: false },
		},
	},
	databaseHooks: {
		user: {
			update: {
				before: async (user) => {
				},
			},
		},
	},
	plugins: [
		customOAuthProxy(),
		admin({
			ac: userAc,
			roles: userRoles,
		}),
		organization({
			ac: orgAc,
			roles: orgRoles,
		}),
		nextCookies(), // make sure this is the last plugin in the array - it enables auth in server actions
	],
} satisfies BetterAuthOptions;

export const auth = betterAuth(config);

Additional context

No response

Originally created by @michidk on GitHub (Nov 5, 2025). ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce Define ```typescript databaseHooks: { user: { update: { before: async (user) => { }, }, }, }, ``` set `overrideUserInfoOnSignIn: true` and you will get ``` 2025-11-05T09:22:06.028Z ERROR [Better Auth]: TypeError TypeError: Cannot read properties of undefined (reading 'name') at ignore-listed frames # SERVER_ERROR: TypeError: Cannot read properties of undefined (reading 'name') at ignore-listed frames ``` ### Current vs. Expected behavior I expected no error to happen as no logic is executed ### What version of Better Auth are you using? 1.3.34 ### 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": 16, "cpuModel": "12th Gen Intel(R) Core(TM) i5-12500H", "totalMemory": "15.46 GB", "freeMemory": "6.29 GB" }, "node": { "version": "v22.19.0", "env": "development" }, "packageManager": { "name": "bun", "version": "1.3.0" }, "frameworks": [ { "name": "next", "version": "16.0.1" }, { "name": "react", "version": "19.2.0" } ], "databases": [ { "name": "drizzle", "version": "0.44.7" }, { "name": "@neondatabase/serverless", "version": "1.0.2" } ], "betterAuth": { "version": "1.3.34", "config": null } } ``` ### Which area(s) are affected? (Select all that apply) Backend ### Auth config (if applicable) ```typescript import "server-only"; import { authSchemaTables } from "@stabl-energy/sbc-device-common"; import { type BetterAuthOptions, betterAuth } from "better-auth"; import { drizzleAdapter } from "better-auth/adapters/drizzle"; import { nextCookies } from "better-auth/next-js"; import { admin, organization } from "better-auth/plugins"; import siteConfig from "@/config/client"; import { db } from "@/drizzle"; import { env } from "@/env/server.mjs"; import { getServerUrl } from "@/lib/utils"; import { syncCognitoUser } from "../cognito"; import { orgAc, orgRoles } from "../permissions/organization"; import { userAc, userRoles } from "../permissions/user"; import { oAuthProxy as customOAuthProxy } from "../plugins/oauth-proxy"; import { cookiePrefix } from "./shared"; const SECONDS_TO_DAYS = 60 * 60 * 24; const SESSION_DURATION = SECONDS_TO_DAYS * 5; export const config = { appName: siteConfig.siteName, secret: env.BETTER_AUTH_SECRET, baseURL: getServerUrl(), trustedOrigins: [ "http://localhost:3000", "<redacted>" ], database: drizzleAdapter(db, { provider: "pg", schema: { ...authSchemaTables, }, usePlural: true, }), session: { expiresIn: SESSION_DURATION, updateAge: SECONDS_TO_DAYS * 1, // 1 day // caches for `authClient.getSession()` calls cookieCache: { enabled: true, maxAge: SESSION_DURATION, // should match `expiresIn` }, }, advanced: { cookiePrefix, }, socialProviders: { cognito: { clientId: env.COGNITO_CLIENT_ID, clientSecret: env.COGNITO_CLIENT_SECRET, domain: env.COGNITO_DOMAIN, region: env.AWS_REGION, userPoolId: env.COGNITO_USERPOOL_ID, redirectURI: `${env.AUTH_REDIRECT_PROXY_BASE_URL || getServerUrl()}/api/auth/callback/cognito`, scope: ["openid", "email", "profile"], overrideUserInfoOnSignIn: true, mapProfileToUser: async (profile) => { console.log("mapProfileToUser"); // Create a clean copy of userInfo without mapped fields and token claims const userInfo = { ...profile } as Record<string, unknown>; // Remove fields that are already mapped to user schema delete userInfo.sub; delete userInfo.email; delete userInfo.email_verified; delete userInfo.emailVerified; // Remove token claims — not useful to persist delete userInfo.at_hash; delete userInfo.iss; delete userInfo.aud; delete userInfo.token_use; delete userInfo.auth_time; delete userInfo.exp; delete userInfo.iat; delete userInfo.jti; delete userInfo.origin_jti; return { id: profile.sub, email: profile.email, emailVerified: true, image: null, name: profile.name || profile.given_name || profile.preferred_username, userInfo, }; }, }, }, user: { additionalFields: { userInfo: { type: "json", required: false }, }, }, databaseHooks: { user: { update: { before: async (user) => { }, }, }, }, plugins: [ customOAuthProxy(), admin({ ac: userAc, roles: userRoles, }), organization({ ac: orgAc, roles: orgRoles, }), nextCookies(), // make sure this is the last plugin in the array - it enables auth in server actions ], } satisfies BetterAuthOptions; export const auth = betterAuth(config); ``` ### Additional context _No response_
GiteaMirror added the bug label 2026-03-13 09:39:14 -05:00
Author
Owner

@better-auth-agent[bot] commented on GitHub (Nov 5, 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 5, 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 5, 2025):

This is a known issue in Better Auth v1.3.34 when using Drizzle and a custom mapProfileToUser function with overrideUserInfoOnSignIn enabled. The root cause is that custom fields returned from mapProfileToUser must exactly match the property names defined in your Drizzle schema (usually camelCase, like userInfo), not the physical database column names (like user_info). If the keys don't match, the user object passed to the update flow (and your hook) can be undefined or missing properties, leading to TypeErrors when properties like name are accessed—even if your hook is empty. This is not caused by the hook itself, but by a schema mapping mismatch upstream in the OAuth flow see discussion.

To fix: ensure your mapProfileToUser returns custom fields with keys that match your Drizzle schema exactly (e.g., userInfo, not user_info). If you can't upgrade, double-check your schema and mapping for exact matches. Enabling debugLogs: true in your drizzleAdapter config can help you spot mapping issues.

This is suited for GitHub as it is a real bug/limitation in the mapping logic and has been discussed and patched in recent PRs see PR.

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 5, 2025): <!-- Answer --> This is a known issue in Better Auth v1.3.34 when using Drizzle and a custom mapProfileToUser function with overrideUserInfoOnSignIn enabled. The root cause is that custom fields returned from mapProfileToUser must exactly match the property names defined in your Drizzle schema (usually camelCase, like userInfo), not the physical database column names (like user_info). If the keys don't match, the user object passed to the update flow (and your hook) can be undefined or missing properties, leading to TypeErrors when properties like name are accessed—even if your hook is empty. This is not caused by the hook itself, but by a schema mapping mismatch upstream in the OAuth flow [see discussion](https://github.com/better-auth/better-auth/issues/5662). To fix: ensure your mapProfileToUser returns custom fields with keys that match your Drizzle schema exactly (e.g., userInfo, not user_info). If you can't upgrade, double-check your schema and mapping for exact matches. Enabling debugLogs: true in your drizzleAdapter config can help you spot mapping issues. This is suited for GitHub as it is a real bug/limitation in the mapping logic and has been discussed and patched in recent PRs [see PR](https://github.com/better-auth/better-auth/pull/5681). <!-- 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/54815e4b-2c6a-4a02-afc0-ed4e9420cc8d?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/54815e4b-2c6a-4a02-afc0-ed4e9420cc8d?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/54815e4b-2c6a-4a02-afc0-ed4e9420cc8d?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/54815e4b-2c6a-4a02-afc0-ed4e9420cc8d?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/54815e4b-2c6a-4a02-afc0-ed4e9420cc8d?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/54815e4b-2c6a-4a02-afc0-ed4e9420cc8d?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/54815e4b-2c6a-4a02-afc0-ed4e9420cc8d?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/5771)
Author
Owner

@Bekacru commented on GitHub (Nov 5, 2025):

You should use createAuthMiddleware

@Bekacru commented on GitHub (Nov 5, 2025): You should use `createAuthMiddleware`
Author
Owner

@lohrm-stabl commented on GitHub (Nov 6, 2025):

@Bekacru so it's intended that this code:

    databaseHooks: {
        user: {
            update: {
                before: async (user) => {
                },
            },
        },
    },

gives an error?

@lohrm-stabl commented on GitHub (Nov 6, 2025): @Bekacru so it's intended that this code: ```typescript databaseHooks: { user: { update: { before: async (user) => { }, }, }, }, ``` gives an error?
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#2259