Migrating to Foir
Moving an existing user base to Foir is two decisions: how you bring the accounts over, and how you handle passwords. This guide covers both, then the specifics for Clerk and Better Auth.
The accounts part is straightforward — every provider gives you an export of emails and profile data, and you create a matching customer in Foir for each. Passwords are where migrations differ, so start there.
Passwords: pick an approach
Option A — reset on first login (recommended)
Import your users without their passwords. The first time each one signs in, they use OTP or a password reset to set a new password. Nothing about the old hashes matters, and it works today with no special tooling.
This is the right default for most migrations: it’s the least error-prone, it works regardless of how the old provider hashed passwords, and it quietly retires any weak or stale credentials in the process.
- Export your users’ emails and profile fields.
- Create a Foir customer for each (see below).
- Point your login at Foir. On first visit, users go through OTP or “forgot password” once.
Option B — preserve existing passwords
If a forced reset isn’t acceptable, existing passwords can be preserved when they’re bcrypt hashes. Foir’s identity backend (Ory Kratos) verifies a bcrypt hash on the user’s next login and transparently upgrades it to argon2 — so users keep their current password with no reset.
Note: There is no self-serve importer for external password hashes yet — hash-preserving import is an assisted migration today. If you need it, get in touch before you start. bcrypt is the compatible format; other schemes (for example scrypt) can’t be verified as-is and fall back to Option A.
Bring the accounts over
Create a Foir customer per user through the API. The registration mutation returns tokens, but for a bulk import you typically create the account and let the user establish their own session on first login (Option A).
mutation CustomerRegister($email: String!, $password: String!) {
customerRegister(email: $email, password: $password) {
customerId
email
}
}Customer profile and metadata map onto the Foir customer record — see Customers for the data model, including per-customer metadata and the row-level isolation that comes with it.
From Clerk
Clerk’s user export is a CSV that includes each user’s email, profile fields, and their bcrypt password hash.
- Export your users from the Clerk dashboard.
- Map each row to a Foir customer: email and profile fields → the customer record; Clerk
public_metadata→ customer metadata. - Passwords: because Clerk exports bcrypt, Option B can preserve them (assisted migration). Otherwise use Option A — import emails only and let users reset on first login.
- Repoint your app. Clerk’s
<SignIn />becomes Login with Foir; Clerk session lookups become the Foir customer access token you verify against the JWKS.
From Better Auth
Better Auth keeps users in your own database, so you already have direct access to the user and account tables — no export step, just a query.
- Read your
userrows (email, name, metadata) and their linkedaccountrows. - Create a Foir customer per user.
- Passwords: Better Auth hashes passwords in your database (scrypt by default in current versions), which isn’t bcrypt — so use Option A and have users reset on first login. OAuth-only accounts have no password to migrate at all; the user simply signs in with the same provider through Foir.
- Replace Better Auth’s client and server helpers with the Foir flow — hosted Login with Foir for the UI, and the customer access token where you previously read the Better Auth session.
After you migrate
- Wire lifecycle events. Subscribe to
customer.created/updated/deletedso your systems stay in sync. See Webhooks. - Choose a session shape. Bearer tokens, or an
HttpOnlycookie via a first-party session (BFF). - Watch usage. Imported users start counting toward MAU as they sign in.