Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
f74816d
feat(migrate): make clerk migrate generally available
royanger Oct 6, 2026
86a8be1
docs: list clerk migrate in the README and match its help
royanger Oct 6, 2026
34774b5
docs(changeset): Make clerk migrate available without CLERK_EXPERIMENTAL
royanger Oct 6, 2026
b98d1e1
docs(changeset): stop calling clerk migrate experimental in the earli…
royanger Oct 14, 2026
e020a2c
fix(migrate): point migration doc links at the per-platform guides
royanger Oct 28, 2026
5665983
fix(migrate): point the custom-source link at the migration guide
royanger Oct 29, 2026
f4a99e4
test(completion): check completion offers migrate and its subcommands
royanger Oct 30, 2026
6dbe8b6
docs(changeset): add the Oxford commas to the migrate changesets
royanger Oct 30, 2026
4fecd43
docs(migrate): say the CLI redacts a libsql token, in the active voice
royanger Oct 30, 2026
ad744f1
refactor: keep the CLERK_EXPERIMENTAL gate, and document it
royanger Oct 30, 2026
7c3f02e
docs(migrate): say "auth platform" in the migrate help and README
royanger Oct 30, 2026
f7d8d41
docs(migrate): add the missing Oxford commas
royanger Oct 30, 2026
323272a
docs(migrate): say "auth platform" in the --reserve-unverified text
royanger Oct 30, 2026
0191411
docs(migrate): drop the save-location prompt from export clerk's example
royanger Oct 30, 2026
397e6e0
docs(migrate): say --service-account takes the key's JSON too
royanger Oct 30, 2026
6aba1b0
docs(migrate): correct the dry-run exit codes, Firebase key errors, a…
royanger Oct 30, 2026
b3d1987
docs(migrate): name --source in export clerk's comment
royanger Oct 30, 2026
244d53f
docs(migrate): say Firebase phones import verified, and why
royanger Oct 30, 2026
ad56ec2
docs: match the experimental @example to the migrate stub
royanger Oct 30, 2026
b3af8be
docs(migrate): say a continued run stays in an import's JSON
royanger Oct 30, 2026
403accf
docs(migrate): say which imports --require-password still writes a ru…
royanger Nov 2, 2026
9c59d5e
docs(migrate): drop the README pointer from the release changeset
royanger Nov 2, 2026
c65ff22
docs(changeset): stop calling clerk migrate experimental in the final…
royanger Nov 5, 2026
a8e2d31
docs(migrate): steer --service-account to the key file's path
royanger Nov 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .changeset/migrate-custom-sources.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
"clerk": minor
---

Add `clerk migrate sources`, and let `clerk migrate import --source` take the path to a source you wrote. `clerk migrate sources` lists the built-in sources with the passwords, MFA and metadata each brings across, and `clerk migrate sources <source>` shows where each field lands, how to export, and any caveats. A source you wrote is a `.ts` or `.js` file in your project, checked before use, so a platform with no built-in source can be imported without rebuilding the CLI. `clerk migrate` is still experimental and needs `CLERK_EXPERIMENTAL=migrate`.
Add `clerk migrate sources`, and let `clerk migrate import --source` take the path to a source you wrote. `clerk migrate sources` lists the built-in sources with the passwords, MFA, and metadata each brings across, and `clerk migrate sources <source>` shows where each field lands, how to export, and any caveats. A source you wrote is a `.ts` or `.js` file in your project, checked before use, so a platform with no built-in source can be imported without rebuilding the CLI.
2 changes: 1 addition & 1 deletion .changeset/migrate-final-testing-fixes.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
"clerk": patch
---

