auth schema and are declared only in packages/database/prisma/schema.prisma. better-auth reads and writes them through prismaAdapter(db, { provider: 'postgresql' }) in packages/auth/src/auth.ts. Table names are lowercase singular (user, session, member) because better-auth expects them that way.
advanced.database.generateId is false, so ids come from the Prisma @default(cuid()), not from better-auth.
createdAt and updatedAt on most auth tables have no database default. better-auth supplies the values. If you insert rows by hand, set them yourself.How auth rows link to domain rows
There is no foreign key between theauth and public schemas. The links are plain string columns:
A staff user can belong to several organizations, so one
User can map to several Coach rows, one per studio. The web lane upserts the owner’s Coach row on first request with role: 'HEAD_COACH'.
User
Table auth.user. One row per person who can sign in: studio owners, staff and trainees.
Unique constraints:
email, username, phoneNumber.
Relations: sessions, accounts, passkeys, invitations (as inviter), purchases, members, twofactors, notifications, notificationPreferences. All cascade on user delete.
Session
Table auth.session. One row per signed-in device.
Indexes: unique
token, index on userId.
Account
Table auth.account. One row per sign-in method of a user.
Index on
userId. Account linking trusts google and github as providers (trustedProviders in auth.ts). Google is the only social provider configured under socialProviders.
Verification
Table auth.verification. Short-lived values better-auth needs to check later: magic link tokens, email OTP codes, phone OTP codes, password reset tokens.
Passkey
Table auth.passkey. WebAuthn credentials from the passkey() plugin.
Indexes on
userId and credentialID.
TwoFactor
Table auth.twoFactor.
Perform wraps the plugin in
twoFactorWithOtpLanes (packages/auth/src/plugins/two-factor-otp-lanes.ts).
Organization
Table auth.organization. One row per studio account. The domain twin is Studio.
Relations:
members, invitations, purchases. All cascade on organization delete. The beforeDeleteOrganization hook calls beforeOrganizationDelete (packages/auth/src/lib/deletion-hooks.ts) before the row goes.
Member
Table auth.member. Membership of a user in an organization.
Unique on
(organizationId, userId). Indexes on each column.
Invitation
Table auth.invitation. Pending staff invites.
sendInvitationEmail sends the organizationInvitation template with a link to /login if the email already has a user, or /signup otherwise, carrying invitationId and email as query parameters.
Pending invitations count toward the team seat limit. See Billing models.
Sign-in methods
The plugins enabled inauth.ts decide which rows get written:
The delivery side of the OTP and email methods is covered in SmartSend WhatsApp and Email.
Coach web notifications
Two moreauth models belong to the web app’s in-app notification bell. They are documented with the other notification tables in Messaging and notifications: Notification and UserNotificationPreference, with the enums NotificationType and NotificationTarget.