Skip to content

Migrate from NextAuth / Auth.js

Auth.js is a library, not a hosted service: users live in your database in the standardized adapter schema (users, accounts, sessions, verification_tokens — same shape for Prisma, Drizzle, and every other adapter). There is no export step, no vendor, no ticket. And since Auth.js apps are overwhelmingly OAuth + magic-link (no password hashes to move), the import is just emails and ids.

SELECT u.id, u.email, u.name,
a.provider, a."providerAccountId"
FROM users u
LEFT JOIN accounts a ON a."userId" = u.id
WHERE u.email IS NOT NULL;
const rows = users.map(u => ({
email: u.email,
displayName: u.name || null,
sourceUserId: u.id, // your Auth.js user id — the FK your app already uses
}));
// POST in batches of 500 with source: "nextauth" — loop as in the Clerk guide.

See the Clerk guide for the batching loop. If your app’s tables already use users.id as the FK (they do — that’s the adapter design), keep the sourceUserId → endUserId map from the response and re-map once.

Replace the Auth.js routes with the Axowl SDK against your App Group:

  • OAuth users (accounts.provider = "google", "github", …) — enable the same providers on the App Group; users click the same button and match by email.
  • Magic-link users (verification_tokens flow) — Axowl’s default login is magic link; nothing changes for them.
  • Then invite everyone to add a passkey — the upgrade Auth.js never gave you, plus org-grade audit sealing on every login. Sessions don’t migrate; users just sign in once.
  • Custom Credentials providers: if you rolled your own password column next to Auth.js, pass it as passwordHashAlgorithm + passwordHash (e.g. "bcrypt") — stored dormant like every other provider’s hashes (why).
  • Users without email (possible with some OAuth providers) can’t be imported — backfill emails first, email is the identity key.