Skip to main content
Articles

Add Clerk authentication to a TanStack Start app with the Clerk CLI

Author: Roy Anger
Published:

How do I add Clerk authentication to a TanStack Start app with the Clerk CLI?

Run clerk init --framework tanstack-start against an existing TanStack Start app — the Clerk CLI (released 2026-04-22) wires <ClerkProvider> into __root.tsx, generates catch-all sign-in / sign-up routes, drops a server start.ts with clerkMiddleware() as request middleware, and adds @clerk/tanstack-react-start to your dependencies. Follow that with clerk env pull, clerk doctor, and clerk config patch to manage authentication configuration — passkeys, sign-in methods, session policy — as code. The walkthrough below covers the full flow end-to-end against a fresh @tanstack/cli scaffold, including a sign-in affordance on the landing page and the Core 3 <Show when="signed-in"> component that replaces <SignedIn>.

Validated against TanStack Start 1.167.42, @clerk/tanstack-react-start 1.1.5, Clerk CLI 1.0.2, React 19.2.5, and Vite 8.0.10 on 2026-04-23 — pin equivalent versions if anything drifts. TanStack Start has no middleware.ts/proxy.ts convention like Next.js and many existing tutorials predate the CLI or mix Core 2 component names that Core 3 removed; the CLI output tracks the current SDK surface area instead.

Why the Clerk CLI for TanStack Start

TanStack Start is SSR-first React with server functions and beforeLoad route guards — there's no middleware.ts / proxy.ts convention like Next.js, and the integration points for auth are less obvious the first time you look at a Start project. Most existing tutorials either pre-date the CLI, pre-date @clerk/tanstack-react-start's current shape (older posts reference the renamed @clerk/tanstack-start package), or mix Core 2 component names like <SignedIn> / <SignedOut> that Core 3 removed.

clerk init skips all of that. It detects the framework from package.json, installs the right SDK, writes the provider + middleware + auth-page scaffolding, and seeds .env.local in one shot. Because the CLI is version-current, the output tracks @clerk/tanstack-react-start's current surface area, not whatever shipped eight months ago. If you're coming from the Next.js version of this workflow, the spine is the same — the CLI runs the same way across frameworks — but the files it writes are Start-shaped.

Note

This article shows clerk init running on a project you already scaffolded yourself — the "add auth to my existing app" path. clerk init --starter --framework tanstack-start exists for the opposite case (bootstrap a new template). --starter is a boolean flag that pairs with --framework, not a value-bearing option. The full flag list is in clerk init --help.

Checklist

TanStack Start is currently in Release Candidate: the official overview describes the API as feature-complete but not guaranteed bug-free. Pin a known-good version for production workloads.

Install or update the Clerk CLI

New install — pick whichever fits your machine. The shell one-liner is the fastest path, but Homebrew and a global npm install both work:

terminal
curl -fsSL https://clerk.com/install | sh
terminal
brew install clerk/stable/clerk
terminal
npm install -g clerk

Confirm the version — you want 1.0.2 or later for this tutorial (the 2026-04-22 release):

clerk --version

The CLI has no self-update subcommand. To upgrade, rerun whichever installer you used. The curl installer fetches the latest release by default; pass --canary to track the edge channel. For Homebrew, use the standard upgrade subcommand. For a global npm install, reinstall the @latest tag to pull the newest release:

terminal
curl -fsSL https://clerk.com/install | sh -s -- --canary
terminal
brew upgrade clerk
terminal
npm install -g clerk@latest

Optional but recommended — enable shell completion so subcommand and flag names autocomplete:

terminal
clerk completion zsh > "${fpath[1]}/_clerk"
terminal
clerk completion bash > /etc/bash_completion.d/clerk

Install the Clerk agent skills

