What's the difference between additionalFields and customSession?

Hi, it's a bit unclear to me the difference between additionalFields and customSession plugin. I am trying to migrate an app using Prisma, and I have a User schema that have many other fields. My understanding is that I should add those fields under user.additionalFields in auth.ts. However, most of those fields are many to many relationships with other tables, so I cannot use the proper type in additionalFields. For example, I have a table Role, and I want to be able to retrieve roles in my session, so should I do this?
user: {
additionalFields: {
roles: {
type: "", // <- What type should I use here?
},
}
}
user: {
additionalFields: {
roles: {
type: "", // <- What type should I use here?
},
}
}
I also a Preference table, and I want to have the user preferences in the session. Should I also add the preferences in the additionalFields? That way of extending the User schema seems a bit weird to me. Otherwise, I have tried to add the customSession plugin, like this:
customSession(async ({ user, session }) => {
const dbUser = await prisma.user.findUnique({
where: {
id: user.id,
},
include: {
roles: true,
preferences: true,
subscription: true,
},
});

return {
user: {
...user,
roles: dbUser?.roles.map((role) => role.name),
preferences: {
distanceUnit: dbUser?.preferences?.distanceUnit,
elevationUnit: dbUser?.preferences?.elevationUnit,
},
subscriptionStatus: dbUser?.subscription?.status,
},
session,
};
}),
customSession(async ({ user, session }) => {
const dbUser = await prisma.user.findUnique({
where: {
id: user.id,
},
include: {
roles: true,
preferences: true,
subscription: true,
},
});

return {
user: {
...user,
roles: dbUser?.roles.map((role) => role.name),
preferences: {
distanceUnit: dbUser?.preferences?.distanceUnit,
elevationUnit: dbUser?.preferences?.elevationUnit,
},
subscriptionStatus: dbUser?.subscription?.status,
},
session,
};
}),
Which is the way I would like to store those data in my session, the problem here is that the types are not inferred unless I also add additionalFields. I guess my questions would be: How can I simply add custom fields to my session that are also inferred? Could you explain the difference between additionalFields and customSession? Is there a way to have many-to-many relationships in additionalFields?
6 Replies
bekacru
bekacru2mo ago
Additinoal fields are just fields that needs to be added in the session table directly. While custom session is a custom fuction that changes the response of /get-session this is useful if you want to query and return additinoal data whenever you call getsession. Regarding type inference, on the server it should automatically ifner it but for the client check the doc linked below. https://www.better-auth.com/docs/concepts/typescript#inferring-additional-fields-on-client
TypeScript | Better Auth
Better Auth TypeScript integration.
phoskee
phoskee2mo ago
@bekacru by doing that, additionalFields are not typed, any suggestions?
import type { BetterAuthOptions } from "better-auth";
import { expo } from "@better-auth/expo";
import { betterAuth } from "better-auth";
import { prismaAdapter } from "better-auth/adapters/prisma";
import { nextCookies } from "better-auth/next-js";
import {
admin,
customSession,
multiSession,
oAuthProxy,
twoFactor,
username,
} from "better-auth/plugins";
import { passkey } from "better-auth/plugins/passkey";

import { db } from "../../db/src/client";
import { env } from "../env";

export const config = {
database: prismaAdapter(db, {
provider: "sqlite",
}),
secret: env.AUTH_SECRET,
baseURL: "http://localhost:3000",
plugins: [
oAuthProxy(),
twoFactor(),
passkey(),
expo(),
multiSession(),
admin({
adminRoles: ["admin", "superadmin"],
}),
username(),
nextCookies(),
customSession(async ({ user, session }) => {
const permissions = await db.userPermissions.findMany({
where: {
userId: user.id,
},
});
return {
permissions,
user: {
...user,
userPermissions: permissions,
},
session,
};
}),
],
emailAndPassword: {
enabled: true,
autoSignIn: false,
},
trustedOrigins: ["exp://", "http://localhost:3000"],

user: {
additionalFields: {
surname: {
type: "string",
},
role: {
type: "string",
required: true,
},
gender: {
type: "string",
},
dateOfBirth: {
type: "date",
},
rank: {
type: "string",
},
},
},
} satisfies BetterAuthOptions;

export const auth = betterAuth(config);
export type Session = typeof auth.$Infer.Session;
import type { BetterAuthOptions } from "better-auth";
import { expo } from "@better-auth/expo";
import { betterAuth } from "better-auth";
import { prismaAdapter } from "better-auth/adapters/prisma";
import { nextCookies } from "better-auth/next-js";
import {
admin,
customSession,
multiSession,
oAuthProxy,
twoFactor,
username,
} from "better-auth/plugins";
import { passkey } from "better-auth/plugins/passkey";

