Skip to content

Migrate 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).

Clerk CSV columnImport row field
primary_email_addressemail
first_name + last_namedisplayName
id (user_…)sourceUserId
password_digestpasswordHash
passwordHashAlgorithm: "bcrypt"
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
}

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 o
SET user_id = m.axowl_end_user_id
FROM migration_map m -- built from the import response: sourceUserId → endUserId
WHERE 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.

  • 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.