Define an editable profile field across storage, server validation, sign-up, and account settings without exposing protected data.

A profile field needs a stored schema, a server input policy, and a form that exposes it. BTST Better Auth UI can render additional fields at sign-up and in account settings, but its labels and client validation do not define the server's data rules.
This guide adds an optional department field to an existing auth integration using @btst/better-auth-ui@2.0.1, @btst/stack@3.1.2, and better-auth@1.6.16. The field is user-supplied profile information. It must not determine organization membership, billing entitlements, or access permissions.
Merge this fragment into the existing server's user.additionalFields, preserving other user options and fields:
import type { BetterAuthOptions } from "better-auth";
import { z } from "zod";
export const profilePolicy = {
user: {
additionalFields: {
department: {
type: "string",
required: false,
validator: {
input: z.string().trim().max(80),
},
},
},
},
} satisfies BetterAuthOptions;
Generate and review the corresponding auth schema with the tools for your installed Better Auth version, then migrate through the adapter's normal workflow. An optional field lets existing users continue without a backfill. If you make a field required later, plan how older records and every registration path obtain a valid value first.
The pinned input parser applies the input validator when a value is supplied and uses its parsed result. This example trims surrounding whitespace and rejects values longer than 80 characters. Empty text is allowed, so a user can clear the field without submitting null.
Keep this validator synchronous. The pinned parser rejects asynchronous input validation; a database lookup belongs in an appropriate server hook or application operation. A UI-only validation callback cannot protect direct HTTP requests.
Merge these fragments into the existing Stack provider's overrides.auth and overrides.account, respectively:
import type {
AccountPluginOverrides,
AuthPluginOverrides,
} from "@btst/better-auth-ui/client";
export const profileAuthUI = {
additionalFields: {
department: {
label: "Department",
type: "string",
required: false,
placeholder: "Engineering",
instructions: "Optional; up to 80 characters.",
validate: async (value: string) => value.trim().length <= 80,
},
},
signUp: {
fields: ["name", "department"],
},
} satisfies Partial<AuthPluginOverrides>;
export const profileAccountUI = {
account: {
fields: ["name", "department"],
},
} satisfies Partial<AccountPluginOverrides>;
These arrays are the fields for this example. Retain other existing fields, such as image, if your application already exposes them. The account client plugin must be mounted for account settings, normally /p/account/settings.
Declaring an entry in additionalFields supplies its UI description. The sign-up and account fields arrays decide where that description is used. An entry missing from those arrays does not automatically appear on both screens.
The released account field card and sign-up submit handler both invoke the custom validate callback for string values before sending their mutations. That gives feedback in both forms. Retain the server length bound because a direct HTTP request bypasses both callbacks.
If application code also reads or updates the field, add Better Auth's inference plugin to the existing auth client, retaining its other plugins:
import { createAuthClient } from "better-auth/react";
import { inferAdditionalFields } from "better-auth/client/plugins";
import type { auth } from "@/lib/auth";
export const authClient = createAuthClient({
plugins: [inferAdditionalFields<typeof auth>()],
});
The import of auth is type-only. Keep the actual server module, database adapter, and secrets out of the browser bundle. For independently built client and server projects, use the upstream additional-field guidance and keep the two schemas aligned.
Inference describes the API to TypeScript. It neither migrates the database nor validates a malicious request. Inspect the authoritative user response after a save when verifying persistence; optimistic UI state alone is insufficient.
Use this pattern for editable profile data. Do not expose an administrator flag, organization role, or paid-plan status merely by adding it to a field list. Such values need server-controlled writes and permission checks at the operation that uses them.
Better Auth additional fields support input: false for data that clients must not set. Configure that protection on the server and omit the field from self-service UI. A field hidden from a form remains writable through HTTP if the server still accepts it. Conversely, input: false restricts writes; it does not by itself make a field secret or remove it from returned user data.
Use a disposable account and test the actual configured adapter:
input: false field. Confirm neither changes protected data.The fragments were type-checked, and focused in-memory handler checks cover optional registration, normalization, rejected writes, clearing, and protected-field behavior. Those checks do not run a production migration or the full UI flow. Continue with the BTST auth documentation for wiring and the auth-page customization guide for page-level presentation.