SKILL.md
Clerk Migration Deep Dive
Current State
!npm list @auth0/nextjs-auth0 next-auth @supabase/auth-helpers-nextjs firebase 2>/dev/null | grep -E "auth0|next-auth|supabase|firebase" || echo 'No auth providers detected'
Overview
Comprehensive guide to migrating from Auth0, Firebase Auth, Supabase Auth, or NextAuth to Clerk. Covers user data export, bulk import, parallel running, and phased migration.
Prerequisites
- Current auth provider access with admin/export permissions
- Clerk account with API keys
- Git repository with clean working state
- Migration timeline planned (recommend 2-4 weeks)
Instructions
Step 1: Export Users from Current Provider
Auth0 Export:
# Export users via Auth0 Management API
curl -s -H "Authorization: Bearer $AUTH0_TOKEN" \
"https://$AUTH0_DOMAIN/api/v2/users?per_page=100&page=0" \
| jq '[.[] | {email: .email, name: .name, picture: .picture, created_at: .created_at}]' \
> auth0-users.json
NextAuth (Prisma) Export:
// scripts/export-nextauth-users.ts
const users = await prisma.user.findMany({
include: { accounts: true },
})
const exported = users.map((u) => ({
email: u.email,
name: u.name,
image: u.image,
provider: u.accounts[0]?.provider,
createdAt: u.createdAt,
}))
await fs.writeFile('nextauth-users.json', JSON.stringify(exported, null, 2))
Step 2: Import Users to Clerk
// scripts/import-to-clerk.ts
import { createClerkClient } from '@clerk/backend'
import users from './auth0-users.json'
const clerk = createClerkClient({ secretKey: process.env.CLERK_SECRET_KEY! })
interface MigrationResult {
email: string
status: 'created' | 'exists' | 'error'
clerkId?: string
error?: string
}
async function importUsers(): Promise<MigrationResult[]> {
const results: MigrationResult[] = []
for (const user of users) {
try {
const created = await clerk.users.createUser({
emailAddress: [user.email],
firstName: user.name?.split(' ')[0],
lastName: user.name?.split(' ').slice(1).join(' '),
skipPasswordRequirement: true, // User will set password on first sign-in
})
results.push({ email: user.email, status: 'created', clerkId: created.id })
console.log(`Created: ${user.email} -> ${created.id}`)
} catch (err: any) {
if (err.status === 422) {
results.push({ email: user.email, status: 'exists' })
} else {
results.push({ email: user.email, status: 'error', error: err.message })
}
}
// Respect rate limits
await new Promise((resolve) => setTimeout(resolve, 100))
}
return results
}
importUsers().then((results) => {
const summary = {
total: results.length,
created: results.filter((r) => r.status === 'created').length,
exists: results.filter((r) => r.status === 'exists').length,
errors: results.filter((r) => r.status === 'error').length,
}
console.log('Migration summary:', summary)
fs.writeFileSync('migration-results.json', JSON.stringify(results, null, 2))
})
