Better Auth Integration
What We Did
Section titled “What We Did”Added Better Auth to the Convex backend for user authentication. This provides a foundation for email/password authentication with the ability to expand to OAuth providers later.
Why Better Auth
Section titled “Why Better Auth”Key reasons:
- Convex-native integration:
@convex-dev/better-authprovides seamless adapter for Convex’s database - TypeScript-first: Full type safety from auth config to user sessions
- Minimal setup: Works with Convex’s serverless model without external auth services
- Extensible: Supports OAuth, magic links, 2FA via plugins
Alternatives considered:
- Clerk: Excellent UX but adds external dependency and cost
- Auth.js (NextAuth): More complex setup with Convex, designed for traditional databases
- Convex Auth (built-in): Less feature-rich than Better Auth
Implementation Details
Section titled “Implementation Details”Package Dependencies
Section titled “Package Dependencies”{ "dependencies": { "@convex-dev/better-auth": "^0.10.10", "better-auth": "1.4.9", "convex": "^1.31.6" }}Critical: Pin better-auth to exactly 1.4.9. The @convex-dev/better-auth adapter has strict version requirements. Using ^1.4.17 or later causes type incompatibility errors:
Type 'AdapterFactory' is not assignable to type 'DBAdapterInstance<BetterAuthOptions>'File Structure
Section titled “File Structure”packages/backend/convex/├── auth.ts # Auth instance and helpers├── auth.config.ts # Convex auth configuration├── convex.config.ts # Better Auth component registration└── _generated/ # Auto-generated typesComponent Registration (convex.config.ts)
Section titled “Component Registration (convex.config.ts)”import betterAuth from "@convex-dev/better-auth/convex.config"import { defineApp } from "convex/server"
const app = defineApp()app.use(betterAuth)
export default appAuth Configuration (auth.config.ts)
Section titled “Auth Configuration (auth.config.ts)”import { getAuthConfigProvider } from "@convex-dev/better-auth/auth-config"import type { AuthConfig } from "convex/server"
export default { providers: [getAuthConfigProvider()],} satisfies AuthConfigAuth Instance (auth.ts)
Section titled “Auth Instance (auth.ts)”import { type GenericCtx, createClient } from "@convex-dev/better-auth"import { convex } from "@convex-dev/better-auth/plugins"import { betterAuth } from "better-auth/minimal"
import { components } from "./_generated/api"import type { DataModel } from "./_generated/dataModel"import { query } from "./_generated/server"import authConfig from "./auth.config"
const siteUrl = process.env.SITE_URL!
// Component client for Convex integrationexport const authComponent = createClient<DataModel>(components.betterAuth)
export const createAuth = (ctx: GenericCtx<DataModel>) => { return betterAuth({ baseURL: siteUrl, database: authComponent.adapter(ctx), emailAndPassword: { enabled: true, requireEmailVerification: false, }, plugins: [convex({ authConfig })], })}
// Helper to get current authenticated userexport const getCurrentUser = query({ args: {}, handler: async (ctx) => { return authComponent.getAuthUser(ctx) },})Important TypeScript note: Use import type for DataModel when verbatimModuleSyntax is enabled:
// Correctimport type { DataModel } from "./_generated/dataModel"// Wrong - causes TS1484 errorimport { DataModel } from "./_generated/dataModel"Environment Variables
Section titled “Environment Variables”Set these in Convex:
# Generate auth secretbunx convex env set BETTER_AUTH_SECRET=$(openssl rand -base64 32)
# Set site URL for auth callbacksbunx convex env set SITE_URL http://localhost:3000Context for AI
Section titled “Context for AI”When working with Better Auth in this codebase:
- Version pinning is critical: Always use
better-auth: "1.4.9"exactly, not a caret range - Import types correctly: Use
import typefor DataModel due to verbatimModuleSyntax - Auth component: Access via
authComponentexported fromauth.ts - Creating auth instance: Use
createAuth(ctx)within Convex functions - Getting current user: Use
authComponent.getAuthUser(ctx)or thegetCurrentUserquery
Common Errors
Section titled “Common Errors”| Error | Cause | Fix |
|---|---|---|
Type 'AdapterFactory' is not assignable... | better-auth version mismatch | Pin to 1.4.9 exactly |
TS1484: 'DataModel' is a type... | Missing type-only import | Use import type { DataModel } |
SITE_URL undefined | Missing env var | Run bunx convex env set SITE_URL |
Outcomes
Section titled “Outcomes”Before
Section titled “Before”- No authentication system
- All Convex functions publicly accessible
- Better Auth integrated with Convex
- Email/password authentication ready
getCurrentUserquery available for auth checks- Foundation for protected routes and user-specific data
Next Steps
Section titled “Next Steps”To complete the authentication flow, the following still needs to be added:
- HTTP Routes (
convex/http.ts): Register Better Auth route handlers - Client Auth (
apps/web/lib/auth-client.ts): Browser-side auth instance - Server Auth (
apps/web/lib/auth-server.ts): Next.js server helpers - API Route (
apps/web/app/api/auth/[...all]/route.ts): Proxy auth requests - Provider Update: Replace
ConvexProviderwithConvexBetterAuthProvider - Login/Signup UI: Forms for user authentication
See the Better Auth + Convex + Next.js guide for full implementation details.