`clerk migrate import` now warns about users it created without an email or phone Clerk refused, in the summary, `--json`, and `clerk migrate runs <id>`, which also warns about users imported without their first phone. An attach that fails on the key or a `429` that outlasts the retries stays pending for a re-run, with a note saying why, and one Clerk calls taken counts as attached when the user already holds it. A secret key Clerk rejects gets Clerk's error and says where the key came from. A refused dry run says so and gives the same next steps as the real run. A run undone in full no longer blocks an import when Clerk can't name the instance. A gateway's HTML error page no longer fills the error breakdown. The checks explain how to replace `APP_ID` in fix commands, and `--json` carries the notes the summary prints. `clerk migrate` is still experimental and needs `CLERK_EXPERIMENTAL=migrate`.
`clerk migrate import` now warns about users it created without an email or phone Clerk refused, in the summary, `--json`, and `clerk migrate runs <id>`, which also warns about users imported without their first phone. An attach that fails on the key or a `429` that outlasts the retries stays pending for a re-run, with a note saying why, and one Clerk calls taken counts as attached when the user already holds it. A secret key Clerk rejects gets Clerk's error and says where the key came from. A refused dry run says so and gives the same next steps as the real run. A run undone in full no longer blocks an import when Clerk can't name the instance. A gateway's HTML error page no longer fills the error breakdown. The checks explain how to replace `APP_ID` in fix commands, and `--json` carries the notes the summary prints.
2 changes: 1 addition & 1 deletion .changeset/migrate-reserve-unverified.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
"clerk": minor
---

Add `clerk migrate import --reserve-unverified`, which creates the emails and phones a source never verified as reserved (usable for sign-in, locked to the user) instead of unverified. At a terminal, the import asks. `clerk migrate` is still experimental and needs `CLERK_EXPERIMENTAL=migrate`.
Add `clerk migrate import --reserve-unverified`, which creates the emails and phones an auth platform never verified as reserved (usable for sign-in, locked to the user) instead of unverified. At a terminal, the import asks.
2 changes: 1 addition & 1 deletion .changeset/migrate-user-limit.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
"clerk": patch
---

Check `clerk migrate import` against the user limit Clerk reports for the instance, so a development instance Clerk has raised no longer rejects users it has room for. `CLERK_MIGRATE_DEV_USER_LIMIT` is now only a fallback for when Clerk doesn't report a limit, and `--json` says where the limit came from. `clerk migrate` is still experimental and needs `CLERK_EXPERIMENTAL=migrate`.
Check `clerk migrate import` against the user limit Clerk reports for the instance, so a development instance Clerk has raised no longer rejects users it has room for. `CLERK_MIGRATE_DEV_USER_LIMIT` is now only a fallback for when Clerk doesn't report a limit, and `--json` says where the limit came from.
9 changes: 9 additions & 0 deletions .changeset/migrate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"clerk": minor
---

Make `clerk migrate` available without `CLERK_EXPERIMENTAL=migrate`: it now shows in `clerk --help` and shell completion.

- `clerk migrate export` gets users out of Clerk, Supabase, Firebase, Auth0, WorkOS, Better Auth, or Auth.js into a run, and `clerk migrate import` checks every user against your instance, asks before it writes, and continues a run that stopped partway.
- `clerk migrate runs` shows what each run did, `clerk migrate undo` deletes the users an import created, and `clerk migrate sources` shows what each source brings across. `--source ./my-source.ts` imports with a source you wrote.
- Firebase phones import verified, because Firebase has no phone-verified field.
12 changes: 12 additions & 0 deletions .claude/rules/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,18 @@ When creating or modifying a command, evaluate whether it needs an agent mode. C

When adding or modifying a command, ensure tab-completion stays correct. See `.claude/rules/completion.md` for the full guide. Short version: use `.choices()` on arguments and options so completions are automatic; only touch `__complete.ts` when `.choices()` is not suitable.

## Shipping a command behind `CLERK_EXPERIMENTAL`

A command can merge to `main`, and reach `@canary` and releases, before it's ready, behind a name in `CLERK_EXPERIMENTAL`. `src/lib/experimental.ts` holds the gate: `isExperimentEnabled(name)` and `requireExperiment(name)`. `clerk migrate` shipped this way, one slice per PR.

To gate a command:

1. In its `register<Name>`, check `isExperimentEnabled(name, env)` before building the tree. When it's off, register a hidden stub instead, and return: `program.command(name, { hidden: true })` with `.helpOption(false)`, `.allowUnknownOption()`, `.allowExcessArguments()`, `.argument("[args...]")`, and an action that calls `requireExperiment(name, env)`. Override the stub's `helpInformation` to call `requireExperiment` too, so `clerk help <name>` refuses. The stub has no subcommands, so it stays out of `clerk --help` and completion, and every invocation exits 2 with `experiment_disabled`, naming the variable to set.
2. Set `CLERK_EXPERIMENTAL=<name>` in the command's tests and E2E runs.
3. Say so in its README and every changeset: the command is experimental and needs `CLERK_EXPERIMENTAL=<name>`.

