Custom User Fields
Place user data in the session, Better Auth component, JWT, or application projection deliberately.
Choose the field's source from how it is written, read, and refreshed.
Decision table
| Requirement | Store/read from |
|---|---|
| Auth-domain field managed with the user | Better Auth user/additional fields |
| Small field needed during SSR immediately | Session user when appropriate |
| Small field needed in every Convex identity | JWT claim |
| Frequently changing product profile | Application Convex projection/table |
| Role or membership used for authorization | Canonical backend data checked by the function |
Better Auth additional fields
Define the field in server Better Auth options and infer it in the client definition using the Better Auth-supported client mechanism for the pinned version. Confirm the value appears at runtime; TypeScript inference alone does not populate it.
JWT claims
The Convex plugin can define a payload:
convex({
authConfig,
jwt: {
definePayload: ({ user }) => ({
name: user.name,
image: user.image ?? undefined,
}),
},
})JWT values remain unchanged until a new token is issued. Do not use a long-lived claim as the only source for revocable permission.
Application projection
Use a projection for product-specific fields, search indexes, or joins in Convex. Mark it as derived and provide a rebuild path.
const profile = useConvexUser(
api.users.getCurrent,
{},
{
source: 'projection',
},
)The projection does not become session truth. Better Auth deletion and update flows must synchronize or rebuild it.
Module augmentation
Use augmentation only to describe runtime fields that are already guaranteed by your configured JWT/session contract. Never add a TypeScript field to make an absent runtime value appear supported.