10+ AI SaaS templates for web & mobile
home

Migrate from Clerk to Better Auth

·11 min read

How to migrate Clerk to Better Auth: bcrypt passwords, user export, cutover vs dual-run, middleware, and what breaks (sessions, orgs).

You shipped with Clerk, and it worked. Then the bill, the lock-in, or the "your users live in someone else's database" feeling pushed you to look at Better Auth. Searching for how to migrate from Clerk to Better Auth usually lands you on a script dump or a comparison post. This guide is the project plan: what moves, what breaks, which strategy fits, and how to cut over without guessing.

Still deciding if you should leave Clerk at all? Read Better Auth vs Clerk vs NextAuth vs Supabase Auth first, then come back here when the decision is migrate.

Short answer

Yes, you can migrate from Clerk to Better Auth with password continuity. Clerk's dashboard export includes hashed passwords (password_digest / password_hasher). Better Auth defaults to scrypt, so you configure bcrypt verify/hash for migrated users, import users + OAuth accounts + optional 2FA via the official Clerk migration guide, then replace Clerk middleware and UI. Active Clerk sessions do not transfer. Pick big-bang cutover (simpler) or dual-run (accept either cookie for 1–4 weeks) based on how hard a forced re-login is for your users. Organizations need extra work; the official guide does not migrate them yet.

Why teams leave Clerk for Better Auth

Clerk is a hosted auth product. Better Auth is a self-hosted TypeScript library. The migration is really a change of ownership model.

PressureWhat people mean in practice
Cost at scalePro + MRU overages vs Postgres + your compute
Data ownershipUsers/sessions in your DB instead of Clerk's API
Reliability / SPOFSession refresh no longer depends on Clerk staying up
Product fitSocial apps, custom user models, or multi-platform shared auth
Stack alignmentAlready on Drizzle/Postgres and want auth tables next to app data

That last row is why this topic shows up next to SaaS starters: kits like TurboStarter already ship Better Auth wired to your database, so greenfield skips this migration entirely.

Cutover vs dual-run: pick before you write a script

Do not start with migrate-clerk.ts. Start with session strategy.

StrategyHow it worksBest whenCost
Big-bang cutoverExport → import → flip UI/middleware → everyone signs in again on Better AuthSmall user base, B2B with notice windowForced re-login; simplest codebase
Dual-run (trickle)Auth endpoints accept Clerk or Better Auth cookies; new logins only BAConsumer apps where logout day is a support fireTemporary dual middleware; longer Clerk bill

Clerk itself documents the same two shapes when people migrate into Clerk (export/import vs trickle). The reverse trip uses the same tradeoffs. Val Town's public write-up on leaving Clerk for Better Auth used a two-week dual-run: every auth-aware endpoint accepted either cookie until traffic drained off Clerk (Val Town blog).

Decision rule: if a full re-login on deploy day is acceptable (email announcement + OAuth one-click), use cutover. If you need sessions to die of old age, dual-run.

Sessions never migrate

The Better Auth Clerk guide states it plainly: migration invalidates all active sessions. Users must sign in again on Better Auth. Dual-run only softens when that happens, not whether.

What actually migrates (and what does not)

Clerk can export user data from the Dashboard (Settings → User Exports → Export all users). That CSV includes hashed passwords for email/password users (Clerk: migrating / exporting users). You can also pull rich user records from the Backend API for OAuth links, images, ban flags, and more.

DataMigrates with official path?Notes
Users (id, email, name, image)YesKeep Clerk IDs if your app FKs them (forceAllowId)
Password hashesYesCSV password_digest + bcrypt config in Better Auth
OAuth / external accountsYesScript fetches Clerk API external_accounts
TOTP / 2FA secretsYes if 2FA plugin enabledBackup codes are regenerated in the official script
Phone / usernameYes if plugins enabledMatch plugins before import
Active sessionsNoRe-login required
Organizations / membershipsNot in official guidePossible with Organization plugin + custom mapping
Clerk Dashboard roles / UINoRebuild admin with your own tools or Better Auth admin plugin

Ignore blog posts that claim "Clerk never exports password hashes." Clerk's own docs say the Dashboard export includes hashed passwords. The catch is algorithm mismatch (bcrypt vs Better Auth's default scrypt), not missing digests.

Prerequisites

Before any import:

  1. A Postgres (or other) database Better Auth can own
  2. Better Auth installed and schema migrated (npx auth migrate or your Drizzle generate/migrate flow)
  3. Same OAuth apps (Google, GitHub, etc.) with new callback URLs pointing at Better Auth routes
  4. Email provider ready for verification / reset (you own that now)
  5. Staging clone of production user export for a dry run
  6. Inventory of every place you call auth() / currentUser() / Clerk middleware

If you need drop-in screens after the server move, pair Better Auth with Better Auth UI or start from a Next.js Better Auth template.

How to migrate from Clerk to Better Auth (cutover)

This follows the official guide's shape. Use their full script as the source of truth; customize for your adapters and plugins.

Install Better Auth and connect your database

Follow the Better Auth installation guide. Point database at the same Postgres your app already uses (or a new one if you are splitting concerns). Generate or migrate the auth schema so user, account, session, and plugin tables exist before the import.

Configure bcrypt for password continuity

