Migrate from Supabase

Migrate from Supabase Auth

Supabase Auth is auth-as-a-feature inside the Supabase platform. Most teams migrate when they outgrow single-tenant and want first-class B2B orgs.

What you'll keep, what you'll lose, what you'll gain

  • Keep: Email + verification status, display name (from raw_user_meta_data.full_name), avatar URL, phone number, auth.identities (Google / GitHub / Apple / Microsoft / Facebook), raw_user_meta_data and raw_app_meta_data preserved as Authio user metadata.
  • Lose: Bcrypt password hashes (Authio is passwordless), Supabase sessions, Row-Level-Security policies (these live in Postgres, not in the auth provider — they keep working).
  • Gain: First-class B2B orgs and memberships. SCIM. The risk engine. The audit stream as a separate service.

Orgs — bring your own graph

Supabase orgs are app-level (typically a public.orgs table you maintain), not auth-level. The importer supports either:

  • Default: all users land under one synthetic Default Authio org.
  • With --orgs-table: you provide a JSON mapping orgs → member emails. Authio creates each org and wires the memberships.
# orgs-table.json
{
  "orgs": [
    {
      "external_id": "acme",
      "name": "Acme Corp",
      "slug": "acme",
      "domain": "acme.com",
      "members": [
        {"email": "ada@acme.com", "role": "owner"},
        {"email": "grace@acme.com", "role": "member"}
      ]
    }
  ]
}

Sessions

Supabase JWTs are not Authio-compatible. Users re-authenticate on next visit. Your Supabase Postgres database keeps working — only the auth provider changes.

Step-by-step: CLI

# 1) Dump auth.users + auth.identities to JSON.
psql "$SUPABASE_DB_URL" <<'SQL' --tuples-only --no-align > supabase.json
SELECT jsonb_build_object('users', jsonb_agg(jsonb_build_object(
  'id', u.id, 'email', u.email,
  'email_confirmed_at', u.email_confirmed_at,
  'raw_user_meta_data', u.raw_user_meta_data,
  'raw_app_meta_data', u.raw_app_meta_data,
  'phone', u.phone, 'banned_until', u.banned_until,
  'identities', (
    SELECT jsonb_agg(jsonb_build_object(
      'id', i.id, 'provider', i.provider, 'identity_data', i.identity_data))
    FROM auth.identities i WHERE i.user_id = u.id
  )
))) FROM auth.users u;
SQL

# 2) Optional: prepare an orgs-table.json mapping app-level orgs.

# 3) Dry-run.
authio import supabase --input ./supabase.json \
  --orgs-table ./orgs-table.json --dry-run | jq '.stats'

# 4) Apply.
authio import supabase --input ./supabase.json --orgs-table ./orgs-table.json

Step-by-step: dashboard wizard

Visit app.authio.com/migrate/supabase — the wizard accepts both the auth.users dump and an optional orgs-table.

Live import (paste an API token)

Paste a Supabase Personal Access Token + your project ref into the dashboard wizard. Authio pulls users (with their identity provider links) from api.supabase.com.

  1. Supabase dashboard → Account → Access Tokens → Generate new token.
  2. Grab your project ref — it's the subdomain of your project URL (https://<project-ref>.supabase.co).
  3. Paste both into the Authio wizard's Connect with API token tab. The probe hits /v1/projects/{ref}.
  4. Start the job; all users land in a default org (Supabase has no native org concept).
Tokens are encrypted at rest with envelope encryption and the credential row is auto-deleted within 24 hours.

Live import via the CLI

authio import supabase \
  --live-token "$SUPABASE_PAT" \
  --supabase-ref "$PROJECT_REF" \
  --dry-run

Post-migration checklist

  • Swap @supabase/auth-js for the Authio SDK.
  • Update JWT verification: Supabase JWTs → Authio JWKS.
  • If your app gated rows by auth.uid() in RLS policies, you'll need to map Authio user IDs back to Supabase user IDs — or change RLS to gate on email.
  • Send migration emails.
Authio's user IDs are different from Supabase user IDs. The importer records the Supabase ID in source_external_ids on each Authio user, so you can join the two via a one-time SQL migration.