If you're using Claude Code, Cursor, or Codex, you have two paths to get the Clerk-maintained skills into your project:

  • Let clerk init do it (recommended). The next step — clerk init — ends with an Install agent skills? prompt that defaults to yes. Accept it, or pass -y to skip the prompt and auto-accept. The CLI then runs npx skills add clerk/skills under the hood.
  • Install manually (if you want skills in a project you're not scaffolding with clerk init, or if you skipped the prompt):
npx skills add clerk/skills

For TanStack Start, clerk init installs three skills: clerk (the CLI + core concepts), clerk-setup (the quickstart surface), and clerk-tanstack-patterns (TanStack-specific auth patterns — loaders, beforeLoad, server functions). The base two ship with every framework; the third is selected by matching @tanstack/react-start in package.json against the CLI's framework → skill map.

Coding agents trained on older Clerk content tend to hallucinate the removed <SignedIn> / <SignedOut> components, the old @clerk/tanstack-start package name, and long-retired patterns. The bundled skills are how you keep them honest.

Note

Clerk ships breaking changes on a regular cadence. Refresh skills by rerunning npx skills add clerk/skills — the command is idempotent and picks up new versions each time. Rerunning clerk init also reinstalls them as part of the flow.

If you already have hand-rolled clerk-* skills under .claude/skills/ from an earlier project, audit them after the install: the Clerk-maintained skills supersede most hand-authored ones and the overlap will confuse your agent. To suppress the prompt inside clerk init (so it doesn't try to reinstall), pass --no-skills.

Log in and pick an application

clerk auth login
clerk whoami

clerk auth login opens the dashboard in a browser, completes OAuth, and persists credentials and config to the OS-standard CLI directory (macOS: ~/Library/Preferences/clerk-cli/config.json, Linux: ~/.config/clerk-cli/config.json, Windows: %APPDATA%\clerk-cli\Config\config.json). Override with CLERK_CONFIG_DIR if you need a custom location, or run clerk doctor to print the resolved path. clerk whoami confirms which account you're operating as — useful when you have multiple logins or switch between personal and work accounts.

List your apps and pick one:

clerk apps list
clerk apps list --json  # machine-readable output, pipe to jq for scripts

If you don't have an app yet, create one from the CLI:

clerk apps create "my-tanstack-app"

Or let clerk init prompt you interactively later. Either path works — the article's validation pre-linked a test app with clerk link --app <id> so clerk init could skip the app-picker prompt.

Scaffold a TanStack Start app with @tanstack/cli

Create a working directory and scaffold Start:

pnpm dlx @tanstack/cli@latest create my-clerk-tanstack-start-app

The TanStack scaffolder is interactive. Accept Tailwind CSS, decline ESLint (add it back later if you want — it's out of scope here), and take defaults for the rest. The version-pinned non-interactive equivalent:

pnpm dlx @tanstack/cli@latest create my-clerk-tanstack-start-app \
  --framework React \
  --package-manager pnpm \
  --no-toolchain \
  --no-examples \
  --no-git \
  --yes

Note

TanStack CLI prompts drift between versions. If the interactive prompts look different from the list above, accept Tailwind and decline ESLint — other toggles don't affect this tutorial. The non-interactive form is more stable if you're automating.

Move into the new app and confirm it boots:

cd my-clerk-tanstack-start-app
pnpm install
pnpm dev

You should see a "Welcome to TanStack Start" page on http://localhost:3000. TanStack Start pins vite dev --port 3000 in the scaffolded package.json. Stop the dev server (Ctrl-C) before the next step — clerk init writes files you'd rather not have HMR-reloaded mid-flight.

The scaffolded structure you care about:

src/
├── router.tsx
├── routes/
│   ├── __root.tsx
│   └── index.tsx
├── routeTree.gen.ts
├── start.ts
└── styles.css

Note the modern src/-rooted layout. Older TanStack Start content still references an app/ layout — that's been replaced. clerk init targets src/ correctly; if you're migrating an older app, move the files first. The scaffold's src/start.ts is where clerk init inserts clerkMiddleware() (see appendix).

Add Clerk with clerk init

From inside the my-clerk-tanstack-start-app/ directory:

clerk init --framework tanstack-start

The CLI auto-detects the framework from package.json, so --framework tanstack-start is technically redundant — but explicit is worth the typing for reproducibility and CI scripts. The package manager is auto-detected from the lockfile the same way. Pass -y for non-interactive mode in CI (accepts the scaffold plan, skips the skills prompt by auto-accepting), or leave it off locally to preview the plan before it writes:

clerk init --framework tanstack-start -y

If you want clerk init to pull keys for a specific app without interactive picking, link the app first:

clerk link --app app_xxx
clerk init --framework tanstack-start -y

clerk link --app <id> writes the link to the CLI config directory. When clerk init runs afterward, link({ skipIfLinked: true }) finds the existing link and skips the prompt, and env pull picks up the linked app's keys. Without a pre-existing link, clerk init runs clerk link interactively so you can pick or create an app during the flow. (clerk init itself has no --app flag — only link, env pull, config, and api do.)

What changes in your project when clerk init runs:

  • package.json — adds "@clerk/tanstack-react-start": "^1.1.5" to dependencies. Note the -react- infix; the older @clerk/tanstack-start package name was renamed.
  • src/routes/__root.tsx — wraps {children} in <ClerkProvider> from @clerk/tanstack-react-start.
  • src/routes/sign-in.$.tsx (new) — catch-all route rendering <SignIn />.
  • src/routes/sign-up.$.tsx (new) — catch-all route rendering <SignUp />.
  • src/start.ts — adds clerkMiddleware() to the requestMiddleware array returned by createStart(). TanStack's scaffold creates src/start.ts with an empty middleware config; clerk init modifies the existing file rather than writing a new one.
  • .env.local — seeded with Clerk env vars. The route URL vars (VITE_CLERK_SIGN_IN_URL, VITE_CLERK_SIGN_UP_URL, and the two _FALLBACK_REDIRECT_URL vars) are written by the framework scaffold step. The keys (VITE_CLERK_PUBLISHABLE_KEY and CLERK_SECRET_KEY) are written by clerk init's built-in env pull, which runs after authentication and link succeed — so real keys land in the file in one pass as long as you're logged in and have either pre-linked an app or picked one at the prompt.

The full file-by-file diff is in the appendix below.

Note

With -y, clerk init rewrites src/routes/__root.tsx without prompting — but it wraps your existing JSX in <ClerkProvider>, it doesn't replace it. The only observed side-effect is cosmetic: the existing <TanStackDevtools> block loses two spaces of indentation and a trailing newline. Run pnpm format (or prettier --write) and the diff disappears. Commit before running the command if you want a clean before/after.

Install the new dependency:

pnpm install

clerk init does not run the install for you. That's intentional — it means you control when your lockfile updates — but easy to forget.

Note

Inside a pnpm monorepo, the scaffolded app will be slurped into the parent workspace unless it has its own sentinel. Add pnpm-workspace.yaml at the top of my-clerk-tanstack-start-app/ (an empty file is fine) to isolate it before you run pnpm install. Standalone projects don't hit this.

If you want to reassign the CLI to a different app after clerk init:

clerk unlink
clerk link --app app_xxx

clerk whoami reflects the currently-linked app and instance, so run it to sanity-check which environment you're pointing at before you make config changes. The CLI stores the active app and secret key reference in the OS-standard CLI config directory (see the previous section for the per-platform path), not in your repo.

Pull (or refresh) environment variables

clerk env pull

clerk init already runs env pull internally once authentication + link succeed, so right after a successful clerk init your .env.local is already populated. clerk env pull is the standalone command for everything afterward — refreshing keys after a rotation, switching between dev and prod (--instance prod), targeting a different app (--app <id>), or repopulating the file after you accidentally deleted it.

TanStack Start uses Vite, which reads VITE_-prefixed env vars on the client — so your publishable key lands as VITE_CLERK_PUBLISHABLE_KEY, not Next's NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY. The CLI handles the prefix difference automatically based on the framework it detects.

After clerk init (or after a manual clerk env pull), .env.local looks like this (real values redacted):

VITE_CLERK_SIGN_IN_URL=/sign-in
VITE_CLERK_SIGN_UP_URL=/sign-up
VITE_CLERK_SIGN_IN_FALLBACK_REDIRECT_URL=/
VITE_CLERK_SIGN_UP_FALLBACK_REDIRECT_URL=/

# Clerk
VITE_CLERK_PUBLISHABLE_KEY=pk_test_...
CLERK_SECRET_KEY=sk_test_...

Two categories of vars here. The four VITE_CLERK_*_URL entries are seeded by clerk init's framework scaffold step (they wire the routes Clerk's components navigate to). The two keys are written by clerk init's built-in env pull against the linked app and can be refreshed any time with a standalone clerk env pull.

Tip

Targeting production? clerk env pull --instance prod pulls prod keys into .env.production.local. Keep dev and prod files separate.

Your CLERK_SECRET_KEY is a secret key — never commit it, never ship it to the client. The default .gitignore that @tanstack/cli writes includes .env*.local, which covers this, but double-check.

Run clerk doctor the first time (it should fail)

Before pulling env vars, clerk doctor is a teaching moment:

mv .env.local .env.local.bak
clerk doctor

Expected output flags the missing keys. Doctor validates the CLI version, auth session, linked app + instance IDs, and .env.local contents — the things that actually break integrations. It's framework-agnostic plumbing validation, not a framework-aware linter. It doesn't lint your start.ts or route files for TanStack-specific wiring.

Restore the file:

mv .env.local.bak .env.local
clerk doctor

Every check should pass. If anything red remains:

  • clerk doctor --spotlight filters output to warnings and failures only — useful when most checks pass and you want to focus on what's broken without scrolling.
  • clerk doctor --fix runs in interactive mode and prompts per issue before applying a fix, then re-runs checks to verify. Skip it in CI; it needs a TTY.
  • clerk doctor --verbose shows the full per-check output. Helpful when a check fails for a non-obvious reason.

Tip

clerk doctor is the fastest way to diagnose "my keys aren't loading" in CI. Wire it into a preflight step alongside pnpm install and you'll catch most env-misconfigurations before they become runtime failures.

Wire up the landing page

clerk init deliberately leaves src/routes/index.tsx alone — it's your landing page, and the CLI doesn't make assumptions about your layout. That means out of the box the app has no sign-in affordance on the home page. Add one:

import { Show, SignInButton, SignUpButton, UserButton } from '@clerk/tanstack-react-start'
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/')({ component: Home })

function Home() {
  return (
    <div className="p-8">
      <header className="mb-8 flex items-center justify-end gap-3">
        <Show when="signed-out">
          <SignInButton mode="modal" />
          <SignUpButton mode="modal" />
        </Show>
        <Show when="signed-in">
          <UserButton />
        </Show>
      </header>
      <h1 className="text-4xl font-bold">Welcome to TanStack Start</h1>
      <p className="mt-4 text-lg">
        Edit <code>src/routes/index.tsx</code> to get started.
      </p>
    </div>
  )
}

Important

Clerk Core 3 (released 2026-03-03) removed <SignedIn>, <SignedOut>, and <Protect> and replaced them with a single <Show> component. Any older TanStack Start tutorial you find on the internet still showing <SignedIn> / <SignedOut> is pre-Core-3 — port it. The Core 3 upgrade guide has a npx @clerk/upgrade codemod that handles most call sites automatically.

This is the minimum viable landing page. The mode="modal" prop opens the sign-in/sign-up components in a modal rather than routing to /sign-in or /sign-up. Drop mode="modal" if you prefer full-page navigation to the catch-all routes clerk init generated.

Start the dev server and sign up a test user

pnpm dev

Open http://localhost:3000. You should see the "Welcome to TanStack Start" header with Sign in / Sign up buttons in the top right. Click Sign up, run through the flow, and you'll land back on the home page with a <UserButton /> in place of the sign-in/sign-up pair.

If the buttons don't render, two things to check before anything else:

  1. Restart the dev server. Vite caches aggressively and new env vars don't always hot-reload.
  2. clerk doctor. Missing publishable key is the single most common cause, and doctor catches it in one shot.

Configure Clerk as code: clerk config

clerk config treats your Clerk instance configuration as data. You can pull the current state, diff it, patch fields, and audit the result. It's the same surface whether you're on dev or prod — add --instance prod when you're ready to push changes upstream.

Start by inspecting the schema:

clerk config schema
clerk config schema --keys auth_passkey session auth_attack_protection

clerk config schema prints the entire config surface. --keys scopes it — useful when you know the key names and just want the shape.

Pull the current config as a baseline:

clerk config pull --output config.before.json

Four patches follow. Run each --dry-run first; the dry run prints the exact shape that would be sent without making changes. Drop --dry-run and add --yes to commit.

Patch 1: block disposable email domains (no paid-plan gate):

clerk config patch --dry-run --json '{"auth_access_control":{"block_disposable_email_domains":true}}'
clerk config patch --json '{"auth_access_control":{"block_disposable_email_domains":true}}' --yes

Patch 2: tighten lockout to 10 failed attempts (no paid-plan gate — the default is 100):

clerk config patch --dry-run --json '{"auth_attack_protection":{"user_lockout":{"max_attempts":10}}}'
clerk config patch --json '{"auth_attack_protection":{"user_lockout":{"max_attempts":10}}}' --yes

Patch 3: enable passkeys as a sign-in factor (paid plan required):

clerk config patch --dry-run --json '{"auth_passkey":{"used_for_sign_in":true}}'
clerk config patch --json '{"auth_passkey":{"used_for_sign_in":true}}' --yes

Important

Passkeys can be registered on any plan; using them as a sign-in factor is a paid-plan feature. The used_for_sign_in: true flip is what unlocks the sign-in affordance in the <SignIn /> component. See pricing.

Patch 4: tune session config (paid plan required):

clerk config patch --dry-run --json '{"session":{"allowed_clock_skew":5,"claims":{},"lifetime":3600}}'
clerk config patch --json '{"session":{"allowed_clock_skew":5,"claims":{},"lifetime":3600}}' --yes

allowed_clock_skew tolerates small time drift between client and server (seconds). claims is where you inject custom session claims. lifetime is the session token lifetime in seconds (3600 = 1 hour).

Important

Session config changes are gated on a paid plan. If you hit a 403 on this patch, that's the plan gate — not a CLI bug.

Pull the new config and diff it:

clerk config pull --output config.after.json
diff config.before.json config.after.json

You should see four blocks: block_disposable_email_domains flipped, max_attempts dropped from 100 to 10, used_for_sign_in flipped to true, and a full session object materialized (it was null before Patch 4).

Warning

clerk config put replaces your entire config with the contents of a JSON file. It's destructive — use patch for day-to-day changes. put is for bootstrapping a new environment from a template, not for tuning.

Verify the config changes worked

Reload the app and walk the sign-up flow again. A few things to confirm:

  • Disposable-email blocking. Try signing up with a @mailinator.com address. The signup should be rejected at the email step.
  • Lockout. Fail sign-in 10 times on purpose. The account should lock.
  • Passkey sign-in. Sign in with your test user, open <UserProfile /> (add a /user route rendering <UserProfile /> if you don't have one yet), go to the Security tab, and register a passkey. Sign out, then sign in again — "Continue with passkey" should appear above the email field. Validated with platform authenticators (Touch ID, Windows Hello) and Bitwarden.
  • Session lifetime. Signed-in sessions now expire after 1 hour instead of the default. Easy to verify in a long-running tab; easy to forget in dev unless you leave one open.

Inspect your instance with clerk api

clerk api is a direct wrapper around the Clerk Backend API and (with --platform) the Clerk Platform API. It authenticates with your linked app's keys, so you don't need to craft curl commands or copy CLERK_SECRET_KEY into Postman.

List available endpoints and commands:

clerk api ls
clerk api ls users

List users:

clerk api /users
clerk api /users?limit=5

Fetch a single user:

clerk api /users/<user_id>

The Platform API (cross-instance application management) lives behind --platform. The CLI auto-prepends /v1 and points at the Platform API host (api.clerk.com). Platform resources are namespaced under /platform/, so the full path for listing applications is /platform/applications — the CLI resolves this to api.clerk.com/v1/platform/applications:

clerk api --platform /platform/applications

Backend API calls (no --platform) hit api.clerk.dev instead, and their paths do not need the /platform/ prefix — /users resolves to api.clerk.dev/v1/users, /organizations to api.clerk.dev/v1/organizations.

Note

If a --platform call returns clerk_key_invalid, the usual cause is a missing auth token rather than a bad path — clerk api --platform uses the OAuth token from clerk auth login, not CLERK_SECRET_KEY. Re-run clerk auth login if the token has expired. If you get 404, double-check the path against the Platform API spec: Platform resources need the /platform/ segment, Backend resources do not.

clerk api isn't a replacement for the full Backend SDK in application code — it's for one-off inspection, ops, and scripts.

Appendix: what clerk init wrote for you

If you ever need to reproduce clerk init's output by hand — porting to a non-supported framework, or just curious — here's the exact surface. Validated against TanStack Start 1.167.42 + @clerk/tanstack-react-start@1.1.5.

package.json — one dependency added:

{
  "dependencies": {
    "@clerk/tanstack-react-start": "^1.1.5"
  }
}

src/routes/__root.tsx — existing content wrapped in <ClerkProvider> (reformatted by pnpm format):

import { ClerkProvider } from '@clerk/tanstack-react-start'
import { HeadContent, Scripts, createRootRoute } from '@tanstack/react-router'
import { TanStackRouterDevtoolsPanel } from '@tanstack/react-router-devtools'
import { TanStackDevtools } from '@tanstack/react-devtools'

import appCss from '../styles.css?url'

export const Route = createRootRoute({
  head: () => ({
    meta: [
      { charSet: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' },
      { title: 'TanStack Start Starter' },
    ],
    links: [{ rel: 'stylesheet', href: appCss }],
  }),
  shellComponent: RootDocument,
})

function RootDocument({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <head>
        <HeadContent />
      </head>
      <body>
        <ClerkProvider>
          {children}
          <TanStackDevtools
            config={{ position: 'bottom-right' }}
            plugins={[
              {
                name: 'Tanstack Router',
                render: <TanStackRouterDevtoolsPanel />,
              },
            ]}
          />
          <Scripts />
        </ClerkProvider>
      </body>
    </html>
  )
}

src/routes/sign-in.$.tsx (new) — catch-all route so <SignIn /> handles any sub-path (Clerk uses this for flow steps like /sign-in/factor-two):

import { SignIn } from '@clerk/tanstack-react-start'
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/sign-in/$')({
  component: Page,
})

function Page() {
  return (
    <div className="flex min-h-screen items-center justify-center">
      <SignIn />
    </div>
  )
}

src/routes/sign-up.$.tsx (new) — mirror of sign-in for sign-up:

import { SignUp } from '@clerk/tanstack-react-start'
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/sign-up/$')({
  component: Page,
})

