---
title: "Migrate from Clerk"
description: "Move Clerk sign-in, route protection and sign-in tokens onto the MainSDK auth interface and switch drivers when you choose."
section: "guides"
status: "Planned for 0.7"
spec: "migration-guides"
canonical: "https://mainsdk.dev/docs/guides/migrate-from-clerk"
updated: "2026-10-28"
---

# Migrate from Clerk

> Move Clerk sign-in, route protection and sign-in tokens onto the MainSDK auth interface and switch drivers when you choose.

Status: Planned for 0.7 · Canonical: https://mainsdk.dev/docs/guides/migrate-from-clerk · Updated: 2026-10-28

Move Clerk sign-in, route protection and sign-in tokens onto the MainSDK auth interface and switch drivers when you choose. Your Clerk account stays; nothing here deletes it.

**Before you start:**

- A git repository with a clean working tree. `mainsdk migrate` works on a branch and never touches yours.
- Node 22 and `npx mainsdk`. No `main.yaml` yet? `migrate` writes one; or start with [Adopt an existing app](https://mainsdk.dev/docs/getting-started/existing-app).
- Your Clerk keys stay where they are. Move them to `secret:` references when the driver is chosen (see [Secrets](https://mainsdk.dev/docs/interfaces/secrets)).

## 1. Read the report

```text
npx mainsdk migrate clerk --report-only
```

The report writes no file. It sorts every Clerk call in your code into three lists: **mapped** (an interface call replaces it), **raw** (it stays behind `raw()`, with the reason) and **needs a person** (a policy, a rule file or a call with no interface). Add `--json` for a machine-readable report.

For the sample app of this guide (`packages/cli/test/fixtures/migrate/clerk`), the report finds 4 mapped, 0 raw and 1 for a person.

## 2. What the codemod changes

| Where | Before | After |
| --- | --- | --- |
| `app/api/orders/route.ts:4` | `const { userId } = auth()` | `auth.require(req)` |
| `app/sign-in/route.ts:6` | `const token = await clerkClient.signInTokens.createSignInToken({ userI` | `auth.signIn()` |
| `middleware.ts:3` | `const isPublic = createRouteMatcher(['/sign-in(.*)', '/'])` | `auth.routes()` |
| `middleware.ts:5` | `export default clerkMiddleware(async (guard, req) => {` | `auth.middleware()` |

Each rewrite is mechanical and one line at a time. Imports of Clerk go when no call needs them, and `mainsdk` is added to `package.json`.

## 3. What stays behind raw()

- Nothing in the sample stays behind raw().

A call behind `raw()` keeps working through the `Clerk` host on your network allow list, and `mainsdk check` still sees it. Each one has a marker comment, `// mainsdk migrate: ...`, so you can find it later.

## 4. What needs a person

- `app/layout.tsx:4`: no interface call for <ClerkProvider; a person decides

The codemod leaves these lines alone and marks them. No tool should guess a policy.

## 5. Stateful moves are guided jobs

Data does not move with the code. Each stateful move is a job with steps and a rollback, listed in the pull request and never run by the codemod.

### users: Move Clerk users to the auth driver you choose

1. Export users from the Clerk dashboard (Users, Export) or the Backend API.
2. Import them into the new driver on staging and compare counts.
3. Switch sign-in for 1% of traffic, then all.

Rollback: Keep auth.driver: clerk until the new driver has served a week; switching back needs no data move.

## 6. Open the pull request

```text
npx mainsdk migrate clerk
```

This records your sign-in and checkout flows as locked flows first, then creates the branch `migrate/clerk`, applies the codemods, adds `.github/workflows/mainsdk-migrate.yml`, runs `mainsdk check` and the locked flows, commits, pushes and opens one pull request with `gh`. If a gate fails, the branch is kept and no pull request opens (`MAIN-MIGRATE-0001`). `--no-pr` stops after the commit.

Locked flows run again in CI: `npx mainsdk migrate flows`.

## Where you end

You end at the reference architecture [`mainsdk/saas-web`](https://mainsdk.dev/docs/guides/reference-saas-web): the same shape, with its tests, benchmark and chaos drills.

```text
npx mainsdk init --template mainsdk/saas-web
```
