Prisma and NextAuth: Additional fields in the `User` model

I have a system where users have to enter some additional information such username or birthday, NextAuth says that they should be defined as nullabe fields in the schema, but I don't want to, is there a way around this?
21 Replies
GBianchi
GBianchi9mo ago
This. I tried recently to have required fields for user. This would mean that I would need to override user creation function. But I could not find a way. They do have a way to call a function AFTER a user has been created. But not before, or to override how user creation works. @Aerys could you please ping me in dm if you find a fix for this? As an example: here’s how I added a workspaceId to a user upon user creation. (See events.createUser function https://github.com/dBianchii/kodix-turbo/blob/main/packages/auth/index.ts)
Aerys
Aerys9mo ago
sorry for reaching you a bit late. in the end I just had to create a separate model and a one-to-one relation to the User model. so far so good.
GBianchi
GBianchi9mo ago
Humm, but did you make this extra model optional for the user? @Aerys This is a big issue from the next-auth package. There are many things that are wrong with it. This one is a big offender IMO
Aerys
Aerys9mo ago
it's just a reference from the new model to the User model, nothing needs to be optional so true
GBianchi
GBianchi9mo ago
@Aerys do you mind sharing the schema?
Aerys
Aerys9mo ago
No description
BigLung
BigLung9mo ago
I'm gonna be 100 we have a working version of this in our code base with a custom role property but I just tried to reproduce it from my works codebase on a personal project and It wasn't working so idk if its a bug, or if our cto did some witchcraft ill ask about it and let yall know lol
GBianchi
GBianchi9mo ago
How did you do it @BigLung Could you share?
BigLung
BigLung9mo ago
As I said I don't know how my team did it because I'm not the one who got it working when I tried to reproduce it on a new t3 app it didn't work though Waiting to see if someone did an extra config or something
Josh
Josh9mo ago
you can fully override the adapters pretty easily i personally found using a fully custom adapter was the best solution when you want a custom user table
Josh
Josh9mo ago
GitHub
t3-app-dir/src/server/auth.ts at main · GentikSolm/t3-app-dir
t3 app dir boilerplate. Contribute to GentikSolm/t3-app-dir development by creating an account on GitHub.
Josh
Josh9mo ago
in my example i didnt want nextauth to automatically fill the username field or whatever it was since i wanted to have the user choose that after they initially login if you look at the db schema file you can see i have a bunch of custom fields on there
GBianchi
GBianchi9mo ago
@Josh but I see you are not using notNull() in these custom user fields for your schema. This makes it so that the fields in user are optional. Which is not what we want. Yes?
Josh
Josh9mo ago
thats what i want but if you want them to be not null, just add notNull and then add them to the create statement
GBianchi
GBianchi9mo ago
I don’t think that works. I tried. It throws an error before reaching the createUser adapter
Josh
Josh9mo ago
"it throws error" does not help, what error did it throw, and what code did you use so that it was erroring What's most likely happening is that when you're not using a full custom adapter, the first thing it tries to do is fetch the user from the db to see if they exist. If you're using custom fields, this will fail unless you also overwrite your get user adapter. This is because under the hood next auth is using an orm that is trying to reference tables that do not exist. Orms will generally do some sort of "select table.column1, table.col2, ... etc" instead of "select *"
GBianchi
GBianchi9mo ago
I’ll try it again and report back Hi @Josh , thank you for sharing your repo. I managed to fix it! Here's what I did:
function CustomPrismaAdapter(p: typeof prisma): Adapter {
return {
...PrismaAdapter(p),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
async createUser(data): Promise<any> {
const user = await p.user.create({
data: {
...data,
activeWorkspace: {
create: {
name: `${data.name ?? ""}'s Workspace`,
},
},
},
});

await p.workspace.update({
where: {
id: user.activeWorkspaceId,
},
data: {
users: {
connect: {
id: user.id,
},
},
},
});

return user;
},
};
}

export const {
handlers: { GET, POST },
auth,
CSRF_experimental,
} = NextAuth({
adapter: {
...CustomPrismaAdapter(prisma),
},
callbacks: {
async session({ session, user }) {
const activeWs = await prisma.workspace.findFirstOrThrow({
where: {
activeUsers: {
some: {
id: user.id,
},
},
},
select: {
name: true,
},
});
session.user.activeWorkspaceName = activeWs.name;
session.user.activeWorkspaceId = (user as User).activeWorkspaceId;

return session;
},
///...rest
});
function CustomPrismaAdapter(p: typeof prisma): Adapter {
return {
...PrismaAdapter(p),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
async createUser(data): Promise<any> {
const user = await p.user.create({
data: {
...data,
activeWorkspace: {
create: {
name: `${data.name ?? ""}'s Workspace`,
},
},
},
});

await p.workspace.update({
where: {
id: user.activeWorkspaceId,
},
data: {
users: {
connect: {
id: user.id,
},
},
},
});

return user;
},
};
}

export const {
handlers: { GET, POST },
auth,
CSRF_experimental,
} = NextAuth({
adapter: {
...CustomPrismaAdapter(prisma),
},
callbacks: {
async session({ session, user }) {
const activeWs = await prisma.workspace.findFirstOrThrow({
where: {
activeUsers: {
some: {
id: user.id,
},
},
},
select: {
name: true,
},
});
session.user.activeWorkspaceName = activeWs.name;
session.user.activeWorkspaceId = (user as User).activeWorkspaceId;

return session;
},
///...rest
});
I created this CustomPrismaAdapter that returns Adapter and takes in my prisma object. I use this to override custom adapters. I had to use // eslint-disable-next-line @typescript-eslint/no-explicit-any in async createUser result.. but other than that, looks good!
Josh
Josh9mo ago
Iirc I had some errors too with eslint, the solution was going to nextauths source code and directly copying the entire adapter and then just changing what I needed Unironically it's actually way easier to see what's going on and change stuff with a fully custom adapter The one in my repo is my modification of the drizzle orm adapter
GBianchi
GBianchi9mo ago
Yes, it's very much more visible and can help you uderstand what is going on with Next-Auth behind the scenes I just feel like they should have streamlined this process way more for users. It's stupid that I have to go through this to adapt the adapter Or, at least have a section of it in the docs
Josh
Josh9mo ago
Agreed I may write a blog post on this actually
GBianchi
GBianchi9mo ago
Id like to see that @Josh