Migrate from Clerk
Move Clerk users, organizations, and components to Avnology ID.
Migrate from Clerk
Avnology ID's developer-facing surface is deliberately close to Clerk's — <SignIn/>, <UserButton/>, <Protect/>, and auth() / currentUser() helpers all ship with the same semantics. The migration boils down to user import + env-var swap.
Prerequisites
avnologyCLI installed — see installation.- Admin API key in
.env. - Clerk instance owner permissions.
Equivalent concepts
| Clerk | Avnology ID |
|---|---|
| Instance | Organization |
| Application | Same (OAuth 2.1 client) |
| User | Identity |
| Email address / Phone number | Identity trait + verifier |
| Primary email address | email trait |
<SignIn/> | <SignIn/> (@avnology/id-elements) |
<UserButton/> | <UserButton/> (@avnology/id-elements) |
<Protect/> | <Protect/> (@avnology/id-elements) |
auth() / currentUser() | auth() / currentUser() (@avnology/nextjs) |
clerkMiddleware() | avnologyMiddleware() |
| Session token | OIDC access token + session cookie |
| JWT template | OAuth scopes + custom claim resolver |
| Organization | Organization (same concept) |
| Organization role | Role tuple in Keto (orgs:<id>#<role>@<user>) |
Export users from Clerk
- In the Clerk dashboard, go to Users → Export.
- Download the JSON export. The top-level shape is
{"data": [<user>, …]}.
Each user looks like:
{
"id": "user_2Fg…",
"first_name": "Alan",
"last_name": "Turing",
"username": "alan",
"email_addresses": [
{"id": "idn_1", "email_address": "[email protected]", "verified": true}
],
"primary_email_address_id"
Import with the CLI
avnology migrate clerk --import clerk_users.json --dry-run
avnology migrate clerk --import clerk_users.jsonThe CLI:
- Picks the primary email (or the first email when
primary_email_address_idis absent). - Maps
first_name/last_name/usernameinto identity traits. - Preserves
public_metadataandunsafe_metadataunder the traits bag. - Prefers Clerk's
external_idforexternal_idand falls back to the Clerk user ID.
Component swap
Change your imports:
-import { SignIn, SignUp, UserButton, ClerkProvider } from "@clerk/nextjs";
+import { SignIn, SignUp, UserButton } from "@avnology/id-elements";
+import { AvnologyProvider } from "@avnology/nextjs";<ClerkProvider> becomes <AvnologyProvider domain={process.env.NEXT_PUBLIC_AVNOLOGY_DOMAIN!}>. Everything inside renders the same.
Middleware:
-import { clerkMiddleware } from "@clerk/nextjs/server";
-export default clerkMiddleware();
+import { avnologyMiddleware } from "@avnology/nextjs";
+export default avnologyMiddleware({ publicPaths: ["/", "/sign-in(.*)", "/sign-up(.*)"] });Server helpers are identical:
import { auth, currentUser } from "@avnology/nextjs";
const { userId } = await auth();
const user = await currentUser();OAuth client migration
Clerk's "Application" maps 1:1 to an Avnology OAuth client. Copy your Publishable Key and Secret Key from Clerk (for reference), then register a new application in the Avnology dashboard under Developer → Applications. Swap the env vars:
-NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_…
-CLERK_SECRET_KEY=sk_test_…
+NEXT_PUBLIC_AVNOLOGY_DOMAIN=<Domain id="app"/>
+NEXT_PUBLIC_AVNOLOGY_PUBLISHABLE_KEY=pk_test_…
+AVNOLOGY_SECRET_KEY=sk_test_…Redirect URL mapping
| Clerk URL | Avnology ID URL |
|---|---|
https://<slug>.accounts.dev/oauth/authorize | https://<Domain id="api"/>/oauth2/auth |
https://<slug>.accounts.dev/oauth/token | https://<Domain id="api"/>/oauth2/token |
https://<slug>.accounts.dev/.well-known/jwks.json | https://<Domain id="api"/>/.well-known/jwks.json |
If you used Clerk's Satellite Domains feature, register each as a separate redirect URI on Avnology; custom-domain per-tenant wildcards are not supported.
Webhook migration
Clerk's clerk.user.created event is user.created on Avnology. The signing secret header is X-Avnology-Signature (instead of svix-signature); use @avnology/backend's verifyRequest to verify.
Cutover plan
- Point a staging env at Avnology first. Verify sign-in, sign-up, org switching.
- Dual-write is tricky with Clerk's session cookie — plan on forcing users through a one-time re-auth after the env-var flip.
- Swap env vars in prod.
- Decommission Clerk after 30 days of audit-log retention.