BA
Better Auth•2w ago
rhys

The field "id" does not exist in the "verification" schema

Hey guys, I'm trying to get Better Auth working with Directus, a headless CMS system. I'm having to hack around a little by manually creating the tables that there isn't an analog in Directus, and am having an issue with the verification table, which outputs
# SERVER_ERROR: [BetterAuthError: The field "id" does not exist in the "verification" schema. Please update your drizzle schema or re-generate using "npx @better-auth/cli generate".] {
cause: undefined
}
# SERVER_ERROR: [BetterAuthError: The field "id" does not exist in the "verification" schema. Please update your drizzle schema or re-generate using "npx @better-auth/cli generate".] {
cause: undefined
}
Introspecting my postgres db, this is the table that I've created;
export const verification = pgTable("verification", {
id: text().primaryKey().notNull(),
date_created: timestamp({ mode: 'string' }),
expires_at: timestamp({ mode: 'string' }).notNull(),
value: text().notNull(),
identifier: text(),
date_updated: timestamp({ mode: 'string' }),
});
export const verification = pgTable("verification", {
id: text().primaryKey().notNull(),
date_created: timestamp({ mode: 'string' }),
expires_at: timestamp({ mode: 'string' }).notNull(),
value: text().notNull(),
identifier: text(),
date_updated: timestamp({ mode: 'string' }),
});
Not sure what's wrong- it's complaining about the ID. How is the id created? Is it expecting it to be a uuid?
14 Replies
Ping
Ping•2w ago
Is there a specific action which you did before seeing this error? I don't believe this actually an error due to your Drizzle schema.
rhys
rhysOP•2w ago
Yeah, I created the table in the drizzle studio. The error was occurring when I tried to use the social provider login method. Directus has some of the tables already set up since it implements its own auth methods, but it's relatively primitive and doesn't go nearly as in depth, so I wanted to tack betterauth onto it and use it for client authentication. It's gotta leverage the directus methods since it uses the access token to handle permissions for requesting data via the SDK though.
Ping
Ping•2w ago
You created the table straight in drizzle studio? So is this code inside your drizzle schema?
export const verification = pgTable("verification", {
id: text().primaryKey().notNull(),
date_created: timestamp({ mode: 'string' }),
expires_at: timestamp({ mode: 'string' }).notNull(),
value: text().notNull(),
identifier: text(),
date_updated: timestamp({ mode: 'string' }),
});
export const verification = pgTable("verification", {
id: text().primaryKey().notNull(),
date_created: timestamp({ mode: 'string' }),
expires_at: timestamp({ mode: 'string' }).notNull(),
value: text().notNull(),
identifier: text(),
date_updated: timestamp({ mode: 'string' }),
});
@rhys
rhys
rhysOP•2w ago
I created the table via the drizzle studio, and then introspected it (I've got some funky stuff going on which would be destroyed if i migrated, hence why I opted to use the studio)
Ping
Ping•2w ago
Oh, interesting.
rhys
rhysOP•2w ago
It's uhh, certainly not ideal 😅
Ping
Ping•2w ago
Did you use any database hooks in your auth config?
rhys
rhysOP•2w ago
No, none. Directus uses knex and might have some hooks under the hood for the user table, but the verification table's totally divorced from it currently since there isn't a directus equivalent to my understanding
Ping
Ping•2w ago
Do you think I can see your auth & your drizzle schema files?
rhys
rhysOP•2w ago
Of course!
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { db } from "../db"
import { GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET } from "$env/static/private";
import { PUBLIC_APP_NAME } from "$env/static/public";

export const auth = betterAuth({
appName: PUBLIC_APP_NAME,
user: {
modelName: 'directus_users',
fields: {
name: 'first_name',
email: 'email',
image: 'avatar',
createdAt: 'date_created',
updatedAt: 'date_updated',

}
},
session: {
modelName: 'directus_sessions',
fields: {
id: 'token',
userId: 'user',
expiresAt: 'expires',
ipAddress: 'ip',
userAgent: 'user_agent',
token: 'token',
createdAt: 'date_created',
updatedAt: 'date_updated',
}
},
verification: {
modelName: 'verification',
fields: {
createdAt: 'date_created',
updatedAt: 'date_updated',
expiresAt: 'expires_at' ,
value: 'value',
identifier: 'identifier',
},
},
database: drizzleAdapter(db, {
schema: {
users: 'directus_users',
sessions: 'directus_sessions',
verification: 'verification',
},
provider: "pg",
}),
socialProviders: {
google: {
clientId: GOOGLE_CLIENT_ID,
clientSecret: GOOGLE_CLIENT_SECRET,
mapProfileToUser: (profile) => {
return {
firstName: profile.given_name,
lastName: profile.family_name,
image: profile.picture
};
},
}
}
});
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { db } from "../db"
import { GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET } from "$env/static/private";
import { PUBLIC_APP_NAME } from "$env/static/public";

export const auth = betterAuth({
appName: PUBLIC_APP_NAME,
user: {
modelName: 'directus_users',
fields: {
name: 'first_name',
email: 'email',
image: 'avatar',
createdAt: 'date_created',
updatedAt: 'date_updated',

}
},
session: {
modelName: 'directus_sessions',
fields: {
id: 'token',
userId: 'user',
expiresAt: 'expires',
ipAddress: 'ip',
userAgent: 'user_agent',
token: 'token',
createdAt: 'date_created',
updatedAt: 'date_updated',
}
},
verification: {
modelName: 'verification',
fields: {
createdAt: 'date_created',
updatedAt: 'date_updated',
expiresAt: 'expires_at' ,
value: 'value',
identifier: 'identifier',
},
},
database: drizzleAdapter(db, {
schema: {
users: 'directus_users',
sessions: 'directus_sessions',
verification: 'verification',
},
provider: "pg",
}),
socialProviders: {
google: {
clientId: GOOGLE_CLIENT_ID,
clientSecret: GOOGLE_CLIENT_SECRET,
mapProfileToUser: (profile) => {
return {
firstName: profile.given_name,
lastName: profile.family_name,
image: profile.picture
};
},
}
}
});
// schema.ts

export const directus_users = pgTable("directus_users", {
id: uuid().primaryKey().notNull(),
first_name: varchar({ length: 50 }),
last_name: varchar({ length: 50 }),
email: varchar({ length: 128 }),
password: varchar({ length: 255 }),
location: varchar({ length: 255 }),
title: varchar({ length: 50 }),
description: text(),
tags: json(),
avatar: uuid(),
language: varchar({ length: 255 }).default(sql`NULL`),
tfa_secret: varchar({ length: 255 }),
status: varchar({ length: 16 }).default('active').notNull(),
role: uuid(),
token: varchar({ length: 255 }),
last_access: timestamp({ withTimezone: true, mode: 'string' }),
last_page: varchar({ length: 255 }),
provider: varchar({ length: 128 }).default('default').notNull(),
external_identifier: varchar({ length: 255 }),
auth_data: json(),
email_notifications: boolean().default(true),
appearance: varchar({ length: 255 }),
theme_dark: varchar({ length: 255 }),
theme_light: varchar({ length: 255 }),
theme_light_overrides: json(),
theme_dark_overrides: json(),
short_bio: text(),
biography: text(),
hero_image: uuid(),
is_group: boolean().default(false),
social_media: json(),
date_created: timestamp({ mode: 'string' }),
date_updated: timestamp({ mode: 'string' }),
}, (table) => [
foreignKey({
columns: [table.hero_image],
foreignColumns: [directus_files.id],
name: "directus_users_hero_image_foreign"
}).onDelete("set null"),
foreignKey({
columns: [table.role],
foreignColumns: [directus_roles.id],
name: "directus_users_role_foreign"
}).onDelete("set null"),
unique("directus_users_email_unique").on(table.email),
unique("directus_users_token_unique").on(table.token),
unique("directus_users_external_identifier_unique").on(table.external_identifier),
]);


export const directus_sessions = pgTable("directus_sessions", {
token: varchar({ length: 64 }).primaryKey().notNull(),
user: uuid(),
expires: timestamp({ withTimezone: true, mode: 'string' }).notNull(),
ip: varchar({ length: 255 }),
user_agent: text(),
share: uuid(),
origin: varchar({ length: 255 }),
next_token: varchar({ length: 64 }),
}, (table) => [
foreignKey({
columns: [table.share],
foreignColumns: [directus_shares.id],
name: "directus_sessions_share_foreign"
}).onDelete("cascade"),
foreignKey({
columns: [table.user],
foreignColumns: [directus_users.id],
name: "directus_sessions_user_foreign"
}).onDelete("cascade"),
]);

export const verification = pgTable("verification", {
id: text().primaryKey().notNull(),
date_created: timestamp({ mode: 'string' }),
expires_at: timestamp({ mode: 'string' }).notNull(),
value: text().notNull(),
identifier: text(),
date_updated: timestamp({ mode: 'string' }),
});

export const account = pgTable("account", {
id: text("id").primaryKey(),
accountId: text('account_id').notNull(),
providerId: text('provider_id').notNull(),
userId: text('user_id').notNull().references(()=> directus_users.id, { onDelete: 'cascade' }),
accessToken: text('access_token'),
refreshToken: text('refresh_token'),
idToken: text('id_token'),
accessTokenExpiresAt: timestamp('access_token_expires_at'),
refreshTokenExpiresAt: timestamp('refresh_token_expires_at'),
scope: text('scope'),
password: text('password'),
createdAt: timestamp('created_at').notNull(),
updatedAt: timestamp('updated_at').notNull()
});
// schema.ts

export const directus_users = pgTable("directus_users", {
id: uuid().primaryKey().notNull(),
first_name: varchar({ length: 50 }),
last_name: varchar({ length: 50 }),
email: varchar({ length: 128 }),
password: varchar({ length: 255 }),
location: varchar({ length: 255 }),
title: varchar({ length: 50 }),
description: text(),
tags: json(),
avatar: uuid(),
language: varchar({ length: 255 }).default(sql`NULL`),
tfa_secret: varchar({ length: 255 }),
status: varchar({ length: 16 }).default('active').notNull(),
role: uuid(),
token: varchar({ length: 255 }),
last_access: timestamp({ withTimezone: true, mode: 'string' }),
last_page: varchar({ length: 255 }),
provider: varchar({ length: 128 }).default('default').notNull(),
external_identifier: varchar({ length: 255 }),
auth_data: json(),
email_notifications: boolean().default(true),
appearance: varchar({ length: 255 }),
theme_dark: varchar({ length: 255 }),
theme_light: varchar({ length: 255 }),
theme_light_overrides: json(),
theme_dark_overrides: json(),
short_bio: text(),
biography: text(),
hero_image: uuid(),
is_group: boolean().default(false),
social_media: json(),
date_created: timestamp({ mode: 'string' }),
date_updated: timestamp({ mode: 'string' }),
}, (table) => [
foreignKey({
columns: [table.hero_image],
foreignColumns: [directus_files.id],
name: "directus_users_hero_image_foreign"
}).onDelete("set null"),
foreignKey({
columns: [table.role],
foreignColumns: [directus_roles.id],
name: "directus_users_role_foreign"
}).onDelete("set null"),
unique("directus_users_email_unique").on(table.email),
unique("directus_users_token_unique").on(table.token),
unique("directus_users_external_identifier_unique").on(table.external_identifier),
]);


export const directus_sessions = pgTable("directus_sessions", {
token: varchar({ length: 64 }).primaryKey().notNull(),
user: uuid(),
expires: timestamp({ withTimezone: true, mode: 'string' }).notNull(),
ip: varchar({ length: 255 }),
user_agent: text(),
share: uuid(),
origin: varchar({ length: 255 }),
next_token: varchar({ length: 64 }),
}, (table) => [
foreignKey({
columns: [table.share],
foreignColumns: [directus_shares.id],
name: "directus_sessions_share_foreign"
}).onDelete("cascade"),
foreignKey({
columns: [table.user],
foreignColumns: [directus_users.id],
name: "directus_sessions_user_foreign"
}).onDelete("cascade"),
]);

export const verification = pgTable("verification", {
id: text().primaryKey().notNull(),
date_created: timestamp({ mode: 'string' }),
expires_at: timestamp({ mode: 'string' }).notNull(),
value: text().notNull(),
identifier: text(),
date_updated: timestamp({ mode: 'string' }),
});

export const account = pgTable("account", {
id: text("id").primaryKey(),
accountId: text('account_id').notNull(),
providerId: text('provider_id').notNull(),
userId: text('user_id').notNull().references(()=> directus_users.id, { onDelete: 'cascade' }),
accessToken: text('access_token'),
refreshToken: text('refresh_token'),
idToken: text('id_token'),
accessTokenExpiresAt: timestamp('access_token_expires_at'),
refreshTokenExpiresAt: timestamp('refresh_token_expires_at'),
scope: text('scope'),
password: text('password'),
createdAt: timestamp('created_at').notNull(),
updatedAt: timestamp('updated_at').notNull()
});
Ping
Ping•2w ago
We currently don't support using custom id fields. In your auth config, you're trying to rename id in session model to token, which we don't support yet. This is likely the cause. @rhys
rhys
rhysOP•2w ago
Ahhh yeah, I can see that. I've removed that, but it's still returning the same issue.
Ping
Ping•2w ago
In your Better auth config, you're passing the schema incorrectly for the drizzleAdapter. You should pass your entire Drizzle schema from schema.ts in. @rhys
rhys
rhysOP•2w ago
You're absolutely right! Thanks so much.

Did you find this page helpful?