Authentication has long been a pain point in web development, especially on the frontend. Countless debates have emerged about the “best” way to handle it. While most developers agree that authentication is hard and that no one-size-fits-all solution exists, few are eager to build an auth system from scratch when that time could be spent on core product work. As a result, many teams turn to third-party providers.
Over the years, solutions like NextAuth (Auth.js), Auth0, and more recently Clerk have emerged. While they generally deliver on their promises, each comes with trade-offs, from complexity and pricing to vendor lock-in and reliability concerns. Clerk, for example, is known for its developer-friendly setup and prebuilt UIs, but its closed-source model, higher cost, and reports of downtime or redirect issues on custom login pages leave gaps.
Enter Better Auth – a relatively new, open-source authentication library that aims to check many of the boxes its predecessors left empty.
In this article, we’ll look at what Better Auth is, walk through its setup and features, and compare it with NextAuth, Clerk, and Auth0 to see whether it can finally ease today’s authentication headaches.
Better Auth is a headless authentication and authorization framework that is widely regarded as one of the most comprehensive authentication libraries for TypeScript, with a strong focus on developer experience and an extensive set of features out of the box. Whether you’re implementing a simple auth flow or building an enterprise-grade system, Better Auth provides the tools to get it done.
Unlike many other solutions that started with a single framework and expanded outward, Better Auth was designed from day one to be framework-agnostic. It works seamlessly across multiple JavaScript frameworks while maintaining excellent type safety. This approach stems from the philosophy that authentication should not be tied to a single framework, but should instead offer broad compatibility while deeply integrating with TypeScript.
Better Auth also introduces features rarely found in typical auth libraries. A particularly notable one and a personal favorite is its database adapters with automatic schema generation and migration support, which significantly reduce the boilerplate work usually associated with setting up authentication.
Some of Better Auth’s core features set includes:
The library uses a plugin architecture that allows for a rich ecosystem of plugins, enabling developers to extend functionality without reinventing authentication patterns. For example, the Polar plugin allows developers to integrate payments and subscriptions directly into their applications with far less overhead than traditional implementations. This combination of flexibility, extensibility, and ease of use makes Better Auth especially appealing to developers who want complex features without added complexity.
Better Auth prioritizes simplicity with minimal configuration in its initial setup. Getting started with the library requires installing the package using the following command:
npm install better-auth
Once installed, you’ll need to generate a secret key from Better Auth’s documentation site and set it up as an environment variable like so:
BETTER_AUTH_SECRET=your-secret-key BETTER_AUTH_URL=http://localhost:3000
Better Auth is designed to automatically detect and use these variables, so you don’t have to manually reference them anywhere in your code. However, providing them is mandatory for the library to function properly.
To complete the initial setup, create a Better Auth instance in a file named auth.ts
. This file can sit in your project root or inside a utility folder like lib
or utils
. In it, you’ll define the core configuration properties that shape how authentication works in your project:
import { betterAuth } from "better-auth" import { Pool } from "pg"; export const auth = betterAuth({ database: new Pool({ connectionString: process.env.DATABASE_URL, }), emailAndPassword: { enabled: true }, socialProviders: { github: { clientId: process.env.GITHUB_CLIENT_ID!, clientSecret: process.env.GITHUB_CLIENT_SECRET! } } })
The configuration properties let you customize important aspects of your authentication setup, such as the database type and preferred authentication methods. The database
property supports multiple options, including SQLite, PostgreSQL, and MySQL. In the example below, we’re using PostgreSQL via the pg
driver:
// ... import { Pool } from "pg"; export const auth = betterAuth({ database: new Pool({ connectionString: process.env.DATABASE_URL, }), // ... })
In addition, Better Auth provides built-in adapters for seamless integration with popular ORMs such as Drizzle and Prisma. These adapters let you plug in your existing ORM configuration instead of wiring up database connections manually:
// Drizzle import { betterAuth } from "better-auth"; import { drizzleAdapter } from "better-auth/adapters/drizzle"; import { db } from "@/db"; // your drizzle instance export const auth = betterAuth({ database: drizzleAdapter(db, { provider: "pg", // or "mysql", "sqlite" }), }); // Prisma import { betterAuth } from "better-auth"; import { prismaAdapter } from "better-auth/adapters/prisma"; import { PrismaClient } from "@/generated/prisma"; const prisma = new PrismaClient(); export const auth = betterAuth({ database: prismaAdapter(prisma, { provider: "sqlite", // or "mysql", "postgresql", etc. }), });
To simplify database management, Better Auth also provides a CLI tool that can automatically generate schemas for your chosen database or adapter, as well as perform migrations and create tables:
# Generate schema npx @better-auth/cli generate # Migrate database npx @better-auth/cli migrate
The workflow varies slightly depending on whether you’re using a built-in database or an adapter, see the CLI documentation for more information.
Beyond database setup, the configuration options let you specify which authentication methods to enable. Better Auth includes built-in support for a variety of methods. In a basic setup, you might enable just email/password login alongside social sign-on providers. However, the library also supports less common authentication flows such as passkeys, usernames, and magic link authentication, features that are often missing or treated as secondary in other auth libraries.
The initial setup essentially covers the entire process of integrating Better Auth into your application. Anything beyond this, such as adding sign-up and sign-in flows, protecting routes, or managing sessions, builds on top of this foundation.
To put this into perspective, let’s say we’re working with a Next.js application. After installing Better Auth and completing the base setup, we decided to use a PostgreSQL database with a provider like Neon. All that’s required is to create the database on Neon’s platform and then add the connection string to the Better Auth configuration file like so:
import { betterAuth } from "better-auth"; import { Pool } from "pg"; export const auth = betterAuth({ database: new Pool({ connectionString: process.env.NEON_STRING, }), emailAndPassword: { enabled: true }, });
Once that’s done, running the generate
and migrate
commands will automatically generate a schema and create the necessary tables directly in the Neon database:
It’s worth noting that Better Auth requires a few core tables, account
, session
, user
, and verification
. These are added automatically to the schema during the generation process. If you were writing the schema manually, you’d need to define them yourself.
This process alone eliminates countless hours that would otherwise be spent doing everything manually with other authentication solutions. And keep in mind, all of this still falls under the initial setup. This example simply highlights how seamless and efficient Better Auth makes setting up a complete authentication flow from the start.
Now, to start using Better Auth in your application, you need to first create a route handler that manages the authentication flow through framework integration. In the case of a Next.js application, this is done by creating a new catch-all route file at app/api/auth/[...all]/route.ts
, and add the following code:
// app/api/auth/[...all]/route.ts import { auth } from "@/lib/auth"; // path to your auth file import { toNextJsHandler } from "better-auth/next-js"; export const { POST, GET } = toNextJsHandler(auth);
You can find the equivalent setup for other frameworks in the official documentation.
Next, you’ll need to create a client instance to interact with the auth server. This is usually placed alongside the auth.ts file under the lib
folder at lib/auth-client
with the following code:
import { createAuthClient } from "better-auth/react" export const authClient = createAuthClient({ /** The base URL of the server (optional if you're using the same domain) */ baseURL: "http://localhost:3000" })
From here, you can export the main client or even expose specific methods directly, such as:
import { createAuthClient } from "better-auth/react"; export const { signIn, signUp, signOut, useSession } = createAuthClient()
These methods are self-explanatory, they’re what you’ll use to implement sign-up and sign-in flows on the client side and to manage user sessions.
If you decide to use the credential/password authentication method, all you need to do is create sign-up and sign-in pages with forms that collect user credentials. Then use the signUp
and signIn
methods to either create new accounts or authenticate existing ones.
This flexibility is part of what makes Better Auth a headless auth solution, it doesn’t force you into pre-built UIs but instead gives you full freedom to design and implement your own flows.
By contrast, providers like Clerk ship with polished prebuilt UIs, which many developers appreciate. However, customizing those UIs often introduces complexity and errors.
The creator knows not everyone wants to build a custom UI from scratch, which is why Better Auth also provides Shadcn-based sign-in and sign-up components. These can be copied directly into your codebase and will work immediately, with no configuration changes required. This gives you the best of both worlds: the freedom to build your own UI or the option to use plug-and-play components for faster development, such as:
After setting these up in our hypothetical Next.js application, we can begin handling registrations and sign-ins. When users navigate to the sign-up page and register, as shown in the GIF below:
Their credentials are instantly stored in the Neon database:
It’s really that straightforward, from the initial setup to using basic authentication in your application, you hardly need to write any additional code. Everything you need, regardless of your framework, is already provided in the Better Auth documentation.
As you may have noticed from the initial setup, Better Auth’s configuration feels quite similar to NextAuth’s flow, one could even say it takes inspiration from NextAuth. The key difference, however, is that while NextAuth requires more configuration and boilerplate, Better Auth removes much of this complexity and streamlines the authentication process.
Before Better Auth, Clerk was widely regarded as one of the best authentication libraries on the web. Clerk’s setup is simple and comprehensive, especially when compared to older solutions. However, Better Auth’s seamless database integrations, automatic schema generation, and built-in migration support more than make up for any shortcomings it may have relative to Clerk, giving it a clear edge in real-world developer experience.
Another key advantage of Better Auth is that it remains framework-agnostic, unlike libraries such as Clerk and NextAuth, which primarily focus on React and React-based frameworks. Better Auth supports a wide range of frameworks, including Vue, Svelte, Astro, Solid, Nuxt, Remix, and even backend frameworks like Express and Fastify. This flexibility enables developers to integrate robust authentication into virtually any stack, making it well-suited for diverse and evolving tech ecosystems.
Out of the box, Better Auth ships with features many libraries require plugins for, two-factor authentication (2FA), type safety, email/password authentication, social logins, and organization management, all as core functionality.
By contrast, libraries like NextAuth excel in OAuth-based authentication and offer deep customization through callbacks and database integrations. However, this flexibility often comes with trade-offs. Implementing them typically requires custom logic or third-party packages, adding complexity to the setup.
Better Auth’s plugin system also makes a big difference compared to older libraries. It makes scaling much easier and offers a high degree of customization. For example, the Polar plugin makes integrating a payment system into your application straightforward.
All it takes is installing the Polar SDK, generating an access token from your Polar dashboard, and adding it as a plugin to the Better Auth instance, like so:
import { betterAuth } from "better-auth"; import { polar, checkout, portal, usage, webhooks } from "@polar-sh/better-auth"; import { Polar } from "@polar-sh/sdk"; const polarClient = new Polar({ accessToken: process.env.POLAR_ACCESS_TOKEN, server: "sandbox", }); const auth = betterAuth({ // ... Better Auth config plugins: [ polar({ client: polarClient, createCustomerOnSignUp: true, use: [ checkout({ products: [ { productId: "123-456-789", // ID from Polar Dashboard slug: "pro", // e.g., /checkout/pro }, ], successUrl: "/success?checkout_id={CHECKOUT_ID}", authenticatedUsersOnly: true, }), portal(), usage(), webhooks({ secret: process.env.POLAR_WEBHOOK_SECRET, onCustomerStateChanged: (payload) => {}, onOrderPaid: (payload) => {}, // several other webhook handlers available onPayload: (payload) => {}, }), ], }), ], });
That’s all it takes to set up Polar in your application with Better Auth. From there, you can use the auth client to interact with the Polar SDK, and take advantage of additional plugins that add functionalities such as checkout, portal management, usage-based billing, and webhooks.
Libraries like Auth0 also provide extensive customization through rules, hooks, and APIs. However, as a managed service, they come with limitations when it comes to deeper customizations unless you build custom extensions. The same applies to Clerk, which offers customizable UI and workflow add-ons, but still cannot match an open-source framework in terms of extensibility.
Among the alternatives, NextAuth is perhaps the only library that comes close to Better Auth in customization and extensibility, as it is also open source and somewhat similar in design. However, the additional effort Better Auth puts into providing advanced built-in features places it far ahead of NextAuth.
Better Auth is still relatively new, so its community is in the early stages. However, it’s growing at a pace I haven’t seen from any JavaScript library in a long time, largely because almost every developer who tries it is instantly sold on the experience. Clerk saw similar traction when it launched, and while its community remains fairly large despite being relatively new itself, Better Auth’s adoption rate has been remarkable. Thanks to this momentum, there’s already a wealth of content, tutorials, and resources about Better Auth available across the web.
Unlike many other authentication services that charge based on monthly active users or feature tiers, Better Auth is open-source and can be self-hosted. The primary costs associated with Better Auth come from infrastructure, such as server and database hosting, and developer time for setup, updates, and maintenance. This makes the pricing predictable and scalable, particularly suitable for startups and growing applications that want to avoid escalating usage fees.
In comparison, managed services like Auth0 and Clerk often have free tiers but quickly scale to costly monthly subscriptions as the user base grows. Auth0’s pricing, for example, can become expensive at scale with usage-based billing and enterprise add-ons, while Clerk charges per MAU with additional fees for premium features.
NextAuth, being open-source like Better Auth, shares a similar pricing benefit but may require more developer time due to its somewhat more complex setup and customization needs.
Here’s a comparison table for a clearer overview:
Aspect | Better Auth | Clerk | NextAuth.js (Auth.js) | Auth0 |
---|---|---|---|---|
Authentication features | Email/password, social login, passwordless, MFA, multi-tenancy, organizations, SSO | Email/password, social login, passwordless, MFA, prebuilt UI components | OAuth providers, custom credentials, passwordless, configurable sessions | Social, enterprise SSO, MFA, B2B features |
Customization & extensibility | High customization via plugin system and hooks | UI customization, less extensible beyond UI | High customization through callbacks and events | Extensive, but with limited custom control |
TypeScript support | Native TypeScript-first, full type safety | Good TypeScript support but React-focused | Good TS support via declaration files | TypeScript SDK available |
Database integration | Supports Prisma, Drizzle ORM, TypeORM with auto schema generation | Managed user data storage | Multiple adapters (Prisma, TypeORM, Sequelize) | Managed user database |
Session management | Flexible JWT and database sessions, token rotation, revocation | Managed sessions, token-based | JWT and DB sessions, configurable session handling | Managed sessions with advanced token security |
Security | Strong password hashing, CSRF protection, MFA, rate limiting | MFA, secure session management | Standard OAuth & JWT, CSRF protection | Enterprise-grade security, anomaly detection |
Enterprise features | Built-in organizations, teams, multi-tenancy | B2B multi-tenancy features | Basic organization support | Advanced enterprise compliance and governance |
Pricing | Free, self-hosted, hosting costs only | Free tier + paid plans, usage-based | Free, dev/infra costs | Usage-based, can be expensive |
When you look closely, Better Auth leans more toward a low-level implementation of authentication. Even with its advanced built-in features, many developers may still prefer Clerk for its higher-level abstractions. However, sooner or later, most teams will need the flexibility Better Auth offers, it’s essentially the Swiss Army knife of authentication.
In this article, we introduced Better Auth, explored how to implement it in a React/Next.js application, and compared it with established solutions like NextAuth, Clerk, and Auth0. The goal was to help you make an informed decision about the right authentication tool for your needs. As its ecosystem grows, Better Auth has the potential to become the go-to open-source choice for developers who want control, extensibility, and fewer authentication headaches.
LogRocket lets you replay user sessions, eliminating guesswork by showing exactly what users experienced. It captures console logs, errors, network requests, and pixel-perfect DOM recordings — compatible with all frameworks, and with plugins to log additional context from Redux, Vuex, and @ngrx/store.
With Galileo AI, you can instantly identify and explain user struggles with automated monitoring of your entire product experience.
Modernize how you understand your web and mobile apps — start monitoring for free.
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowRead one developer’s detailed account of using a screen reader to learn more about a11y and build more accessible websites.
Walk through six tips and tricks that help you level up Claude Code to move beyond simply entering prompts into a text box.
React Router v7 is now more than routing. This guide explains its three modes—declarative, data, and framework and how to choose the right one for your app.
CSS @function brings reusable logic to native CSS. Learn how to replace Sass and JavaScript utilities with native functions for typography, color, and fluid design.