Avnology ID
Migrate from another IAM

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

  • avnology CLI installed — see installation.
  • Admin API key in .env.
  • Clerk instance owner permissions.

Equivalent concepts

ClerkAvnology ID
InstanceOrganization
ApplicationSame (OAuth 2.1 client)
UserIdentity
Email address / Phone numberIdentity trait + verifier
Primary email addressemail 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 tokenOIDC access token + session cookie
JWT templateOAuth scopes + custom claim resolver
OrganizationOrganization (same concept)
Organization roleRole tuple in Keto (orgs:<id>#<role>@<user>)

Export users from Clerk

  1. In the Clerk dashboard, go to Users → Export.
  2. 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"




Clerk stores passwords as bcrypt. Avnology ID verifies bcrypt natively — existing passwords work on day 1.

Import with the CLI

avnology migrate clerk --import clerk_users.json --dry-run
avnology migrate clerk --import clerk_users.json

The CLI:

  • Picks the primary email (or the first email when primary_email_address_id is absent).
  • Maps first_name/last_name/username into identity traits.
  • Preserves public_metadata and unsafe_metadata under the traits bag.
  • Prefers Clerk's external_id for external_id and 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 URLAvnology ID URL
https://<slug>.accounts.dev/oauth/authorizehttps://<Domain id="api"/>/oauth2/auth
https://<slug>.accounts.dev/oauth/tokenhttps://<Domain id="api"/>/oauth2/token
https://<slug>.accounts.dev/.well-known/jwks.jsonhttps://<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

  1. Point a staging env at Avnology first. Verify sign-in, sign-up, org switching.
  2. Dual-write is tricky with Clerk's session cookie — plan on forcing users through a one-time re-auth after the env-var flip.
  3. Swap env vars in prod.
  4. Decommission Clerk after 30 days of audit-log retention.