CtrlK
BlogDocsLog inGet started
Tessl Logo

pleaseai/better-auth

Better Auth authentication framework skills for JavaScript/TypeScript projects

72

Quality

91%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Passed

No findings from the security scan

Overview
Quality
Evals
Security
Files

databases.mdskills/use-better-auth/references/

Database Adapters

Verified against $(ask src better-auth)/packages/better-auth/src/adapters/ and $(ask src better-auth)/docs/content/docs/adapters/. Confirm the current list before writing imports:

SRC=$(ask src better-auth)
ls "$SRC/packages/better-auth/src/adapters"      # drizzle-adapter, kysely-adapter, memory-adapter, mongodb-adapter, prisma-adapter
ls "$SRC/packages" | grep adapter                 # authoritative package list

Two routes are supported:

  1. Built-in Kysely dialect — pass a raw database pool / driver directly via database:. Only for SQLite / Postgres / MySQL / MSSQL. No external package required. Migrations run with npx auth migrate.
  2. External ORM adapter — wrap an ORM instance (Prisma / Drizzle / Mongo) in the matching Adapter function. Migrations are owned by the ORM; better-auth only generates the schema snippet.

Option 1 — Built-in Kysely (zero-ORM)

Postgres

import { betterAuth } from "better-auth";
import { Pool } from "pg";

export const auth = betterAuth({
  database: new Pool({ connectionString: process.env.DATABASE_URL }),
});

MySQL / MSSQL

Follow the same pattern with mysql2/promise or tedious. See docs/content/docs/adapters/{mysql,mssql}.mdx for the exact dialect import.

SQLite

import Database from "better-sqlite3";

export const auth = betterAuth({
  database: new Database("database.sqlite"),
});

Also supports libsql, node:sqlite (Node 22+ native), and bun:sqlite — see the SQLite adapter doc for the dialect shim per driver.

Apply migrations after config changes:

npx auth migrate

Option 2 — Prisma

import { betterAuth } from "better-auth";
import { prismaAdapter } from "better-auth/adapters/prisma";
import { PrismaClient } from "@prisma/client";

const prisma = new PrismaClient();

export const auth = betterAuth({
  database: prismaAdapter(prisma, { provider: "sqlite" }), // "postgresql" | "mysql" | "mongodb" | ...
});

Prisma 7 caveat: if schema.prisma sets a custom output path, import PrismaClient from that path, not @prisma/client. The adapter cannot introspect a mis-aimed client.

Schema flow

Prisma owns migrations. Better-auth generates the schema snippet:

npx auth generate       # writes / updates prisma schema fragments
npx prisma migrate dev  # prisma owns the actual migration

Option 3 — Drizzle

import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { db } from "./database";

export const auth = betterAuth({
  database: drizzleAdapter(db, { provider: "pg" }), // "pg" | "mysql" | "sqlite"
});

Provider values are pg / mysql / sqlite — different from Prisma's postgresql. The generator remaps automatically; match whichever value appears in the Drizzle config.

Schema flow

npx auth generate                # writes / updates drizzle schema files
pnpm drizzle-kit generate        # drizzle owns the migration step
pnpm drizzle-kit migrate

Option 4 — Kysely

import { kyselyAdapter } from "better-auth/adapters/kysely";

export const auth = betterAuth({
  database: kyselyAdapter(db, { type: "postgres" }),
});

Useful when an existing Kysely instance is already shared across the app. Otherwise the built-in Kysely dialect in Option 1 is simpler.

Option 5 — MongoDB

import { betterAuth } from "better-auth";
import { MongoClient } from "mongodb";
import { mongodbAdapter } from "better-auth/adapters/mongodb";

const client = new MongoClient("mongodb://localhost:27017/app");
const db = client.db();

export const auth = betterAuth({
  database: mongodbAdapter(db, { client }), // client is optional but required for transactions
});

MongoDB does not have a migration step — collections are created lazily. Since v1.4.0, the adapter supports joins via experimental.joins: true, which speeds up /get-session and /get-full-organization by 2–3×.

Option 6 — Memory (tests only)

import { memoryAdapter } from "better-auth/adapters/memory";

const store = {};
export const auth = betterAuth({ database: memoryAdapter(store) });

Never for production — the store is process-local and cleared on restart. Useful as a Vitest fixture.

Schema generation cheatsheet

npx auth generate       # diff / write schema for configured adapter
npx auth migrate        # apply (built-in Kysely only)

Every time a plugin is added or removed, rerun generate. Common symptoms of skipping this step: column "twoFactorEnabled" does not exist, no such table: organization, P2022: The column ... does not exist — see references/common-errors.md.

Extending the user / session / account models

Add columns through the user / session / account options on betterAuth({...}):

betterAuth({
  user: {
    additionalFields: {
      displayName: { type: "string", required: false },
      role: { type: "string", defaultValue: "member" },
    },
  },
});

Propagate the extra fields to the client SDK's types with the inferAdditionalFields helper:

// auth-client.ts
import { createAuthClient } from "better-auth/react";
import { inferAdditionalFields } from "better-auth/client/plugins";
import type { auth } from "./auth";

export const authClient = createAuthClient({
  plugins: [inferAdditionalFields<typeof auth>()],
});

Without inferAdditionalFields, authClient.useSession() will type the user as the built-in shape and the extra columns will be silently dropped from intellisense.

Picking between built-in Kysely and an ORM adapter

SituationPick
Greenfield app, no ORM yetBuilt-in Kysely (simplest)
App already uses PrismaprismaAdapter
App already uses DrizzledrizzleAdapter
App uses MongomongodbAdapter
Cloudflare / edge runtimeDrizzle + better-sqlite3 replacement (e.g. libsql, d1) — Kysely built-in pg/mysql2 are Node-only
High-volume reads on sessions / orgsMongo with experimental.joins or SQL with proper indexes

Edge-runtime compatibility is the most common source of silent failures. See references/common-errors.md ("edge runtime").

README.md

tile.json