Extending User Names
Better Auth uses name as its standard display-name field. Keep this field for compatibility with auth APIs, email templates, and plugins.
Some applications need separate first and last names. This guide adds firstName and lastName while keeping name as the display name.
1. Add the database fields
Add both fields to the user table in src/config/schema.ts:
export const user = sqliteTable('user', {
id: text('id').primaryKey(),
name: text('name').notNull(),
firstName: text('firstName'),
lastName: text('lastName'),
// Keep the other fields.
});Nullable fields let this migration run when users already exist. Better Auth will require both values for new registrations.
Generate and apply the migration:
npm run db:generate
npm run db:migrate:localReview the generated SQL before you apply it to a remote database.
2. Tell Better Auth about the fields
Add one shared extension function to src/config/auth.ts:
import type { BetterAuthOptions } from 'better-auth';
export function extendAuthOptions(
options: BetterAuthOptions,
): BetterAuthOptions {
return {
...options,
user: {
...options.user,
additionalFields: {
...options.user?.additionalFields,
firstName: {
type: 'string',
required: true,
input: true,
},
lastName: {
type: 'string',
required: true,
input: true,
},
},
},
};
}Pass extendAuthOptions to every createAuthFromEnv() call:
import { authConfig, extendAuthOptions } from '@/config/auth';
const auth = createAuthFromEnv({
// Keep the existing values.
authConfig,
extendOptions: extendAuthOptions,
});Make this change in these files:
src/middleware.tssrc/pages/api/auth/[...all].ts
The API call accepts the fields. The middleware call returns the fields with each loaded user.
Add an application user type near the top of src/env.d.ts:
type ApplicationUser = import('@vergekit/core/auth').AppAuthUser & {
firstName?: string | null;
lastName?: string | null;
};Use ApplicationUser for both user properties in App.Locals:
interface Locals {
user: ApplicationUser | null;
// Keep the other fields.
loadAuthSession: () => Promise<{
user: ApplicationUser;
session: import('@vergekit/core/auth').AppAuthSession;
} | null>;
}Read the Better Auth guide for additional user fields.
3. Change the registration form
Replace the name field in src/pages/register.astro with these fields:
<Field>
<FieldLabel for="register-first-name">First name</FieldLabel>
<Input
id="register-first-name"
name="firstName"
autocomplete="given-name"
required
/>
</Field>
<Field>
<FieldLabel for="register-last-name">Last name</FieldLabel>
<Input
id="register-last-name"
name="lastName"
autocomplete="family-name"
required
/>
</Field>
<input id="register-name" name="name" type="hidden" />
<script>
const firstName = document.querySelector<HTMLInputElement>(
'#register-first-name',
);
const lastName = document.querySelector<HTMLInputElement>(
'#register-last-name',
);
const name = document.querySelector<HTMLInputElement>('#register-name');
const syncName = () => {
if (firstName && lastName && name) {
name.value = `${firstName.value} ${lastName.value}`.trim();
}
};
firstName?.addEventListener('input', syncName);
lastName?.addEventListener('input', syncName);
document
.querySelector<HTMLFormElement>('form[data-auth-form]')
?.addEventListener('submit', syncName, { capture: true });
</script>The shared auth form code sends all three values to Better Auth.
4. Change user displays and editors
Use one fallback while old users still have empty fields:
const displayName =
[user?.firstName, user?.lastName].filter(Boolean).join(' ') ||
user?.name ||
user?.email ||
'Authenticated user';Update user tables, profile forms, and administrator forms. Include both fields in each Drizzle selection that needs them.
The current boilerplate also uses name in src/pages/dashboard.astro and cli/init-admin.ts. Keep name as the derived display name.
In cli/init-admin.ts, collect both names and add both columns to the user insert. Build name from the two values.
Find the remaining uses:
rg -n 'user\.name|userTable\.name|name:' src cli tests5. Migrate existing users
Add first and last names to existing users through an administrator form or a reviewed migration. Do not split old names automatically.