function Page() {
  return (
    <div className="flex min-h-screen items-center justify-center">
      <SignUp />
    </div>
  )
}

src/start.ts (modified) — Clerk's request middleware slotted into TanStack Start's server instance (TanStack's scaffold creates this file; clerk init adds the clerkMiddleware() call and the @clerk/tanstack-react-start/server import):

import { clerkMiddleware } from '@clerk/tanstack-react-start/server'
import { createStart } from '@tanstack/react-start'

export const startInstance = createStart(() => {
  return {
    requestMiddleware: [clerkMiddleware()],
  }
})

This is the TanStack Start equivalent of Next's proxy.ts / middleware — the hook-point where Clerk resolves the session for every server-side request. createStart takes a callback that returns the config, not a plain object. clerkMiddleware() populates getAuth(req) inside server functions and loaders so you can gate them with beforeLoad or by checking userId before returning data.

.env.local — the four route URL vars come from the framework scaffold step, the two key vars come from clerk init's built-in env pull (real values land whenever you pre-link an app with clerk link --app <id> or pick an app at the interactive prompt):

VITE_CLERK_SIGN_IN_URL=/sign-in
VITE_CLERK_SIGN_UP_URL=/sign-up
VITE_CLERK_SIGN_IN_FALLBACK_REDIRECT_URL=/
VITE_CLERK_SIGN_UP_FALLBACK_REDIRECT_URL=/

# Clerk
VITE_CLERK_PUBLISHABLE_KEY=pk_test_...
CLERK_SECRET_KEY=sk_test_...

What clerk init does not touch: src/routes/index.tsx (landing page), vite.config.ts, src/router.tsx, src/styles.css, README.md. That's why the "wire up the landing page" step exists — you're filling in the piece the CLI intentionally left under your control.

CLI reference (quick skim)

Commands the article touched:

CommandWhat it does
clerk auth loginOAuth login, persists to the OS-standard CLI config directory (override with CLERK_CONFIG_DIR).
clerk whoamiShow current user + linked app.
clerk apps listList apps in your org (add --json for machine-readable output).
clerk apps create <name>Create a new app from the CLI.
clerk init --framework tanstack-startInstall SDK + wire providers/routes/middleware. Auto-detects framework/package manager when the flag is omitted.
clerk link --app app_xxxPoint the CLI at an existing app (run before clerk init to skip the app-picker).
clerk unlinkUnlink the current app (add --yes to skip the confirmation prompt).
clerk env pullWrite env vars to .env.local (or .env.production.local with --instance prod; target a specific app with --app <id>).
clerk doctorValidate auth + linked app + env vars.
clerk config schemaPrint the full config schema.
clerk config pull --output file.jsonSnapshot current config.
clerk config patch --json '...' --dry-runPreview a partial update.
clerk config patch --json '...' --yesCommit the update.
clerk config put --file file.jsonDestructive — replace config from file.
clerk api /usersQuery the Clerk Backend API at api.clerk.dev (CLI auto-prepends /v1).
clerk api --platform /platform/applicationsQuery the Platform API at api.clerk.com (CLI auto-prepends /v1; Platform resources live under /platform/).
clerk completion zshPrint shell completion (pipe to your completion dir).
npx skills add clerk/skillsInstall/refresh the Clerk agent skills. Run inside clerk init or manually.

Flags worth memorizing:

  • --yes / -y — non-interactive; accept defaults.
  • --dry-run — print the operation without executing (config patch, config put, api).
  • --instance prod — target production instead of dev.
  • --app app_xxx — scope the command to a specific app (valid on link, env pull, config *, apinot on init).
  • --no-skills — skip the agent-skills prompt inside clerk init.

To upgrade the CLI itself, rerun the installer (curl -fsSL https://clerk.com/install | sh, brew upgrade clerk, or npm install -g clerk@latest). There is no clerk update subcommand in 1.0.2.

Full reference: Clerk CLI docs.

FAQ