Migrate from Clerk
1. Export from Clerk
Section titled “1. Export from Clerk”In the Clerk Dashboard → User Exports, download the CSV. It includes each user’s email,
name, the Clerk user id (user_…), and — for password users — the bcrypt hash. For a live
sync instead of a snapshot, use the Clerk Backend API (GET /v1/users).
2. Map the fields
Section titled “2. Map the fields”| Clerk CSV column | Import row field |
|---|---|
primary_email_address | email |
first_name + last_name | displayName |
id (user_…) | sourceUserId |
password_digest | passwordHash |
| — | passwordHashAlgorithm: "bcrypt" |
3. Import
Section titled “3. Import”const rows = parsedCsv.map(u => ({ email: u.primary_email_address, displayName: [u.first_name, u.last_name].filter(Boolean).join(" ") || null, sourceUserId: u.id, passwordHashAlgorithm: u.password_digest ? "bcrypt" : null, passwordHash: u.password_digest || null,}));
for (let i = 0; i < rows.length; i += 500) { const res = await fetch("https://testapi.axowl.com/api/public/v1/end-users/import", { method: "POST", headers: { "X-Api-Key": process.env.AXOWL_API_KEY, "Content-Type": "application/json" }, body: JSON.stringify({ appGroupId: process.env.AXOWL_APP_GROUP_ID, source: "clerk", sendInvites: false, users: rows.slice(i, i + 500), }), }); console.log(await res.json()); // per-row: created / duplicate / invalid + endUserId}4. Re-map your foreign keys
Section titled “4. Re-map your foreign keys”If your tables store Clerk’s user_id, translate them with the per-row endUserId the import
returns (or query later by sourceUserId):
UPDATE app_orders oSET user_id = m.axowl_end_user_idFROM migration_map m -- built from the import response: sourceUserId → endUserIdWHERE o.user_id = m.clerk_user_id;Better long-term: keep your own user id as the primary key and store the auth provider’s id in one column — the mistake that makes auth migrations painful is using the provider’s id as your PK.
Gotchas
Section titled “Gotchas”- Social login carries over by email. Users who signed in to Clerk with Google/GitHub sign in to Axowl with the same button and land on the same account — no OAuth app changes needed.
- Passkeys do not export. Clerk does not export WebAuthn public keys, and credentials are origin-bound. Migrated passkey users sign in once by magic link, then Axowl prompts them to register a new passkey.
- Passwords are retired, not lost. The bcrypt hashes are stored dormant for provenance; see how the import works for why users won’t miss them.