Clerk hashes with bcrypt. Better Auth defaults to scrypt. Without this override, imported digests will not verify.

import { betterAuth } from "better-auth";
import bcrypt from "bcrypt";

export const auth = betterAuth({
  emailAndPassword: {
    enabled: true,
    password: {
      hash: async (password) => bcrypt.hash(password, 10),
      verify: async ({ hash, password }) => bcrypt.compare(password, hash),
    },
  },
});

Enable the same social providers and plugins (admin, twoFactor, phoneNumber, username) you relied on in Clerk before running the import. The official script branches on which plugins are present.

Export users from Clerk

In the Clerk Dashboard: Settings → User Exports → Export all users, then download the CSV. Save it as exported_users.csv at the project root (or update the script path). Keep CLERK_SECRET_KEY available for the script's Backend API pagination (/v1/users?offset=&limit=500).

Run the migration script on staging first

Copy Better Auth's migrate-clerk.ts. It:

  1. Parses the CSV (password_digest, emails, TOTP secret, etc.)
  2. Fetches full Clerk users for external_accounts and metadata
  3. Inserts user rows (optionally preserving Clerk IDs)
  4. Creates account rows for credentials and OAuth providers
  5. Inserts twoFactor rows when that plugin is enabled

Run it against staging. Spot-check counts: users, accounts per provider, 2FA flags, banned users. Only then run against production during a maintenance window if you chose cutover.

Swap the application layer

Data in the database is not a finished migration. Replace:

  • Sign-in / sign-up UI with Better Auth client calls (authClient.signIn.email, social helpers) or Better Auth UI
  • Middleware with session-cookie checks via getSessionCookie from better-auth/cookies (see the official middleware snippet)
  • Server helpers that used Clerk's auth() / currentUser() with Better Auth auth.api.getSession (or your framework wrapper)
  • Webhook handlers that synced Clerk → your users table (often deletable once Better Auth writes directly to your DB)

If your app stored Clerk user IDs as foreign keys, preserving those IDs during import avoids a second data migration. If you mint new IDs, plan a remap table.

Verify, then remove Clerk

Checklist before uninstall:

  • Email/password login works for a sample of migrated users (no forced reset)
  • Each OAuth provider completes a round trip on the new callbacks
  • Protected routes redirect correctly with the new middleware
  • Sign-out clears the Better Auth session cookie
  • 2FA users can complete a challenge if you migrated TOTP
  • Support docs / emails updated for "sign in again" if you cut over cold

Then remove packages (@clerk/nextjs, themes, types), env vars, and Dashboard webhooks. Cancel the Clerk subscription only after traffic and support tickets confirm the old sessions are gone.

Dual-run extras (if cutover is too sharp)

If you choose dual-run:

  1. Ship Better Auth routes and UI behind the same app
  2. Make new sign-ins create Better Auth sessions only
  3. Teach every auth gate to accept Clerk session or Better Auth session
  4. Optionally nudge users (banner, soft re-auth on sensitive actions)
  5. Watch Clerk MRU / active session metrics until residual traffic is tiny
  6. Export remaining inactive users, import, then delete Clerk codepaths

This is more engineering than the official script. Treat it like a temporary compatibility layer with a kill date, not a permanent abstraction.

Organizations, IDs, and other sharp edges

Organizations. Better Auth's guide does not migrate Clerk Organizations today. If you use B2B orgs, export memberships separately and map them into the Organization plugin tables, or rebuild org invites after cutover. Budget this as its own milestone.

Foreign keys. Apps that treated Clerk as the user table often have clerk_user_id columns everywhere. Prefer preserving IDs on import, or run a one-shot remap before flipping reads.

Rate limits and sync debt. If you already mirrored Clerk users into Postgres via webhooks (common once you hit API rate limits on profile reads), decide which row is canonical after cutover. Better Auth should become the sole writer for auth fields.

Email and abuse. You now own verification, password reset, and rate limiting. Wire those before removing Clerk's hosted flows.

Mobile / extension. Shared-session products need the new auth origin and cookie strategy on every client. TurboStarter's web, mobile, and extension docs assume one Better Auth backend; a Clerk migration that stops at Next.js middleware is incomplete if you ship those surfaces.

When a starter beats DIY migration

Migrating auth is only worth it if the destination stack is the one you want long term. If you are also rewriting billing, orgs, admin, and marketing pages, cloning a kit that already uses Better Auth + Drizzle can be cheaper than a six-week auth-only project.

TurboStarter ships Better Auth on web (and shared patterns for mobile/extension), with MFA, OAuth, and organizations already wired. Use this guide when you have an existing Clerk production app. Use the kit when you are starting over or spinning up the next product and do not want to earn the migration scars twice.

FAQ

Sounds good?Now let's make it real. In minutes.
Try TurboStarter

Migrating from Clerk to Better Auth is a data job plus an application rewrite of every auth boundary. Get the strategy right, preserve bcrypt passwords from Clerk's export, dry-run the official import script, then swap middleware and UI with your eyes open about sessions and organizations. Do that once, cleanly, and you own the identity layer for the rest of the product.

world map
Community

Connect with like-minded people

Join our community to get feedback, support, and grow together with 600+ builders on board, let's ship it!

Join us

Ship your startup everywhere. In minutes.

Skip the complex setups and start building features on day one.

Get TurboStarter