email verification token not storing in table

For whatever reason when a new user signs up on my application, the email verification token sent to the user is not being stored in the database. If a user does password reset it stores this token just fine. I am not sure when or if it ever worked properly but I am certain that the table is hooked up correct as there are other better-auth endpoints that work as intended. I can provide any code needed to fix this.
Solution:
@Sean I just opened pull request https://github.com/better-auth/better-auth/pull/3912 that change that behavior and introduce displayUsernameValidator that is disconnected of usernameValidator
GitHub
fix(username): remove normalize transform for displayUsername by os...
fix(username): remove normalize transform for displayUsername feat(username): add displayUsernameValidator This pull request restores the expected behavior of the username plugin. Before: It was no...
Jump to solution
14 Replies
Krishna B
Krishna B2mo ago
I am too facing the same issue. Using "better-auth": "^1.3.4".
Sean
SeanOP2mo ago
I’m glad to see I’m not the only one. I’m on 1.2.12 @Krishna B they may have switched to a stateless approach in 1.2 at some point
Ping
Ping2mo ago
Hey, mind sharing your auth config?
Sean
SeanOP2mo ago
absolutely
emailAndPassword: {
enabled: true,
requireEmailVerification: true,
minPasswordLength: 8,
maxPasswordLength: 128,
sendResetPassword: async ({ user, url }) => {
await sendEmail({
to: user.email,
subject: "Reset your password!",
body: `Click the link to reset your password: ${url}`,
});
},
resetPasswordTokenExpiresIn: 3600,
},
emailVerification: {
sendVerificationEmail: async ({ user, url }) => {
await sendEmail({
to: user.email,
subject: "Verify your email!",
body: `Click the verify your email: ${url}`,
});
},
sendOnSignUp: true,
expiresIn: 3600,
},
emailAndPassword: {
enabled: true,
requireEmailVerification: true,
minPasswordLength: 8,
maxPasswordLength: 128,
sendResetPassword: async ({ user, url }) => {
await sendEmail({
to: user.email,
subject: "Reset your password!",
body: `Click the link to reset your password: ${url}`,
});
},
resetPasswordTokenExpiresIn: 3600,
},
emailVerification: {
sendVerificationEmail: async ({ user, url }) => {
await sendEmail({
to: user.email,
subject: "Verify your email!",
body: `Click the verify your email: ${url}`,
});
},
sendOnSignUp: true,
expiresIn: 3600,
},
let me know if oyu need more than this snippet. it wont let me send the full config in 1 message
Ping
Ping2mo ago
Yeah it should send as a file, that would be better, thanks
Sean
SeanOP2mo ago
Ty for looking at it for me!
Ping
Ping2mo ago
In the drizzle adapter options, there should be a debugLogs option, enable that and run through the flow again, then try to spot what occurs when it attempts to add a value to DB for the verification table.
Sean
SeanOP5w ago
sorry it took so long, when I generate a new user it created models for account and user but not for the token tables Also still haven't figure out where/why my displayUsername keeps being normalized as well. It seems that its fine until it hits the drizzle adapter then its all lowercased when its received
[Better Auth]: [Drizzle Adapter] #2 [1/4] create (Unsafe Input): { model: 'user',
data:
{ createdAt: 2025-08-10T14:09:31.485Z,
updatedAt: 2025-08-10T14:09:31.485Z,
emailVerified: false,
email: 'contact@tierforge.com',
name: 'TierForge',
image: 'https://cdn.tierforge.com/avatars/2b7cb4f9-366a-4e05-b132-0bf514deae3a.png',
username: 'tierforge',
displayUsername: 'tier forge' } }
[Better Auth]: [Drizzle Adapter] #2 [1/4] create (Unsafe Input): { model: 'user',
data:
{ createdAt: 2025-08-10T14:09:31.485Z,
updatedAt: 2025-08-10T14:09:31.485Z,
emailVerified: false,
email: 'contact@tierforge.com',
name: 'TierForge',
image: 'https://cdn.tierforge.com/avatars/2b7cb4f9-366a-4e05-b132-0bf514deae3a.png',
username: 'tierforge',
displayUsername: 'tier forge' } }
INFO [Better Auth]: [Drizzle Adapter] #2 [2/4] create (Parsed Input): { model: 'user',
data:
{ name: 'TierForge',
email: 'contact@tierforge.com',
emailVerified: false,
image: 'https://cdn.tierforge.com/avatars/2b7cb4f9-366a-4e05-b132-0bf514deae3a.png',
createdAt: 2025-08-10T14:09:31.485Z,
updatedAt: 2025-08-10T14:09:31.485Z,
username: 'tierforge',
displayUsername: 'tier forge',
id: undefined } }
INFO [Better Auth]: [Drizzle Adapter] #2 [2/4] create (Parsed Input): { model: 'user',
data:
{ name: 'TierForge',
email: 'contact@tierforge.com',
emailVerified: false,
image: 'https://cdn.tierforge.com/avatars/2b7cb4f9-366a-4e05-b132-0bf514deae3a.png',
createdAt: 2025-08-10T14:09:31.485Z,
updatedAt: 2025-08-10T14:09:31.485Z,
username: 'tierforge',
displayUsername: 'tier forge',
id: undefined } }
INFO [Better Auth]: [Drizzle Adapter] #2 [3/4] create (DB Result): { model: 'user',
res:
{ id: '83c3a87d-7b8a-4528-b5f6-3a2c42815f7d',
name: 'TierForge',
email: 'contact@tierforge.com',
emailVerified: false,
image: 'https://cdn.tierforge.com/avatars/2b7cb4f9-366a-4e05-b132-0bf514deae3a.png',
createdAt: 2025-08-10T14:09:31.485Z,
updatedAt: 2025-08-10T14:09:31.485Z,
username: 'tierforge',
displayUsername: 'tier forge',
isUserVerified: false } }
INFO [Better Auth]: [Drizzle Adapter] #2 [3/4] create (DB Result): { model: 'user',
res:
{ id: '83c3a87d-7b8a-4528-b5f6-3a2c42815f7d',
name: 'TierForge',
email: 'contact@tierforge.com',
emailVerified: false,
image: 'https://cdn.tierforge.com/avatars/2b7cb4f9-366a-4e05-b132-0bf514deae3a.png',
createdAt: 2025-08-10T14:09:31.485Z,
updatedAt: 2025-08-10T14:09:31.485Z,
username: 'tierforge',
displayUsername: 'tier forge',
isUserVerified: false } }
[Better Auth]: [Drizzle Adapter] #2 [4/4] create (Parsed Result): { model: 'user',
data:
{ name: 'TierForge',
email: 'contact@tierforge.com',
emailVerified: false,
image: 'https://cdn.tierforge.com/avatars/2b7cb4f9-366a-4e05-b132-0bf514deae3a.png',
createdAt: 2025-08-10T14:09:31.485Z,
updatedAt: 2025-08-10T14:09:31.485Z,
username: 'tierforge',
displayUsername: 'tier forge',
id: '83c3a87d-7b8a-4528-b5f6-3a2c42815f7d' } }
[Better Auth]: [Drizzle Adapter] #2 [4/4] create (Parsed Result): { model: 'user',
data:
{ name: 'TierForge',
email: 'contact@tierforge.com',
emailVerified: false,
image: 'https://cdn.tierforge.com/avatars/2b7cb4f9-366a-4e05-b132-0bf514deae3a.png',
createdAt: 2025-08-10T14:09:31.485Z,
updatedAt: 2025-08-10T14:09:31.485Z,
username: 'tierforge',
displayUsername: 'tier forge',
id: '83c3a87d-7b8a-4528-b5f6-3a2c42815f7d' } }
the username is particularly weird,
console.log("display username", displayUsername.trim());
const { data, error } = await authClient.signUp.email({
email,
password,
name: username.trim(),
username: username.trim(),
displayUsername: displayUsername.trim(),
image: safeImage,
fetchOptions: {
headers,
},
});
console.log("display username", displayUsername.trim());
const { data, error } = await authClient.signUp.email({
email,
password,
name: username.trim(),
username: username.trim(),
displayUsername: displayUsername.trim(),
image: safeImage,
fetchOptions: {
headers,
},
});
this snippets console.log shows the displayname with capitalization but when it hits the actual better-auth api it normalizes it. I don't believe this has always occured as I have some users with unnormalized names Im not sure if it was when I updated better-auth with nuxt4's upgrade a month ago I tried upgrading to 1.3.4 and both issues still occur it should still store this token in the database no? I guess no where in sendVerificationEmailFn does it store a token like it does in password-reset.ts which was generating tokens into the db as expected thats interesting that they store it for update-user and password-reset but not email-verification. I guess this is my code is technically working as intended then? I need to make a custom JWT hook then to make it more secure or atleast encrypt the email on verification. I see that in the username schema on better-auth it seems to be normalizing the display username. Is there a way to disable this or edit it in the auth config?
displayUsername: {
type: "string",
required: false,
transform: {
input(value) {
return value == null ? value : normalizer(value as string);
},
},
},
displayUsername: {
type: "string",
required: false,
transform: {
input(value) {
return value == null ? value : normalizer(value as string);
},
},
},
Ping
Ping5w ago
You can pass the normalizer config straight in the username plugin config
Sean
SeanOP5w ago
I only want to unnormalize the displayUsername. I'd like to keep usernames normalized is the issue
Solution
#Oskar
#Oskar5w ago
@Sean I just opened pull request https://github.com/better-auth/better-auth/pull/3912 that change that behavior and introduce displayUsernameValidator that is disconnected of usernameValidator
GitHub
fix(username): remove normalize transform for displayUsername by os...
fix(username): remove normalize transform for displayUsername feat(username): add displayUsernameValidator This pull request restores the expected behavior of the username plugin. Before: It was no...
dun
dun6d ago
@Sean @Ping So, in conclusion, when a user registers, will a verification code be generated in the database or not? I also tried it, and when registering, it didn't add anything to the verification table. However, the verification code sent can verify the user.
Sean
SeanOP5d ago
It never updates, i need to look into the source code and find why. I wonder if it’s either a bug or the intended use. I’d love to be able to look up tokens to verify them rather than the current use case of just sending a jwt token with their email. Doesn’t seem secure

Did you find this page helpful?