import { db } from "../../db/src/client";
import { env } from "../env";

export const config = {
database: prismaAdapter(db, {
provider: "sqlite",
}),
secret: env.AUTH_SECRET,
baseURL: "http://localhost:3000",
plugins: [
oAuthProxy(),
twoFactor(),
passkey(),
expo(),
multiSession(),
admin({
adminRoles: ["admin", "superadmin"],
}),
username(),
nextCookies(),
customSession(async ({ user, session }) => {
const permissions = await db.userPermissions.findMany({
where: {
userId: user.id,
},
});
return {
permissions,
user: {
...user,
userPermissions: permissions,
},
session,
};
}),
],
emailAndPassword: {
enabled: true,
autoSignIn: false,
},
trustedOrigins: ["exp://", "http://localhost:3000"],

user: {
additionalFields: {
surname: {
type: "string",
},
role: {
type: "string",
required: true,
},
gender: {
type: "string",
},
dateOfBirth: {
type: "date",
},
rank: {
type: "string",
},
},
},
} satisfies BetterAuthOptions;

export const auth = betterAuth(config);
export type Session = typeof auth.$Infer.Session;
bekacru
bekacru2mo ago
is it not inferring the additional fields on the user table?
phoskee
phoskee2mo ago
@bekacru no, adding the customSession actually adds the userPermission object to my response, but an error is reported in the code (example:
Property 'twoFactorEnabled' does not exist on type '{ userPermissions: { id: string; role: string; userId: string; port: string; }[]; id: string; name: string; email: string; emailVerified: boolean; createdAt: Date; updatedAt: Date; image?: string | ... 1 more ... | undefined; }'.
Property 'twoFactorEnabled' does not exist on type '{ userPermissions: { id: string; role: string; userId: string; port: string; }[]; id: string; name: string; email: string; emailVerified: boolean; createdAt: Date; updatedAt: Date; image?: string | ... 1 more ... | undefined; }'.
) same for all additionalFields declared, btw
XHRGET
http://localhost:3000/api/auth/get-session
[HTTP/1.1 200 OK 96ms]


permissions [ {…} ]

user Object { id: "t0fwVlYcYF6D97fPbpOC8lDI1Cw5olXX", name: "qwe", email: "[email protected]", … }
id "t0fwVlYcYF6D97fPbpOC8lDI1Cw5olXX"
name "qwe"
emailVerified false
image ""
createdAt "2025-03-16T18:24:30.914Z"
updatedAt "2025-03-16T18:24:30.914Z"
twoFactorEnabled true
role "user"
banned null
banReason null
banExpires null
username "qweqwe"
displayUsername "qweqwe"
surname "qwe"
gender "F"
dateOfBirth "2025-03-04T00:00:00.000Z"
rank "qwe"
userPermissions [ {…} ]

session Object { id: "dCuqjiwaW0aw2Qhr6lLVNgVcw9Q3CAbx", expiresAt: "2025-03-24T21:19:14.590Z", token: "vVoMczrk5Hp6tJfSYLIdsJjKmOMK8yZt", … }
XHRGET
http://localhost:3000/api/auth/get-session
[HTTP/1.1 200 OK 96ms]


permissions [ {…} ]

user Object { id: "t0fwVlYcYF6D97fPbpOC8lDI1Cw5olXX", name: "qwe", email: "[email protected]", … }
id "t0fwVlYcYF6D97fPbpOC8lDI1Cw5olXX"
name "qwe"
emailVerified false
image ""
createdAt "2025-03-16T18:24:30.914Z"
updatedAt "2025-03-16T18:24:30.914Z"
twoFactorEnabled true
role "user"
banned null
banReason null
banExpires null
username "qweqwe"
displayUsername "qweqwe"
surname "qwe"
gender "F"
dateOfBirth "2025-03-04T00:00:00.000Z"
rank "qwe"
userPermissions [ {…} ]

session Object { id: "dCuqjiwaW0aw2Qhr6lLVNgVcw9Q3CAbx", expiresAt: "2025-03-24T21:19:14.590Z", token: "vVoMczrk5Hp6tJfSYLIdsJjKmOMK8yZt", … }
bekacru
bekacru2mo ago
yeah the issue is, it's not able to infer your additional types since both the custom session and your additional fields are defined in the same object. check the caveats section here on how to fix it https://www.better-auth.com/docs/concepts/session-management#customizing-session-response
Session Management | Better Auth
Better Auth session management.
phoskee
phoskee2mo ago
I've already tried this, but I can give it another try Should I simply move the customSession to export const auth? Found where I went wrong in caveats, thank you a lot!

Did you find this page helpful?