Skip to content

Migrate from Supabase Auth

Self-hosted (or via a support-requested dump on the hosted platform), it’s your database:

SELECT id, email, raw_user_meta_data->>'full_name' AS display_name, encrypted_password
FROM auth.users
WHERE deleted_at IS NULL;

encrypted_password is a standard bcrypt hash ($2a$…). On hosted Supabase the column is not exposed through the API — ask Supabase support for an auth.users dump, or skip hashes entirely (Axowl doesn’t need them; see below).

const rows = supabaseUsers.map(u => ({
email: u.email,
displayName: u.display_name || null,
sourceUserId: u.id, // Supabase auth.users.id (UUID)
passwordHashAlgorithm: u.encrypted_password ? "bcrypt" : null,
passwordHash: u.encrypted_password || null,
}));
// POST in batches of 500 with source: "supabase" — loop as in the Clerk guide.

See the Clerk guide for the batching loop and foreign-key re-mapping SQL. Because Supabase’s auth.users.id is usually the FK all over your schema, keep the sourceUserId → endUserId map from the import response — that map is your migration.

  • RLS policies referencing auth.uid() are the real migration work — they die with Supabase Auth. Plan how Axowl’s JWT (issued per App Group) replaces those checks in your API layer.
  • Social login carries over by email; magic-link users carry over with zero friction (Axowl’s default login is magic link).
  • Hashes are stored dormant — see how the import works.