Firestore (Firebase Admin SDK) adapter for Better Auth. A drop-in replacement for the Auth.js Firebase adapter with matching data shape.
- Install:
pnpm add @yultyyev/better-auth-firestore firebase-admin better-auth - Docs: Quickstart • Options • Migration • Emulator
- Example: See
/examples/minimalfor a complete Next.js App Router example
npm install @yultyyev/better-auth-firestore firebase-admin better-authpnpm add @yultyyev/better-auth-firestore firebase-admin better-authyarn add @yultyyev/better-auth-firestore firebase-admin better-authbun add @yultyyev/better-auth-firestore firebase-admin better-authimport { firestoreAdapter } from "@yultyyev/better-auth-firestore";
import { createAuth } from "better-auth";
import { getFirestore } from "firebase-admin/firestore";
export const auth = createAuth({
adapter: firestoreAdapter({ firestore: getFirestore() })
});import { betterAuth } from "better-auth";
import { firestoreAdapter, initFirestore } from "@yultyyev/better-auth-firestore";
import { cert } from "firebase-admin/app";
const firestore = initFirestore({
credential: cert({
projectId: process.env.FIREBASE_PROJECT_ID!,
clientEmail: process.env.FIREBASE_CLIENT_EMAIL!,
privateKey: process.env.FIREBASE_PRIVATE_KEY!.replace(/\\n/g, "\n"),
}),
projectId: process.env.FIREBASE_PROJECT_ID!,
name: "better-auth",
});
export const auth = betterAuth({
// ... your Better Auth options
database: firestoreAdapter({
firestore,
namingStrategy: "default", // or "snake_case"
collections: {
// users: "users",
// sessions: "sessions",
// accounts: "accounts",
// verificationTokens: "verificationTokens",
},
}),
});- Go to Firebase Console
- Click "Add project" or "Create a project"
- Enter a project name and follow the setup wizard
- In your Firebase project, go to Build → Firestore Database
- Click "Create database"
- Choose your preferred security rules mode (you can update rules later)
- Select a location for your database
- Go to Project Settings (gear icon) → Service Accounts
- Under "Firebase Admin SDK", click "Generate new private key"
- Download the JSON file (keep it secure - never commit it to version control)
From the downloaded service account JSON file, extract these values:
project_id→FIREBASE_PROJECT_IDclient_email→FIREBASE_CLIENT_EMAILprivate_key→FIREBASE_PRIVATE_KEY(requires newline replacement - see Troubleshooting)
Alternative: You can use the JSON file directly by setting GOOGLE_APPLICATION_CREDENTIALS environment variable to the path of your service account JSON file.
The adapter uses the Firebase Admin SDK (server-side), so Firestore security rules should deny direct client access. See Firestore Security Rules below.
Required environment variables:
FIREBASE_PROJECT_ID- Your Firebase project IDFIREBASE_CLIENT_EMAIL- Service account email from the JSON fileFIREBASE_PRIVATE_KEY- Service account private key (with newlines properly escaped)
Note: The FIREBASE_PRIVATE_KEY often contains literal \n characters in environment variables. See Troubleshooting for how to handle this.
firestoreAdapter({
firestore?: Firestore;
namingStrategy?: "default" | "snake_case";
collections?: { users?: string; sessions?: string; accounts?: string; verificationTokens?: string };
debugLogs?: boolean | DBAdapterDebugLogOption;
});Default collection names:
users: "users"sessions: "sessions"accounts: "accounts"verificationTokens: "verification_tokens" (snake_case) or "verificationTokens" (default)
firestoreAdapter({
firestore,
debugLogs: true, // Enable verbose logging
});| Runtime | Supported | Notes |
|---|---|---|
| Node 18+ | ✅ | Recommended |
| Next.js (App Router) | ✅ | Server routes only |
| Cloud Functions / Cloud Run | ✅ | Provide FIREBASE_* creds |
| Vercel Edge / CF Workers | ❌ | Firestore Admin SDK not supported at Edge runtime |
The adapter maintains the same data shape as Auth.js/NextAuth for seamless migration:
| Collection | Typical fields |
|---|---|
users |
id, email, name, image, createdAt, updatedAt |
accounts |
provider, providerAccountId, userId, access_token, refresh_token |
sessions |
sessionToken, userId, expires |
verificationTokens |
identifier, token, expires |
Defaults: Collections default to
users,sessions,accounts,verification_tokens(snake_case) /verificationTokens(default). See Options to customize collection names.
Since this adapter uses the Firebase Admin SDK (server-side), Firestore security rules should deny direct client access:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if false;
}
}
}| Feature | Better Auth Firestore | Auth.js Firebase Adapter |
|---|---|---|
| Status | ✅ Active development | Now maintained by Better Auth team (announcement) |
| Firebase Admin SDK | ✅ Uses Admin SDK | ✅ Uses Admin SDK |
| Data shape compatibility | ✅ Matching shape, migration-free | - |
| Drop-in replacement | ✅ Yes | - |
This adapter is the Better Auth-native solution for Firestore users, recommended for new projects.
For complete migration steps, see the Better Auth NextAuth Migration Guide, which covers route handlers, client setup, and server-side session handling.
This adapter uses the same default collection names and field names as Auth.js Firebase adapter, making it a drop-in replacement for the database adapter portion of your migration:
- Collection names:
users,sessions,accounts,verificationTokens(same as Auth.js) - Field names:
sessionToken,userId,providerAccountId, etc. (same as Auth.js) - Data shape: Identical, so no data migration scripts needed
Simply replace your Auth.js Firebase adapter with this one:
// Before (Auth.js)
import { FirestoreAdapter } from "@auth/firebase-adapter";
// After (Better Auth)
import { firestoreAdapter } from "@yultyyev/better-auth-firestore";
// Same Firestore instance, same collections, same data shape
export const auth = betterAuth({
database: firestoreAdapter({ firestore }),
});If you were using custom collection names with Auth.js, you can override them:
firestoreAdapter({
firestore,
collections: {
accounts: "authjs_accounts", // or whatever custom names you were using
// ... other overrides
},
});firestoreAdapter({
firestore,
namingStrategy: "snake_case",
});firestoreAdapter({
firestore,
collections: {
accounts: "accounts", // or your custom collection names
// ... other overrides
},
});// app/api/auth/[...all]/route.ts
import { toNextJsHandler } from "better-auth/next-js";
import { auth } from "@/lib/auth";
export const { GET, POST } = toNextJsHandler(auth);import { firestoreAdapter } from "@yultyyev/better-auth-firestore";
import { createAuth } from "better-auth";
import { initializeApp, cert } from "firebase-admin/app";
import { getFirestore } from "firebase-admin/firestore";
const app = initializeApp({
credential: cert({
projectId: process.env.FIREBASE_PROJECT_ID,
clientEmail: process.env.FIREBASE_CLIENT_EMAIL,
privateKey: process.env.FIREBASE_PRIVATE_KEY?.replace(/\\n/g, "\n"),
}),
});
export const auth = createAuth({
adapter: firestoreAdapter({ firestore: getFirestore(app) }),
});# start emulator (example)
docker run -d --rm \
--name auth-firestore \
-p 8080:8080 \
google/cloud-sdk:emulators gcloud beta emulators firestore start \
--host-port=0.0.0.0:8080
export FIRESTORE_EMULATOR_HOST=localhost:8080
pnpm vitest runSymptom: Authentication fails or you see errors about invalid private key format.
Fix: Environment variables often store newlines as literal \n strings. Replace them at runtime:
privateKey: process.env.FIREBASE_PRIVATE_KEY!.replace(/\\n/g, "\n")Symptom: Firebase Admin SDK requests hang or time out during local development.
Fix: Use the Firestore Emulator and set FIRESTORE_EMULATOR_HOST=localhost:8080 before running your app. See Testing with Firestore Emulator for setup instructions.
- Better Auth Documentation
- Better Auth Adapter Guide
- Auth.js Firebase Adapter (legacy, for reference)
- Auth.js joins Better Auth - Announcement
pnpm buildMIT.