To ship it, remove the stub and the tests' `CLERK_EXPERIMENTAL` setup, add the command to the root README's help output, and write a changeset that announces it. Drop the "experimental" sentence from any of its earlier changesets that haven't been released yet. Keep `experimental.ts` for the next gated command.

## Root README

`README.md` at the project root contains the CLI help output. When commands are added, removed, or their options change, update the help output in `README.md` to stay in sync. You can regenerate it by running `bun run src/cli.ts --help`.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ Commands:
init [options] Initialize Clerk in your project
link [options] Link this project to a Clerk application
mcp Manage the Clerk remote MCP server connection for AI editors and CLIs
migrate Migrate users into Clerk from another auth platform or another Clerk instance
open Open Clerk resources in your browser
security Audit an instance against Clerk's security recommendations
telemetry Control CLI usage telemetry (status, disable, enable)
Expand Down
2 changes: 1 addition & 1 deletion packages/cli-core/src/commands/init/scan.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ describe("detectAuthLibraries", () => {
runDetectAuthLibraries({ "next-auth": "5.0.0", next: "15.0.0" });
const output = captured.out;
expect(output).toContain("NextAuth");
expect(output).toContain("clerk.com/docs/migrations/nextauth");
expect(output).toContain("clerk.com/docs/guides/development/migrating/authjs");
});

test("detects Auth0 via @auth0/nextjs-auth0", () => {
Expand Down
18 changes: 9 additions & 9 deletions packages/cli-core/src/commands/init/scan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,32 +27,32 @@ const AUTH_LIBRARY_SCANS: AuthLibraryScan[] = [
{
packages: ["next-auth"],
name: "NextAuth / Auth.js",
docsUrl: "https://clerk.com/docs/migrations/nextauth",
docsUrl: "https://clerk.com/docs/guides/development/migrating/authjs",
},
{
packages: ["@auth0/nextjs-auth0", "auth0"],
name: "Auth0",
docsUrl: "https://clerk.com/docs/migrations/auth0",
docsUrl: "https://clerk.com/docs/guides/development/migrating/auth0",
Comment thread
royanger marked this conversation as resolved.
Resolved
},
{
packages: ["@supabase/ssr", "@supabase/auth-helpers-nextjs"],
name: "Supabase Auth",
docsUrl: "https://clerk.com/docs/migrations/supabase",
docsUrl: "https://clerk.com/docs/guides/development/migrating/supabase",
},
{
packages: ["firebase"],
name: "Firebase",
docsUrl: "https://clerk.com/docs/migrations/firebase",
docsUrl: "https://clerk.com/docs/guides/development/migrating/firebase",
},
{
packages: ["passport"],
name: "Passport.js",
docsUrl: "https://clerk.com/docs/migrations/overview",
docsUrl: "https://clerk.com/docs/guides/development/migrating/custom-source",
Comment thread
manovotny marked this conversation as resolved.
Resolved
},
{
packages: ["better-auth"],
name: "Better Auth",
docsUrl: "https://clerk.com/docs/migrations/overview",
docsUrl: "https://clerk.com/docs/guides/development/migrating/better-auth",
},
{
// AuthKit only: `@workos-inc/node` also serves SSO, Directory Sync and
Expand All @@ -65,12 +65,12 @@ const AUTH_LIBRARY_SCANS: AuthLibraryScan[] = [
"@workos-inc/authkit-js",
],
name: "WorkOS",
docsUrl: "https://clerk.com/docs/guides/development/migrating/overview",
docsUrl: "https://clerk.com/docs/guides/development/migrating/workos",
},
{
packages: ["@kinde-oss/kinde-auth-nextjs"],
name: "Kinde",
docsUrl: "https://clerk.com/docs/migrations/overview",
docsUrl: "https://clerk.com/docs/guides/development/migrating/custom-source",
},
];

Expand Down Expand Up @@ -129,7 +129,7 @@ const CODE_SCANS: CodeScan[] = [
// out. The `sso` and `directorySync` modules alone hold no users.
pattern: "\\bworkos\\.userManagement\\b",
message: "WorkOS User Management still in use",
docsUrl: "https://clerk.com/docs/guides/development/migrating/overview",
docsUrl: "https://clerk.com/docs/guides/development/migrating/workos",
},
{
pattern: "getServerSession\\s*\\(",
Expand Down
Loading
Loading