---
title: "clerk/skills"
description: "AI Skills to enhance working with Clerk"
source: https://github.com/clerk/skills
ref: main
license: null
licenseName: "No licence detected"
canonical: https://skillsdocs.com/clerk/skills
base: https://github.com/clerk/skills/blob/main/
chapters: 22
inlined: 22
withheld: 0
words: 16917
updated: 2026-08-06T18:23:21Z
generator: "Skills Docs"
---

> **clerk/skills** — every Agent Skill in this repository, inlined verbatim.
>
> Canonical HTML: https://skillsdocs.com/clerk/skills
> Per-chapter Markdown: https://skillsdocs.com/clerk/skills/<skill>.md
> Machine manifest: https://skillsdocs.com/clerk/skills/.well-known/agent-skills/index.json
> JSON: https://skillsdocs.com/api/v1/books/clerk/skills
> Install: `npx skills add clerk/skills`
> Upstream: https://github.com/clerk/skills @ `main`
> Licence: No licence detected — all rights reserved by its authors
>
> Content is mirrored from GitHub and © its authors, served unmodified. Takedown: https://github.com/DreambaseAI/skillsdocs/issues/new?labels=takedown&title=Takedown+request

# clerk/skills

AI Skills to enhance working with Clerk

- **Chapters:** 22
- **Inlined:** 22 (licence detected)
- **Words:** 16,917
- **Reading time:** 80 min
- **Stars:** 66

## Table of contents

1. [audit-clerk-skill](https://skillsdocs.com/clerk/skills/audit-clerk-skill.md) — Audits the bundled `clerk-cli` skill against the Clerk CLI source tree and proposes or applies updates. Use when the user says "audit the clerk-cli skill", "up…
2. [audit-expo-skill](https://skillsdocs.com/clerk/skills/audit-expo-skill.md) — Audits the bundled `clerk-expo` skill against the @clerk/expo SDK source and clerk-docs, then proposes or applies updates. Use when the user says "audit the ex…
3. [clerk-backend-api](https://skillsdocs.com/clerk/skills/clerk-backend-api.md) — Clerk Backend REST API explorer and executor. Browse tags, inspect endpoint schemas, and execute authenticated requests. Use when listing users, managing organ…
4. [clerk-cli](https://skillsdocs.com/clerk/skills/clerk-cli.md) — Operate the Clerk CLI (`clerk` binary) for authentication, user/org/session management, impersonation, local webhook testing, deploy verification, instance con…
5. [clerk-custom-ui](https://skillsdocs.com/clerk/skills/clerk-custom-ui.md) — Custom authentication flows and component appearance - hooks (useSignIn, useSignUp), themes, colors, fonts, CSS. Use for custom sign-in/sign-up flows, appearan…
6. [clerk-setup](https://skillsdocs.com/clerk/skills/clerk-setup.md) — Add Clerk authentication to any project by following the official quickstart guides.
7. [clerk](https://skillsdocs.com/clerk/skills/clerk.md) — Clerk authentication router. Use when user asks about Clerk CLI operations, adding authentication, setting up Clerk, custom sign-in flows, Swift or native iOS…
8. [clerk-billing](https://skillsdocs.com/clerk/skills/clerk-billing.md) — Clerk Billing for subscription management - render Clerk's PricingTable and in-app checkout drawer, configure subscription plans, seat-limit plans for B2B, fea…
9. [clerk-orgs](https://skillsdocs.com/clerk/skills/clerk-orgs.md) — Clerk Organizations for B2B SaaS - create multi-tenant apps with org switching, role-based access, verified domains, and enterprise SSO. Use for team workspace…
10. [clerk-testing](https://skillsdocs.com/clerk/skills/clerk-testing.md) — E2E testing for Clerk apps. Use with Playwright or Cypress for auth flow tests.
11. [clerk-webhooks](https://skillsdocs.com/clerk/skills/clerk-webhooks.md) — Clerk webhooks for real-time events and data syncing. Verify with verifyWebhook from the framework-specific package. Handle user, session, organization, billin…
12. [clerk-astro-patterns](https://skillsdocs.com/clerk/skills/clerk-astro-patterns.md) — Astro patterns with Clerk — middleware, SSR pages, island components, API routes, static vs SSR rendering. Triggers on: astro clerk, clerk astro middleware, as…
13. [clerk-chrome-extension-patterns](https://skillsdocs.com/clerk/skills/clerk-chrome-extension-patterns.md) — Chrome Extension auth with @clerk/chrome-extension -- popup/sidepanel setup, syncHost for OAuth/SAML via web app, createClerkClient for service workers and hea…
14. [clerk-nextjs-patterns](https://skillsdocs.com/clerk/skills/clerk-nextjs-patterns.md) — Advanced Next.js patterns - middleware, Server Actions, caching with Clerk.
15. [clerk-nuxt-patterns](https://skillsdocs.com/clerk/skills/clerk-nuxt-patterns.md) — Nuxt 3 auth patterns with @clerk/nuxt - middleware, composables, server API routes, SSR. Triggers on: Nuxt auth, useAuth composable, clerkMiddleware Nuxt, serv…
16. [clerk-react-patterns](https://skillsdocs.com/clerk/skills/clerk-react-patterns.md) — React SPA auth patterns with @clerk/react for Vite/CRA - ClerkProvider setup, useAuth/useUser/useClerk hooks, React Router protected routes, custom sign-in flo…
17. [clerk-react-router-patterns](https://skillsdocs.com/clerk/skills/clerk-react-router-patterns.md) — React Router v7/v8 patterns with Clerk — rootAuthLoader, getAuth in loaders, clerkMiddleware, protected routes, SSR user data, org switching. Triggers on: reac…
18. [clerk-tanstack-patterns](https://skillsdocs.com/clerk/skills/clerk-tanstack-patterns.md) — TanStack React Start auth patterns with @clerk/tanstack-react-start - createServerFn, beforeLoad guards, loaders, Vinxi server. Triggers on: TanStack auth, cre…
19. [clerk-vue-patterns](https://skillsdocs.com/clerk/skills/clerk-vue-patterns.md) — Vue 3 patterns with Clerk — composables (useAuth, useUser, useClerk, useOrganization), Vue Router guards, Pinia auth store integration. Triggers on: vue clerk,…
20. [clerk-android](https://skillsdocs.com/clerk/skills/clerk-android.md) — Implement Clerk authentication for native Android apps using Kotlin and Jetpack Compose with clerk-android source-guided patterns. Use for prebuilt AuthView/Us…
21. [clerk-expo](https://skillsdocs.com/clerk/skills/clerk-expo.md) — Add Clerk authentication to Expo and React Native apps using @clerk/expo. Use for Expo setup, prebuilt native components (AuthView, UserButton), custom sign-in…
22. [clerk-swift](https://skillsdocs.com/clerk/skills/clerk-swift.md) — Implement Clerk authentication for native Swift and iOS apps using ClerkKit and ClerkKitUI source-guided patterns. Use for prebuilt AuthView or custom native f…


## Front matter

_The repository README, verbatim except that relative links are resolved against https://github.com/clerk/skills/blob/main/._

<p align="center">
  <a href="https://clerk.com?utm_source=github&utm_medium=clerk_skills" target="_blank" rel="noopener noreferrer">
    <img src="./assets/logo-pixel.svg" height="84">
  </a>
  <br />
</p>
<div align="center">
  <h1>
    Clerk Skills
  </h1>
  <a href="https://clerk.com/docs">
    <img alt="Documentation" src="https://img.shields.io/badge/documentation-clerk-green.svg" />
  </a>
  <a href="https://clerk.com/discord">
    <img alt="Discord" src="https://img.shields.io/discord/856971667393609759?color=7389D8&label&logo=discord&logoColor=ffffff" />
  </a>
  <a href="https://x.com/clerk">
    <img alt="Follow on X" src="https://img.shields.io/twitter/url.svg?label=%40clerk&style=social&url=https%3A%2F%2Fx.com%2Fclerk" />
  </a>
  <br />
  <br />
  <p>
    <strong>
      Skills to help AI coding agents work more effectively with Clerk.
    </strong>
  </p>
</div>

---

Skills follow the [Agent Skills](https://agentskills.io/) format.

## Install

### Agent Skills

```bash
npx skills add clerk/skills
```

### Codex

```bash
codex plugin marketplace add clerk/skills
```

After adding the marketplace, restart Codex, open `/plugins`, select
**Clerk Skills**, install and enable `clerk-skills`, then start a new thread.

### Manual (Claude Code)

```bash
git clone https://github.com/clerk/skills ~/.claude/skills/clerk
```

## Skills

### Core

| Skill | Purpose | When to Use |
|-------|---------|-------------|
| `/clerk` | **Router** - Routes to the right skill | Always start here |
| `clerk-cli` | Clerk CLI operations | Users, orgs, apps, env keys, deploy checks |
| `clerk-setup` | Add Clerk to any framework | New projects, framework setup |
| `clerk-custom-ui` | Custom sign-in/up and appearance | Building custom forms, styling |
| `clerk-backend-api` | Backend REST API explorer | Browsing or calling API endpoints |

### Framework Patterns

| Skill | Framework | Patterns |
|-------|-----------|----------|
| `clerk-nextjs-patterns` | Next.js | Middleware, Server Actions, caching |
| `clerk-react-patterns` | React | Hooks, auth guards, protected routes |
| `clerk-react-router-patterns` | React Router | Loaders, actions, route protection |
| `clerk-vue-patterns` | Vue | Composables, Pinia integration |
| `clerk-nuxt-patterns` | Nuxt | Server middleware, SSR auth |
| `clerk-astro-patterns` | Astro | SSR auth, island components |
| `clerk-tanstack-patterns` | TanStack Start | Server functions, route protection |
| `clerk-chrome-extension-patterns` | Chrome Extension | Background scripts, popup auth |

### Features

| Skill | Purpose | When to Use |
|-------|---------|-------------|
| `clerk-orgs` | Multi-tenant B2B organizations | Team workspaces, RBAC |
| `clerk-billing` | Subscription billing and feature gating | Pricing tables, plans, per-seat, entitlements |
| `clerk-webhooks` | Real-time events and data syncing | Database sync, notifications |
| `clerk-testing` | E2E testing for auth flows | Playwright/Cypress tests |

### Native Mobile

| Skill | Platform | Patterns |
|-------|----------|----------|
| `clerk-swift` | iOS | ClerkKit, SwiftUI, Apple Sign-In |
| `clerk-android` | Android | Kotlin, Jetpack Compose |
| `clerk-expo` | Expo / React Native | Native components, custom flows, SMS OTP, OAuth |

## Quick Start

### 1. Set Up API Keys

Get your keys from the [Clerk Dashboard](https://dashboard.clerk.com/) and add them to `.env`:

```bash
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_xxx
CLERK_SECRET_KEY=sk_test_xxx
```

### 2. Ask Your Agent

| You Say | Skill Used |
|---------|------------|
| "List Clerk users" | `clerk-cli` |
| "Add Clerk auth to my Next.js app" | `clerk-setup` |
| "Use Server Actions with Clerk" | `clerk-nextjs-patterns` |
| "Add Clerk to my Vue app" | `clerk-vue-patterns` |
| "Add Clerk to my Nuxt app" | `clerk-nuxt-patterns` |
| "Add auth to my Expo app" | `clerk-expo` |
| "Add phone SMS auth to my Expo app" | `clerk-expo` |
| "Add Clerk to my Astro site" | `clerk-astro-patterns` |
| "Build custom sign-in form" | `clerk-custom-ui` |
| "Sync users to Prisma via webhooks" | `clerk-webhooks` |
| "Add Playwright tests for auth" | `clerk-testing` |
| "Set up organizations for my B2B app" | `clerk-orgs` |
| "Add subscription billing with pricing table" | `clerk-billing` |
| "Gate features by plan" | `clerk-billing` |
| "Add Clerk auth to my iOS app" | `clerk-swift` |
| "Add Clerk auth to my Android app" | `clerk-android` |

## Repository Structure

```
clerk-skills/
├── .agents/
│   └── plugins/
│       └── marketplace.json
├── .codex-plugin/
│   └── plugin.json
├── .claude-plugin/
│   └── marketplace.json
├── skills/
│   ├── core/
│   │   ├── clerk/                  # Router skill
│   │   ├── clerk-cli/              # CLI operations
│   │   ├── clerk-setup/            # Framework setup
│   │   ├── clerk-custom-ui/        # Component customization
│   │   └── clerk-backend-api/      # REST API explorer
│   ├── frameworks/
│   │   ├── clerk-nextjs-patterns/
│   │   ├── clerk-react-patterns/
│   │   ├── clerk-react-router-patterns/
│   │   ├── clerk-vue-patterns/
│   │   ├── clerk-nuxt-patterns/
│   │   ├── clerk-astro-patterns/
│   │   ├── clerk-tanstack-patterns/
│   │   └── clerk-chrome-extension-patterns/
│   ├── features/
│   │   ├── clerk-orgs/
│   │   ├── clerk-billing/
│   │   ├── clerk-webhooks/
│   │   └── clerk-testing/
│   └── mobile/
│       ├── clerk-swift/
│       ├── clerk-android/
│       └── clerk-expo/
└── README.md
```

## Resources

- [Clerk Docs](https://clerk.com/docs)
- [Dashboard](https://dashboard.clerk.com)
- [Discord](https://clerk.com/discord)

## Request a Skill

Don't see what you need? [Request a skill](https://github.com/clerk/skills/issues/new?template=skill-request.md).

## License

MIT

---

<!-- chapter:begin slug=audit-clerk-skill position=1 -->

## 1. audit-clerk-skill

- **Source:** https://github.com/clerk/skills/blob/main/.agents/skills/audit-clerk-skill/SKILL.md
- **Raw:** https://raw.githubusercontent.com/clerk/skills/main/.agents/skills/audit-clerk-skill/SKILL.md
- **Markdown:** https://skillsdocs.com/clerk/skills/audit-clerk-skill.md
- **Licence:** MIT — https://spdx.org/licenses/MIT.html

<!-- Verbatim upstream SKILL.md follows, YAML frontmatter included. -->

---
name: audit-clerk-skill
description: Audits the bundled `clerk-cli` skill against the Clerk CLI source tree and proposes or applies updates. Use when the user says "audit the clerk-cli skill", "update the clerk-cli skill", "check clerk-cli against the CLI source", "resync clerk-cli skill", "run audit-clerk-skill", or after Clerk CLI commands, flags, or agent-mode behavior change.
license: MIT
effort: high
user-invocable: true
disable-model-invocation: true
argument-hint: "[--source <path>] [--apply]"
metadata:
  internal: true
---

# Clerk CLI Skill Audit

Cross-check `skills/core/clerk-cli/` in this repository against the actual Clerk CLI source and propose precise edits where the skill has drifted. The CLI is the source of truth; the skill is maintainer documentation that must track it.

This task needs a complete command inventory, not a quick grep pass. Build the source model first, then compare it to the skill's current claims.

## Inputs

- **CLI source of truth**: a `clerk/cli` checkout containing `packages/cli-core/src/commands/**`, plus `packages/cli-core/src/cli.ts`, `cli-program.ts`, `mode.ts`, and any referenced files in `packages/cli-core/src/lib/`.
- **Target skill**: `skills/core/clerk-cli/SKILL.md` and `skills/core/clerk-cli/references/*.md`.

## Source Checkout Resolution

Resolve the CLI source checkout in this order:

1. Use `--source <path>` when supplied. The path must contain `packages/cli-core/src/commands/`.
2. If the current repository itself contains `packages/cli-core/src/commands/`, use the current repository root.
3. If `CLERK_CLI_REPO` is set, use that path. Do not print the variable value if it contains sensitive path fragments.
4. In Conductor, look for exactly one sibling workspace matching `../cli/*/packages/cli-core/src/commands/`. If multiple match, ask which one to use.
5. If no checkout is available and network access is acceptable for the task, clone the public CLI repository into `.context/clerk-cli-source`:

```sh
mkdir -p .context
cd .context
git clone --depth 1 https://github.com/clerk/cli.git clerk-cli-source
```

If none of these work, stop and ask the user for a CLI source path. Do not audit against the installed `clerk` binary alone, because the binary may be stale and does not expose every source-level branch.

## Workflow

### 1. Inventory the CLI

Walk `packages/cli-core/src/commands/` and build a structured inventory. For each top-level command and subcommand capture:

- Full command path, such as `clerk config patch` or `clerk api ls`.
- Purpose, from the command description or help string in source.
- Flags, including short form, type, default, and destructive behavior.
- Exit codes beyond the default if the command overrides them.
- Agent-mode branches gated on `isAgentMode()`, `CLERK_MODE`, TTY detection, or related helpers.
- Whether the command mutates remote state and needs `--dry-run`, `--yes`, or production-targeting guidance.

Read `packages/cli-core/src/commands/<name>/README.md` as secondary context only. Use those files to flag mocked or stubbed commands and to cross-check API endpoint claims in `references/recipes.md`. Do not propose copying command READMEs into this repository; they include internal implementation detail and would bloat the skill.

Also capture cross-cutting behavior:

- Runner preference logic, including lockfile to package-runner mapping.
- Auth, key resolution, `--app`, and `--instance` targeting.
- `doctor` checks and `--json` output shape.
- Agent-mode behavior for prompts, JSON defaults, browser opening, OAuth callbacks, deploy handoff, and sandbox warnings.
- `clerk init --prompt` or equivalent handoff behavior if the skill mentions it.

Prefer reading source over running the binary. When runtime behavior is unclear, use tests under `packages/cli-core/src/**/*.test.ts` or `packages/cli-core/src/test/` as supporting evidence.

### 2. Extract the Skill's Claims

Read `skills/core/clerk-cli/SKILL.md` and each file under `skills/core/clerk-cli/references/`. Extract every concrete claim:

- Commands in the core command table and invocation guidance.
- Flags named in prose, tables, and examples.
- Agent-mode behavior bullets.
- Exit code, error format, and JSON output claims.
- Cross-references between `SKILL.md` and `references/*.md`.

### 3. Diff Source Against Skill

Produce a structured diff with four buckets:

1. **Missing from skill**: commands, subcommands, flags, or behaviors that exist in source but are not mentioned anywhere in the skill.
2. **Stale in skill**: claims in the skill that no longer match source, including renamed flags, changed defaults, removed commands, shifted exit codes, or reworked agent-mode branches.
3. **Thin in skill**: commands mentioned but under-specified relative to their real complexity or footgun surface.
4. **Over-specified**: details the skill encodes that `clerk <cmd> --help`, generated examples, or referenced files cover better.

For buckets 1 through 3, cite both the source file and line and the target skill location.

### 4. Decide Placement

Route changes by maintenance value:

- **Core loop, mental model, and safety** stay in `SKILL.md`.
- **Per-command flag details, recipes, and edge cases** belong in `references/recipes.md` or a new reference file.
- **Agent-mode branches** belong in `references/agent-mode.md`; `SKILL.md` gets only a concise summary.
- **Auth, key resolution, and targeting** belong in `references/auth.md`.

Prefer `clerk <command> --help` over duplicating generated help. Flags with destructive semantics, hidden interactions, or agent-mode divergence deserve skill coverage. Self-explanatory flags should usually be removed from prose and delegated to `--help`.

Treat skill shrinkage as a valid proposal when it reduces drift risk. The goal is an accurate, durable skill, not a larger one.

### 5. Propose Edits

Emit a review-ready proposal. For each change include:

- Path, such as `skills/core/clerk-cli/SKILL.md` or `skills/core/clerk-cli/references/agent-mode.md`.
- Severity: `drift`, `gap`, or `polish`.
- Why, with a source citation such as `packages/cli-core/src/commands/<cmd>/<file>.ts:<line>`.
- Target location in this repository.
- A unified diff when practical, otherwise a concise before and after block.

Group the proposal by target file. Do not rewrite accurate neighboring sections just because they are nearby.

### 6. Apply or Hand Back

Default behavior: present the proposal and stop for review.

If invoked with `--apply`, apply `drift` and `gap` edits directly, then list `polish` suggestions for review. After applying edits, run available formatting or validation commands for this repository. If no formatter exists, validate Markdown and JSON structure with lightweight checks.

## Guardrails

- Never invent flags. If a flag appears in tests but not in the command parser, mark it for human review.
- Preserve the existing `clerk-cli` skill's terse, third-person voice.
- Do not use em dashes in proposals or edits.
- Keep `skills/core/clerk-cli/SKILL.md` near the 500-line guidance. Move detailed material to `references/` instead of bloating the main skill.
- Do not treat the installed `clerk` binary as authoritative over source. Use `--help` only to confirm generated presentation when source and tests leave ambiguity.
- Do not commit or print secrets. If the audit touches env guidance, preserve the repository's 1Password and no-plaintext-secret rules.

## Output Shape

Return the proposal as:

```markdown
# clerk-cli skill audit - <YYYY-MM-DD>

## Summary
<counts per bucket, plus the largest drift>

## skills/core/clerk-cli/SKILL.md
### <section name>
- [drift|gap|polish] <one-line description>
  - source: packages/cli-core/src/commands/<...>:<line>
  - target: skills/core/clerk-cli/SKILL.md:<line>
  - change: <diff or concise before/after>

## skills/core/clerk-cli/references/<file>.md
...

## New files
...

## Open questions
...
```

Keep the result skimmable so a maintainer can approve, reject, or apply each entry independently.

<!-- chapter:end slug=audit-clerk-skill -->

---

<!-- chapter:begin slug=audit-expo-skill position=2 -->

## 2. audit-expo-skill

- **Source:** https://github.com/clerk/skills/blob/main/.agents/skills/audit-expo-skill/SKILL.md
- **Raw:** https://raw.githubusercontent.com/clerk/skills/main/.agents/skills/audit-expo-skill/SKILL.md
- **Markdown:** https://skillsdocs.com/clerk/skills/audit-expo-skill.md
- **Licence:** MIT — https://spdx.org/licenses/MIT.html

<!-- Verbatim upstream SKILL.md follows, YAML frontmatter included. -->

---
name: audit-expo-skill
description: Audits the bundled `clerk-expo` skill against the @clerk/expo SDK source and clerk-docs, then proposes or applies updates. Use when the user says "audit the expo skill", "update the clerk-expo skill", "check clerk-expo against the SDK", "resync clerk-expo skill", "run audit-expo-skill", or after @clerk/expo ships a minor or major release.
license: MIT
effort: high
user-invocable: true
disable-model-invocation: true
argument-hint: "[--sdk <path>] [--docs <path>] [--apply]"
metadata:
  internal: true
---

# Clerk Expo Skill Audit

Cross-check `skills/mobile/clerk-expo/` in this repository against the actual `@clerk/expo` SDK source and the clerk-docs content it cites. The SDK source is the source of truth for API shape; clerk-docs is the source of truth for recommended patterns; the skill must track both. **When the two conflict on a factual claim (an API exists, a signature, a default, a version floor), SDK source wins — always.** Docs win only on prescriptive questions source cannot answer (which flow to recommend, prop placement in examples, dashboard prerequisites).

The skill intentionally hardcodes verified code snippets (see its freshness gate). This audit is the maintenance half of that contract: it runs after SDK releases so the snippets stay verified rather than becoming folklore.

## Inputs

- **SDK source of truth**: a `clerk/javascript` checkout containing `packages/expo/` (and `packages/shared/src/types/` for the sign-in/sign-up resource types).
- **Docs source of truth**: a `clerk/clerk-docs` checkout containing `docs/getting-started/quickstart.expo.mdx`, `docs/guides/development/custom-flows/authentication/*.mdx`, and `docs/reference/expo/**`.
- **Target skill**: `skills/mobile/clerk-expo/SKILL.md`, `references/*.md`, and `evals/evals.json`.
- Optional corroboration: a `clerk/clerk-expo-quickstart` checkout (three example apps exercising the current API).

## Source Checkout Resolution

Resolve each checkout in this order:

1. Use `--sdk <path>` / `--docs <path>` when supplied. The SDK path must contain `packages/expo/package.json`; the docs path must contain `docs/getting-started/quickstart.expo.mdx`.
2. Look for sibling checkouts of this repository: `../javascript` and `../clerk-docs` (the standard Clerk projects layout), and `../clerk-expo-quickstart` for corroboration.
3. If `CLERK_JAVASCRIPT_REPO` / `CLERK_DOCS_REPO` are set, use those paths.
4. If a checkout is missing and network access is acceptable, shallow-clone into `.context/`:

```sh
mkdir -p .context && cd .context
git clone --depth 1 https://github.com/clerk/javascript.git
git clone --depth 1 https://github.com/clerk/clerk-docs.git
```

If neither is available, stop and ask the user for paths. Do not audit from memory or from an installed `node_modules` copy alone; the audit exists precisely because memory drifts.

## Workflow

### 1. Establish the version delta

- Read the skill's stamped version from `SKILL.md` frontmatter (`compatibility:`) and its freshness-gate text.
- Read the current version from `packages/expo/package.json` plus `peerDependencies` (Expo SDK range, React Native floor, React range) and bundled native SDK versions (`clerk-ios`, `clerk-android` in dependencies).
- Read `packages/expo/CHANGELOG.md` entries between the stamped version and current. This is the primary work queue: every changelog entry either affects the skill or is explicitly irrelevant.

If the stamped version equals the current version and the changelog shows nothing new, report "no drift" and stop.

### 2. Inventory the SDK surface

Build a structured inventory from source (prefer `src/` over `dist/`):

- **Exports map**: `packages/expo/package.json` `exports` — every subpath (`/native`, `/web`, `/token-cache`, `/resource-cache`, `/secure-store`, `/local-credentials`, `/passkeys`, `/google`, `/apple`, `/legacy`, `/experimental`, …), noting additions and removals.
- **Hooks**: everything re-exported from `src/hooks/index.ts` and the Expo-specific hooks (`useSSO`, `useAuth` extensions, `useSignInWithGoogle`, `useSignInWithApple`, `useLocalCredentials`). Capture signatures, return shapes, and `@deprecated` tags.
- **Custom-flow resources**: the `SignInFutureResource` / `SignUpFutureResource` method surface in `packages/shared/src/types/signInFuture.ts` and `signUpFuture.ts` (method names, param shapes, status enums). The skill's custom-flows reference mirrors this surface.
- **Native components**: `src/native/index.ts` exports and each component's props types (`AuthView.types.ts`, `UserProfileView`, `UserButton`). Flag any prop the skill names that no longer exists, and any new public prop.
- **Config plugin**: `src/plugin/withClerkExpo.ts` — required env vars, theme option schema, platform side effects (deployment targets, URL schemes).
- **Provider**: `src/provider/ClerkProvider.tsx` props, especially experimental ones (`__experimental_passkeys`, `__experimental_resourceCache`) and any newly stabilized names.
- **Dev warnings / migration signals**: grep for `@deprecated`, `console.warn`, and package-migration notices (e.g. the `@clerk/expo-google-signin` split). These become "coming changes" notes in the skill.

### 3. Inventory the docs claims

For every canonical docs URL cited in the skill's references:

- Confirm the corresponding `.mdx` file still exists in clerk-docs at that route (URL path → `docs/` path). Broken citations are `drift`.
- Re-read the Expo tab/section of each cited custom-flow guide and the Expo quickstart. Where the docs' recommended pattern changed (new required step, changed prop placement, new dashboard prerequisite), the skill's matching snippet is `drift` even if it still compiles.

### 4. Extract the skill's claims

Read `SKILL.md`, every `references/*.md`, and `evals/evals.json`. Extract each concrete claim:

- Version stamps and peer ranges.
- Every code snippet (imports, method calls, props, env var names).
- Every gate and pitfall that names an API (`useSSO` vs `useOAuth`, `resourceCache` vs `secureStore`, captcha mount point, `treatPendingAsSignedOut` placement).
- The capability matrix (Expo Go / dev build / web).
- Eval expectations that assert API strings.

### 5. Diff and bucket

Produce a structured diff with four buckets:

1. **Missing from skill**: new exports, hooks, props, flows, or prerequisites in SDK/docs that the skill should cover (or explicitly scope out).
2. **Stale in skill**: claims contradicted by source or docs — renamed/removed APIs, changed defaults, changed peer floors, moved props, dead docs URLs, evals asserting outdated strings.
3. **Thin in skill**: covered but under-specified relative to real footgun surface (e.g. a new error code developers will hit).
4. **Over-specified**: detail the docs or installed `.d.ts` cover better; propose shrinkage where it reduces drift risk.

Cite source file and line for every bucket-1/2/3 entry, plus the target skill location.

### 6. Propose edits

Emit a review-ready proposal grouped by target file. For each change include severity (`drift`, `gap`, or `polish`), the source citation, the target location, and a unified diff or concise before/after. Always include, when any change is applied:

- The `compatibility:` stamp and freshness-gate version in `SKILL.md` updated to the audited SDK version.
- Eval updates when expectations reference changed APIs.

Do not rewrite accurate neighboring sections. Skill shrinkage is a valid proposal.

### 7. Apply or hand back

Default: present the proposal and stop for review.

With `--apply`: apply `drift` and `gap` edits, update the version stamps, list `polish` for review, then validate — JSON files parse, every relative reference path in `SKILL.md` resolves, and every canonical docs URL maps to an existing clerk-docs file.

## Guardrails

- Never invent API surface. On factual conflicts between docs and SDK source, side with source and write the skill accordingly; list the discrepancy under "Open questions" as a probable clerk-docs bug worth reporting upstream. Only genuinely prescriptive ambiguity (source supports both patterns, docs unclear on which to recommend) goes to human review.
- Snippets must stay minimal (API shape and control flow, not styled screens) and keep their canonical docs URL citation.
- Preserve the skill's execution-gate structure and voice; edits slot into existing sections.
- The legacy-API prohibition (Gate: method-based flows, never `prepareFirstFactor`/`setActive` for new code) may only be relaxed if the SDK itself re-legitimizes the legacy surface — treat any such change as a major finding, not a routine edit.
- Do not audit against an installed `node_modules` copy as authority; it reflects whatever the last install pulled, not the release being audited.
- Do not commit; leave staging and committing to the maintainer unless explicitly asked.

## Output Shape

```markdown
# clerk-expo skill audit - <YYYY-MM-DD>

## Summary
<stamped vs current version, changelog entries reviewed, counts per bucket, largest drift>

## skills/mobile/clerk-expo/SKILL.md
### <section>
- [drift|gap|polish] <one-line description>
  - source: packages/expo/src/<...>:<line> (or docs/<...>.mdx:<line>)
  - target: skills/mobile/clerk-expo/SKILL.md:<line>
  - change: <diff or concise before/after>

## skills/mobile/clerk-expo/references/<file>.md
...

## skills/mobile/clerk-expo/evals/evals.json
...

## Open questions
...
```

Keep the result skimmable so a maintainer can approve, reject, or apply each entry independently.

<!-- chapter:end slug=audit-expo-skill -->

---

<!-- chapter:begin slug=clerk-backend-api position=3 -->

## 3. clerk-backend-api

- **Source:** https://github.com/clerk/skills/blob/main/skills/core/clerk-backend-api/SKILL.md
- **Raw:** https://raw.githubusercontent.com/clerk/skills/main/skills/core/clerk-backend-api/SKILL.md
- **Markdown:** https://skillsdocs.com/clerk/skills/clerk-backend-api.md
- **Licence:** MIT — https://spdx.org/licenses/MIT.html

Bundled files (6), referenced from this skill's directory:
  - `evals/evals.json` — https://raw.githubusercontent.com/clerk/skills/main/skills/core/clerk-backend-api/evals/evals.json
  - `scripts/api-specs-context.sh` — https://raw.githubusercontent.com/clerk/skills/main/skills/core/clerk-backend-api/scripts/api-specs-context.sh
  - `scripts/execute-request.sh` — https://raw.githubusercontent.com/clerk/skills/main/skills/core/clerk-backend-api/scripts/execute-request.sh
  - `scripts/extract-endpoint-detail.sh` — https://raw.githubusercontent.com/clerk/skills/main/skills/core/clerk-backend-api/scripts/extract-endpoint-detail.sh
  - `scripts/extract-tag-endpoints.sh` — https://raw.githubusercontent.com/clerk/skills/main/skills/core/clerk-backend-api/scripts/extract-tag-endpoints.sh
  - `scripts/extract-tags.js` — https://raw.githubusercontent.com/clerk/skills/main/skills/core/clerk-backend-api/scripts/extract-tags.js

<!-- Verbatim upstream SKILL.md follows, YAML frontmatter included. -->

---
name: clerk-backend-api
description: "Clerk Backend REST API explorer and executor. Browse tags, inspect endpoint schemas, and execute authenticated requests. Use when listing users, managing organizations, or calling any Clerk API endpoint."
allowed-tools: Bash, Read, Grep, Skill, WebFetch
license: MIT
compatibility: Requires CLERK_SECRET_KEY (sk_*) for Backend API calls.
---

## Options context

User Prompt: $ARGUMENTS

## CRITICAL: Mandatory checks before EVERY write request

Before ANY POST / PATCH / PUT / DELETE, you MUST do ALL of the following in your response:

1. **Check CLERK_SECRET_KEY** — verify it is set:
   ```bash
   echo $CLERK_SECRET_KEY | head -c 10
   ```
   If empty, stop and ask the user. Do not proceed without a valid key.

2. **Check CLERK_BAPI_SCOPES** — run:
   ```bash
   echo $CLERK_BAPI_SCOPES
   ```
   Inspect the output. If scopes are missing or do not include the required write permission, tell the user: *"This is a write operation and your current scopes may not allow it. Rerun with --admin to bypass?"* Do NOT attempt the request and fail — ask first.

3. **For DELETE requests:** warn explicitly that the action is **IRREVERSIBLE** and list exactly what data will be permanently destroyed (user record, all sessions, all memberships, all associated data). Require explicit confirmation before proceeding. This warning is MANDATORY — never skip it.

4. **For metadata operations:** always explain which metadata type is being used and why (see Metadata types section below).

---

## FAST PATH: Common operations (use directly, no spec fetching needed)

For the operations below, skip spec fetching and execute immediately using these exact templates. Substitute `$CLERK_SECRET_KEY`, `$USER_ID`, `$ORG_ID`, `$EMAIL` as needed from the user's context.

### Create organization + invite member (two-step)

```bash
# Step 1 — Create organization
ORG=$(curl -s -X POST "https://api.clerk.com/v1/organizations" \
  -H "Authorization: Bearer $CLERK_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"name\": \"Acme Corp\", \"created_by\": \"$USER_ID\"}")
echo "$ORG" | python3 -c "import sys,json; d=json.load(sys.stdin); print(json.dumps(d, indent=2))"

# Step 2 — Extract org ID
ORG_ID=$(echo "$ORG" | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")

# Step 3 — Invite member with role
curl -s -X POST "https://api.clerk.com/v1/organizations/${ORG_ID}/invitations" \
  -H "Authorization: Bearer $CLERK_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"email_address\": \"user@example.com\", \"role\": \"org:admin\"}" \
  | python3 -c "import sys,json; print(json.dumps(json.load(sys.stdin), indent=2))"
```

**Roles:** use `"org:admin"` or `"org:member"` (always prefix with `org:`).

### SDK equivalent (for Next.js / TypeScript projects with `@clerk/nextjs` or `@clerk/backend`)

```typescript
import { clerkClient } from '@clerk/nextjs/server'
// OR if using @clerk/backend directly:
// import { createClerkClient } from '@clerk/backend'
// const clerkClient = createClerkClient({ secretKey: process.env.CLERK_SECRET_KEY })

// Step 1: Create organization
const org = await clerkClient.organizations.createOrganization({
  name: 'Acme Corp',
  createdBy: userId,  // required — the ID of the user creating the org
})

// Step 2: Invite member to the org
const invitation = await clerkClient.organizations.createOrganizationInvitation({
  organizationId: org.id,
  emailAddress: 'user@example.com',
  role: 'org:admin',  // or 'org:member'
})
```

### Update user metadata

**Always explain the three metadata types before asking which to use:**

| Type | Field | Readable by | Writable by | Use for |
|------|-------|-------------|-------------|---------|
| Public | `public_metadata` | Client + Server | **Server only** | Plan tier, roles, feature flags the frontend reads |
| Private | `private_metadata` | **Server only** | **Server only** | Stripe IDs, compliance flags, internal identifiers |
| Unsafe | `unsafe_metadata` | Client + Server | Client + Server | Ephemeral UI state, onboarding steps (client-writable — avoid sensitive data) |

**For `plan: 'pro'` and `onboarded: true` — use `public_metadata`** (frontend-readable, server-writable):

```bash
curl -s -X PATCH "https://api.clerk.com/v1/users/${USER_ID}" \
  -H "Authorization: Bearer $CLERK_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{"public_metadata": {"plan": "pro", "onboarded": true}}' \
  | python3 -c "import sys,json; d=json.load(sys.stdin); print(f'Updated user {d[\"id\"]}: public_metadata={d.get(\"public_metadata\")}')"
```

**SDK equivalent:**

```typescript
import { clerkClient } from '@clerk/nextjs/server'
// OR: import { createClerkClient } from '@clerk/backend'

await clerkClient.users.updateUser(userId, {
  publicMetadata: { plan: 'pro', onboarded: true },   // readable by client, writable server-only
  // privateMetadata: { stripeId: 'cus_xxx' },         // server-only read AND write
  // unsafeMetadata: { step: 'welcome' },              // client-writable, avoid sensitive data
})
```

**Note:** REST API uses `snake_case` (`public_metadata`). SDK uses `camelCase` (`publicMetadata`).

### List users (last 7 days)

```bash
curl -s "https://api.clerk.com/v1/users?limit=100&offset=0&order_by=-created_at&created_at=gt:$(date -d '7 days ago' +%s 2>/dev/null || date -v-7d +%s)000" \
  -H "Authorization: Bearer $CLERK_SECRET_KEY" \
  | python3 -c "
import sys, json
data = json.load(sys.stdin)
if isinstance(data, list):
    print(f'Found {len(data)} users:')
    for u in data:
        print(f'  {u[\"id\"]}: {u.get(\"email_addresses\", [{}])[0].get(\"email_address\", \"no email\")}')
else:
    print(json.dumps(data, indent=2))
"
```

### Delete user (confirm required)

```bash
# ONLY run after explicit user confirmation
curl -s -X DELETE "https://api.clerk.com/v1/users/${USER_ID}" \
  -H "Authorization: Bearer $CLERK_SECRET_KEY" \
  | python3 -c "import sys,json; d=json.load(sys.stdin); print(f'Deleted: {d}')"
```

---

## Clerk Backend API — Full Endpoint Reference

Base URL: `https://api.clerk.com/v1`
Auth: `Authorization: Bearer $CLERK_SECRET_KEY` on every request.

### Users

**List users**
```
GET /v1/users
Query params: limit (max 500, default 10), offset, order_by (+/-created_at, +/-updated_at, +/-email_address, +/-web3wallet, +/-first_name, +/-last_name, +/-phone_number, +/-username, +/-last_active_at, +/-last_sign_in_at), email_address[], phone_number[], username[], web3wallet[], user_id[], query, created_at (ISO 8601 range: gt:TIMESTAMP or lt:TIMESTAMP in Unix ms)
Returns: array of User objects
```

**Get user**
```
GET /v1/users/{user_id}
Returns: User object
```

**Update user**
```
PATCH /v1/users/{user_id}
Body (JSON, snake_case): { public_metadata, private_metadata, unsafe_metadata, first_name, last_name, username, ... }
```

**Delete user — IRREVERSIBLE**
```
DELETE /v1/users/{user_id}
Destroys: user record, all sessions, all memberships, all associated data
Returns: { id, object, deleted: true }
```
Always warn the user this is permanent and confirm before proceeding.

### Organizations

**Create organization**
```
POST /v1/organizations
Body: { name: string, created_by: string (user_id), public_metadata?, private_metadata?, max_allowed_memberships? }
Returns: Organization object with { id, name, slug, ... }
```

**List organizations**
```
GET /v1/organizations
Query params: limit, offset, query, order_by
```

**Invite member**
```
POST /v1/organizations/{organization_id}/invitations
Body: { email_address: string, role: string ("org:admin" or "org:member"), public_metadata?, private_metadata? }
Returns: OrganizationInvitation object
```

---

## How to execute requests

**ALWAYS execute requests with direct `curl` commands.** Use the spec-extraction scripts (`api-specs-context.sh`, `extract-tags.js`, `extract-endpoint-detail.sh`) to discover endpoints, but make actual API calls with `curl`. Do NOT use `scripts/execute-request.sh` — it's a local dev helper, not for agent use.

Template for GET requests:
```bash
curl -s "https://api.clerk.com/v1${PATH}${QUERY_STRING}" \
  -H "Authorization: Bearer $CLERK_SECRET_KEY"
```

Template for POST/PATCH requests:
```bash
curl -s -X ${METHOD} "https://api.clerk.com/v1${PATH}" \
  -H "Authorization: Bearer $CLERK_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '${BODY_JSON}'
```

Template for DELETE requests:
```bash
curl -s -X DELETE "https://api.clerk.com/v1${PATH}" \
  -H "Authorization: Bearer $CLERK_SECRET_KEY"
```

**After getting the response:** Parse and display it clearly. Use `python3 -c "import sys,json; data=json.load(sys.stdin); print(json.dumps(data, indent=2))"` to pretty-print JSON. Extract key fields (id, email, name, etc.) and summarize them for the user.

---

## API specs context

Before doing anything outside the FAST PATH, fetch the available spec versions and tags by running:
```bash
bash scripts/api-specs-context.sh
```

Use the output to determine the latest version and available tags.

**Caching:** If you already fetched the spec context earlier in this conversation, do NOT fetch it again. Reuse the version and tags from the previous call.

---

## Rules

- For common operations (list users, create org, invite, update metadata, delete user): use the FAST PATH above — do NOT fetch specs first.
- Always disregard endpoints/schemas related to `platform`.
- Always confirm before performing write requests (POST/PUT/PATCH/DELETE).
- For DELETE operations, always warn the user that the action is **irreversible** and mention what data will be lost (user record, sessions, memberships). This warning is MANDATORY — never skip it.
- For write operations (POST/PUT/PATCH/DELETE), check `CLERK_BAPI_SCOPES` before attempting the request. If missing or insufficient, ask the user upfront. Do NOT attempt and fail — ask before executing. This check is MANDATORY.
- For metadata operations, always explain all three types (public, private, unsafe) and recommend the appropriate one.
- Pagination: always use `limit` + `offset` and mention that results may be paginated for large datasets.
- Use direct curl commands for all API calls — never use `scripts/execute-request.sh`.

---

## Rate Limits & Gotchas

### Rate Limits

| Environment | Limit |
|-------------|-------|
| Production | 1,000 requests / 10 seconds |
| Development | 100 requests / 10 seconds |
| Single invitations | 100 / hour |
| Bulk invitations | 25 / hour |
| Org invitations | 250 / hour |
| Frontend API sign-in creation | 5 / 10 seconds |
| Frontend API sign-in attempts | 3 / 10 seconds |
| List users max per page | 500 |

`currentUser()` makes a real API call that counts against rate limits. Use `auth()` for just the session claims — it reads from the token without an API call.

### Metadata Overwrites (Not Merges)

`updateUser({ publicMetadata: { role: 'admin' } })` REPLACES all public metadata, not merges. To add a field without losing existing data: read first, spread, then write.

Wrong:
```typescript
await clerkClient.users.updateUser(userId, { publicMetadata: { newField: 'value' } })
```
This DELETES all other `publicMetadata` fields.

Right:
```typescript
const user = await clerkClient.users.getUser(userId)
await clerkClient.users.updateUser(userId, {
  publicMetadata: { ...user.publicMetadata, newField: 'value' },
})
```

---

## Modes

Determine the active mode based on the user prompt in [Options context](#options-context):

| Mode | Trigger | Behavior |
|------|---------|----------|
| `help` | Prompt is empty, or contains only `help` / `-h` / `--help` | Print usage examples (step 0) |
| `browse` | Prompt is `tags`, or a tag name (e.g. `Users`) | List all tags or endpoints for a tag |
| `execute` | Specific endpoint (e.g. `GET /users`) or natural language action (e.g. "get user john_doe") | Look up endpoint, execute request |
| `detail` | Endpoint + `help` / `-h` / `--help` (e.g. `GET /users help`) | Show endpoint schema, don't execute |

---

## Your Task

Use the **LATEST VERSION** from [API specs context](#api-specs-context) by default. If the user specifies a different version (e.g. `--version 2024-10-01`), use that version instead.

Determine the active mode, then follow the applicable steps below.

---

### 0. Print usage

**Modes:** `help` only — **Skip** for `browse`, `execute`, and `detail`.

Print the following examples to the user verbatim:

```
Browse
  /clerk-backend-api tags                         — list all tags
  /clerk-backend-api Users                        — browse endpoints for the Users tag
  /clerk-backend-api Users version 2025-11-10.yml — browse using a different version

Execute
  /clerk-backend-api GET /users             — fetch all users
  /clerk-backend-api get user john_doe      — natural language works too
  /clerk-backend-api POST /invitations      — create an invitation

Inspect
  /clerk-backend-api GET /users help        — show endpoint schema without executing
  /clerk-backend-api POST /invitations -h   — view request/response details

Options
  --admin                            — bypass scope restrictions for write/delete
  --version [date], version [date]   — use a specific spec version
  --help, -h, help                   — inspect endpoint instead of executing
```

Stop here.

---

### 1. Fetch tags

**Modes:** `browse` (when prompt is `tags` or no tag specified) — **Skip** for `help`, `execute`, and `detail`.

If using a non-latest version, fetch tags for that version:
```bash
curl -s https://raw.githubusercontent.com/clerk/openapi-specs/main/bapi/${version_name} | node scripts/extract-tags.js
```
Otherwise, use the **TAGS** already in [API specs context](#api-specs-context).

Share tags in a table and prompt the user to select a query.

---

### 2. Fetch tag endpoints

**Modes:** `browse` (when a tag name is provided) — **Skip** for `help`, `execute`, and `detail`.

Fetch all endpoints for the identified tag:
```bash
curl -s https://raw.githubusercontent.com/clerk/openapi-specs/main/bapi/${version_name} | bash scripts/extract-tag-endpoints.sh "${tag_name}"
```

Share the results (endpoints, schemas, parameters) with the user.

---

### 3. Fetch endpoint detail

**Modes:** `execute`, `detail` — **Skip** for `help` and `browse`.

For natural language prompts in `execute` mode, first check if the operation matches a FAST PATH entry above. If it does, skip this step and proceed directly to step 4 using the FAST PATH template.

For other endpoints, identify the matching endpoint by searching the tags in context. Fetch tag endpoints if needed to resolve the exact path and method.

Extract the full endpoint definition:
```bash
curl -s https://raw.githubusercontent.com/clerk/openapi-specs/main/bapi/${version_name} | bash scripts/extract-endpoint-detail.sh "${path}" "${method}"
```
- `${path}` — e.g. `/users/{user_id}`
- `${method}` — lowercase, e.g. `get`

**`detail` mode:** Share the endpoint definition and schemas with the user. Stop here.

**`execute` mode:** Continue to step 4.

---

### 4. Execute request

**Modes:** `execute` only.

1. Run the **mandatory checks** from the CRITICAL section above.
2. Identify required and optional parameters from the spec (step 3) or FAST PATH.
3. Ask the user for any required path/query/body parameters that weren't provided.
4. Build and execute a **direct curl command** (see How to execute requests above). Do NOT use `scripts/execute-request.sh`.
5. Parse the JSON response and display it clearly. Extract and summarize key fields for the user.

**Example — list users and parse response:**
```bash
RESPONSE=$(curl -s "https://api.clerk.com/v1/users?limit=10" \
  -H "Authorization: Bearer $CLERK_SECRET_KEY")
echo "$RESPONSE" | python3 -c "
import sys, json
data = json.load(sys.stdin)
if isinstance(data, list):
    print(f'Found {len(data)} users:')
    for u in data:
        print(f'  {u[\"id\"]}: {u.get(\"email_addresses\", [{}])[0].get(\"email_address\", \"no email\")}')
else:
    print(json.dumps(data, indent=2))
"
```

## See Also

- `clerk-setup` - Initial Clerk install
- `clerk-orgs` - Manage organizations via API
- `clerk-webhooks` - Real-time event sync

<!-- chapter:end slug=clerk-backend-api -->

---

<!-- chapter:begin slug=clerk-cli position=4 -->

## 4. clerk-cli

- **Source:** https://github.com/clerk/skills/blob/main/skills/core/clerk-cli/SKILL.md
- **Raw:** https://raw.githubusercontent.com/clerk/skills/main/skills/core/clerk-cli/SKILL.md
- **Markdown:** https://skillsdocs.com/clerk/skills/clerk-cli.md
- **Licence:** MIT — https://spdx.org/licenses/MIT.html

Bundled files (3), referenced from this skill's directory:
  - `references/agent-mode.md` — https://raw.githubusercontent.com/clerk/skills/main/skills/core/clerk-cli/references/agent-mode.md
  - `references/auth.md` — https://raw.githubusercontent.com/clerk/skills/main/skills/core/clerk-cli/references/auth.md
  - `references/recipes.md` — https://raw.githubusercontent.com/clerk/skills/main/skills/core/clerk-cli/references/recipes.md

<!-- Verbatim upstream SKILL.md follows, YAML frontmatter included. -->

---
name: clerk-cli
description: >-
  Operate the Clerk CLI (`clerk` binary) for authentication, user/org/session
  management, impersonation, local webhook testing, deploy verification,
  instance config, env keys, feature toggles, and any Clerk Backend, Platform,
  or Frontend API call. Use when the user mentions Clerk management tasks,
  "list clerk users", "impersonate a user", "test webhooks locally",
  "enable orgs", "enable billing",
  "clerk env pull", "clerk doctor", "clerk deploy", "clerk api", or any ad-hoc
  Clerk API request. Prefer the CLI over raw HTTP: it handles auth, key
  resolution, app/instance targeting, and formatting automatically.
license: MIT
---

# Clerk CLI

The `clerk` binary is a pre-authenticated gateway to Clerk's Backend API and Platform API, plus project-level tooling (auth, linking, env pulls, instance config). When the user asks anything that touches a Clerk resource, reach for `clerk` first instead of hand-rolling `curl`.

> This skill targets clerk `latest`. If `clerk --version` disagrees with the latest available CLI, refresh it with `clerk update`, or invoke the latest through a package runner such as `bunx clerk@latest`. The binary is always the source of truth, so run `clerk <command> --help` to verify anything this skill claims.

## Execution environment (prefer the host, understand the sandbox warning)

Most AI coding agents default to running shell commands in a sandbox where the
user's home directory, OS keychain, browser launch, localhost callback
binding, or network access may be blocked. The Clerk CLI depends on all of
those host capabilities:

- **OS credential store**: `clerk auth login` stores the OAuth token in the
  system keychain. A sandbox without keychain access reports "not logged in"
  even when the host is authenticated.
- **Home-directory Clerk state**: saved config, cached metadata, and fallback
  credentials live under the user's Clerk config/data directories.
- **Linked project metadata**: resolved from the repo's git remote plus Clerk
  config. Sandboxes with stripped repo state or blocked home-dir reads can
  misreport "not linked".
- **Local `.env*` files**: publishable and secret keys materialized by
  `clerk env pull`.
- **Outbound network access to Clerk**: every Backend and Platform API call.
- **Browser + localhost OAuth callback**: `clerk auth login` needs both.

In agent mode, the CLI now does a **best-effort warn-once check** at the
host-sensitive library boundaries. When it detects that host-only Clerk state
or system capabilities are unavailable, it emits:

```text
Host-only Clerk state or system capabilities may be unavailable in agent mode. This may be a sandboxed run.
Re-run this command on the host shell before trusting auth, link, env, or API failures.
```

Treat that warning as authoritative. The command may continue, but any auth,
link, env, config, API, browser, or OAuth callback failure from that
invocation is untrusted until you rerun the same command on the host.

**Prefer these commands on the user's host shell, not in a sandbox:**

`clerk doctor`, `clerk whoami`, `clerk auth login`, `clerk link`, `clerk env pull`,
`clerk apps ...`, `clerk config ...`, `clerk api ...`.

If a command was accidentally run in a sandbox and it reports `Not logged in`,
`auth_required`, `not linked`, missing env, keychain/file permission errors,
or network failures, **do not treat the result as authoritative**. Rerun it on
the host before acting on it or reporting it to the user.

## Invoking the CLI

Before running any `clerk` command, figure out which binary to invoke and bind that choice for the rest of the session:

```sh
# 1. Prefer a globally installed binary when it matches the skill's target version.
command -v clerk >/dev/null 2>&1 && clerk --version
```

If that prints `latest` or any version you trust, use bare `clerk` for the rest of the session.

Otherwise fall back to a package runner, in this order (matches the CLI's own `preferredRunner` logic, which prefers the runner that matches the project's lockfile):

| Project package manager   | Invocation                       |
| ------------------------- | -------------------------------- |
| bun (`bun.lock*`)         | `bunx clerk@latest`     |
| npm (`package-lock.json`) | `npx -y clerk@latest`   |
| pnpm (`pnpm-lock.yaml`)   | `pnpm dlx clerk@latest` |
| yarn >= 2 (`yarn.lock`)   | `yarn dlx clerk@latest` |

Yarn Classic (v1) has no `dlx`; treat those projects as "no preferred runner" and fall back to the first runner from the list above that's on PATH.

The published npm package is **`clerk`**, not `@clerk/cli`. Never teach `npm install -g clerk` as the primary path. If the global CLI is stale or behaves differently from this skill, either upgrade the global install or fall back to the `latest` runner form above.

## Prerequisites (run at session start)

Before running any other Clerk command in a session, verify the CLI is authenticated, linked, and healthy:

```sh
clerk --version               # confirm the binary is on PATH
clerk doctor --json           # structured health check; exit 1 if anything failed
```

**Always run `clerk doctor --json` first.** It catches the common setup failures (not logged in, project not linked, missing keys, stale CLI version) up front, so later commands don't fail with confusing errors. In agent mode it also includes a `Host execution` check that warns when Clerk's host-side config / credential directories are not writable, which is the canonical signal that the current invocation is likely sandboxed.

Each result has `name`, `status` (`pass`/`warn`/`fail`), `message`, optional `detail`, optional `remedy` (how to fix it), and optional `fix` (label for auto-fixable issues). Parse that and act on it, or surface it to the user. If `Host execution` warns, rerun the command on the host before trusting any auth/link/env/API failures from the same sandboxed run. Rerun `clerk doctor --json` whenever a later command starts misbehaving.

If `clerk --version` reports a newer CLI than this skill covers, trust `clerk <command> --help` first and refresh this skill bundle from its source.

## The mental model

| Layer                           | What it does                                                                                 | Commands                                                       |
| ------------------------------- | -------------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
| **Session / project**           | Auth, link a repo to a Clerk app, pull env keys                                              | `auth login`, `link`, `unlink`, `whoami`, `env pull`, `doctor` |
| **Instance config**             | Manage the configuration (social providers, session lifetimes, etc.) for a specific instance | `config pull`, `config schema`, `config patch`, `config put`   |
| **Backend API (default)**       | Runtime data: users, orgs, sessions, invitations, JWT templates, webhooks                    | `clerk api <path>`                                             |
| **Platform API (`--platform`)** | Account-level: applications, instances, billing                                              | `clerk api --platform <path>`                                  |
| **Frontend API (`--fapi`)**     | The instance's public client-facing API (what clerk-js calls)                                | `clerk api --fapi <path>`                                      |

A project is "linked" to an application via `clerk link`. Once linked, most commands auto-resolve the target app and dev instance from the repo's git remote. To target something else, pass `--app <id>` and/or `--instance dev|prod|<instance_id>`. See [references/auth.md](references/auth.md) for the full resolution order.

## Discover endpoints - don't memorize them

The CLI ships with the Clerk OpenAPI catalog. Always discover endpoints dynamically instead of guessing paths:

```sh
clerk api ls                  # list every Backend API endpoint
clerk api ls users            # filter by keyword (matches path, summary, tag, operationId)
clerk api ls --platform apps  # list Platform API endpoints
```

Use this before `clerk api <path>`. If you don't see the endpoint you expected, it probably isn't exposed.

## The `clerk api` command (the workhorse)

`clerk api` makes authenticated HTTP calls. It auto-resolves keys, auto-detects method from body presence, supports stdin, and can preview mutations with `--dry-run`.

```sh
# GET requests
clerk api /users                                  # list users
clerk api /users/user_abc123                      # fetch one
clerk api /users?limit=5&order_by=-created_at     # query params work inline

# Mutating requests
clerk api /users -d '{"email_address":["a@b.co"]}'          # POST (auto-detected from body)
clerk api /users/user_abc123 -X PATCH -d '{"first_name":"A"}'
clerk api /users/user_abc123 -X DELETE

# Body from file or stdin
clerk api /users --file payload.json
cat payload.json | clerk api /users

# Always preview mutations first
clerk api /users/user_abc123 -X DELETE --dry-run
clerk api /users/user_abc123 -X DELETE --yes      # skip confirmation once you've verified

# Target a specific app/instance
clerk api /users --app app_abc123 --instance prod

# Include response headers when debugging
clerk api /users --include

# Platform API (account-level, not tenant data)
clerk api /v1/platform/applications --platform

# Frontend API (the instance's public client-facing API — what clerk-js calls.
# Unauthenticated; --fapi and --platform cannot be combined, --secret-key is ignored)
clerk api --fapi /environment
```

In human mode, `clerk api` with no arguments opens an interactive request builder; in agent mode it prints usage guidance and exits `0` — always pass an endpoint (or `ls`) explicitly from scripts.

For instance config, prefer the dedicated `clerk config ...` commands over raw Platform API `/config` paths. They handle dry-run, diffing, and confirmation more cleanly than the raw endpoint form.

**Always `--dry-run` a mutation before running it for real.** Then re-run without `--dry-run` (add `--yes` if you're sure). In agent mode, interactive confirmation is bypassed, so `--dry-run` is the only safety net for destructive calls.

**JSON bodies must be valid JSON.** The CLI validates and rejects malformed payloads.

**Endpoint paths may be given with or without `/v1/` prefix** - both work for Backend API calls. The CLI normalizes.

See [references/recipes.md](references/recipes.md) for concrete patterns: listing/filtering users, creating orgs, impersonation sessions, etc.

## Inspecting large outputs (do not flood your context)

`users list`, `apps list`, `config pull`, and most `clerk api` GETs return payloads that can be many kilobytes or megabytes. Production tenants commonly have thousands of users; an instance config can be hundreds of fields deep. Reading those responses into the conversation costs context window for no benefit. Save the response to a file first, then query just what you need with `jq`:

```sh
# 1. Persist the response. Use --limit 250 to maximize page size for users list.
clerk users list --json --limit 250 > /tmp/users.json
clerk apps list --json                > /tmp/apps.json
clerk api /users/user_abc123          > /tmp/user.json

# 2. Inspect only what you need.
jq '.data | length'                       /tmp/users.json   # current page size
jq '.hasMore'                             /tmp/users.json   # are more pages available?
jq '.data[0] | keys'                      /tmp/users.json   # discover the user shape once
jq '.data[] | {id, email_addresses}'      /tmp/users.json   # project to a few fields
jq '[.data[] | select(.banned)] | length' /tmp/users.json   # aggregate without reading rows
```

**If `jq` is not available**, fall back to Python or Node - both can stream the file without printing it whole:

```sh
python3 -c 'import json; d=json.load(open("/tmp/users.json")); print(len(d["data"]), d["hasMore"])'
node -e 'const d=require("/tmp/users.json"); console.log(d.data.length, d.hasMore)'
```

`cat` / `head` the file only when you genuinely need to see the raw structure for one-off debugging. When walking pages, write each page to its own file (e.g. `page-${offset}.json`) so individual pages stay independently inspectable.

## Core commands at a glance

| Command                       | Purpose                                                                                                                                                                                                                                                                                                             | Key flags                                                                                                                                                                        |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `clerk init`                  | Scaffold Clerk into a project. `--starter` only supports bootstrap for Next.js, React Router, Astro, Nuxt, TanStack Start, React, Vue, and JavaScript.                                                                                                                                                              | `--framework`, `--pm`, `--name` (with `--starter`), `--app`, `--starter`, `-y`, `--no-skills`                                                                                    |
| `clerk auth login`            | OAuth browser login (stores token). Agent mode: no-op if already logged in. With no stored session it still opens a browser and binds a localhost callback, so it is not unattended; prefer `CLERK_PLATFORM_API_KEY` for headless flows. Aliases: `signup`, `signin`, `sign-in`. Top-level shortcut: `clerk login`. | -                                                                                                                                                                                |
| `clerk auth logout`           | Clear stored credentials. Aliases: `signout`, `sign-out`. Top-level shortcut: `clerk logout`.                                                                                                                                                                                                                       | -                                                                                                                                                                                |
| `clerk whoami`                | Print the logged-in email.                                                                                                                                                                                                                                                                                          | -                                                                                                                                                                                |
| `clerk link` / `clerk unlink` | Link this repo to a Clerk app, or remove the link. `unlink` requires `--yes` in agent mode.                                                                                                                                                                                                                         | (see `--help`)                                                                                                                                                                   |
| `clerk env pull`              | Write publishable + secret keys to the framework's env file (merge, not clobber). Resolves `.env.development.local` → framework-preferred file → `.env.local`; override with `--file`.                                                                                                                              | (see `--help`)                                                                                                                                                                   |
| `clerk config {pull,schema}`  | Fetch instance config JSON, or its JSON Schema.                                                                                                                                                                                                                                                                     | (see `--help`)                                                                                                                                                                   |
| `clerk config patch`          | Partial update (PATCH) of instance config. Pass `--destructive` to actually delete sub-resources touched by the patch rather than resetting them to defaults.                                                                                                                                                       | `--app`, `--instance`, `--file`, `--json`, `--dry-run`, `--yes`, `--destructive`                                                                                                 |
| `clerk config put`            | Full replacement (PUT) of instance config. Pass `--destructive` to actually delete removed sub-resources rather than resetting them to defaults.                                                                                                                                                                    | `--app`, `--instance`, `--file`, `--json`, `--dry-run`, `--yes`, `--destructive`                                                                                                 |
| `clerk apps {list,create}`    | List or create Clerk applications. Defaults to JSON in agent mode.                                                                                                                                                                                                                                                  | (see `--help`)                                                                                                                                                                   |
| `clerk users` (no subcommand) | Interactive picker for `users` actions in human mode; in agent mode prints the action list and exits `2`. Always pass an explicit subcommand from agents.                                                                                                                                                           | `--app`, `--instance`, `--secret-key`                                                                                                                                            |
| `clerk users list`            | List users via curated BAPI flags. JSON output (default when piped or in agent mode) is `{data, hasMore}` so callers can paginate without `/users/count`. `--limit` defaults to 100 (max 250).                                                                                                                      | `--limit`, `--offset`, `--query`, `--email-address`, `--phone-number`, `--username`, `--user-id`, `--external-id`, `--order-by`, `--json`, `--app`, `--instance`, `--secret-key` |
| `clerk users create`          | Create a user from curated flags or a raw BAPI body. **No confirmation prompt in any mode** - it writes immediately. `--yes` is accepted but has no effect. `--dry-run` is the only safety net; preview with it first.                                                                                                                                                                                                                            | `--email`, `--phone`, `--username`, `--password`, `--first-name`, `--last-name`, `--external-id`, `-d, --data`, `--file`, `--dry-run`, `--yes`, `--json`                         |
| `clerk users open [user-id]`  | Open a user's dashboard page. Agent mode requires `user-id` and prints a JSON descriptor instead of launching a browser.                                                                                                                                                                                            | (see `--help`)                                                                                                                                                                   |
| `clerk impersonate [user]`    | Sign in as a user for debugging: creates a short-lived actor token and prints the sign-in URL. Alias: `clerk imp`. Requires `clerk auth login` (no `--secret-key`-only bypass) — every token is stamped `cli:<email>` for auditability. `[user]` accepts a `user_...` ID, exact email, or fuzzy search term. On production it bypasses the user's MFA and may count against the impersonation quota — confirm with the user first. | `--print`, `--open`, `--yes`, `--expires-in <seconds>` (default 3600), `--actor <context>`, `--app`, `--instance`                                                                |
| `clerk impersonate revoke <actor-token-id>` | Revoke a pending actor token. The token `id` is printed only at creation (the Backend API has no actor-token list endpoint), so capture it then.                                                                                                                                                      | `--app`, `--instance`                                                                                                                                                            |
| `clerk open [subpath]`        | Open the linked app's dashboard in a browser. Agent mode: prints a JSON descriptor instead of opening.                                                                                                                                                                                                              | (see `--help`)                                                                                                                                                                   |
| `clerk deploy`                | Human-mode production deploy wizard. Agent mode: emits a read-only JSON handoff and tells the agent whether to ask the human to run the wizard, wait for provisioning, finish OAuth, or do nothing.                                                                                                                 | `--mode agent`, `--mode human`, `--verbose`                                                                                                                                      |
| `clerk deploy status`         | Read-only deploy verification. Triggers a DNS check, reports aggregate domain and OAuth readiness, and exits `0` only when complete. Agent mode does one quick check by default; pass `--wait` to keep waiting.                                                                                                     | `--mode agent`, `--wait`, `--verbose`                                                                                                                                            |
| `clerk webhooks listen`       | First-party local webhook tunnel (like `stripe listen`): opens a Svix relay inbox URL and forwards each delivery to your local handler. No auth, no linked project, no Clerk API. Full flow in [references/recipes.md](references/recipes.md#webhooks-local-testing).                                                 | `--forward-to <url>` (required), `--token <c_token>`, `-H, --header <k:v>` (repeatable), `--json` (NDJSON)                                                                       |
| `clerk webhooks token`        | Mint a relay token (`c_` + 10 base62 chars) to pin a stable `listen` inbox URL across machines: `clerk webhooks listen --token "$(clerk webhooks token)" --forward-to ...`.                                                                                                                                          | `--json`                                                                                                                                                                         |
| `clerk webhooks verify`       | Verify a webhook signature offline (pure local HMAC, no auth): from a saved `listen` event line (`--delivery @event.json`) or from the four raw values.                                                                                                                                                             | `--secret <whsec>` (required), `--delivery @file`, `--payload @file`, `--id`, `--timestamp`, `--signature`, `--json`                                                             |
| `clerk enable orgs` / `clerk disable orgs` | Toggle Organizations on the instance. For org features, components, and API usage, see the `clerk-orgs` skill.                                                                                                                                                                                         | `--force-selection`, `--auto-create`, `--max-members <n>`, `--domains`, `--dry-run`, `--yes`, `--app`, `--instance`                                                              |
| `clerk enable billing` / `clerk disable billing` | Toggle billing for users and/or orgs (defaults to both). For plans, pricing components, and entitlements, see the `clerk-billing` skill.                                                                                                                                                         | `--for <orgs\|users>`, `--dry-run`, `--yes`, `--no-skills` (enable only), `--app`, `--instance`                                                                                  |
| `clerk doctor`                | Health check (CLI version, login, link, env, config, completion; plus host-execution probe in agent mode).                                                                                                                                                                                                          | `--json`, `--spotlight`, `--verbose`, `--fix`                                                                                                                                    |
| `clerk api [path]`            | Authenticated HTTP to Backend/Platform API.                                                                                                                                                                                                                                                                         | `-X`, `-d`, `--file`, `--dry-run`, `--yes`, `--include`, `--app`, `--secret-key`, `--instance`, `--platform`                                                                     |
| `clerk api ls [filter]`       | Discover endpoints from the bundled OpenAPI catalog.                                                                                                                                                                                                                                                                | (see `--help`)                                                                                                                                                                   |
| `clerk completion [shell]`    | Print a shell completion script (`bash`, `zsh`, `fish`, `powershell`).                                                                                                                                                                                                                                              | -                                                                                                                                                                                |
| `clerk update`                | Update the CLI to the latest version.                                                                                                                                                                                                                                                                               | `--channel`, `-y`, `--all`                                                                                                                                                       |

**`clerk <command> --help` is the source of truth for flags.** This table is a hint, not a spec. Before running an unfamiliar command or flag combination, run `clerk <command> --help` once per session. Every command also defines `setExamples([...])` in source, which `--help` renders as a copy-pasteable Examples block, so you rarely need to guess syntax.

## Agent-mode behavior (important)

The CLI auto-detects agent mode when stdout is not a TTY, or when `--mode agent` / `CLERK_MODE=agent` is set. In agent mode:

- **Interactive prompts are disabled.** Commands that would normally show pickers (`link` without `--app`, `unlink` without `--yes`, `users` without a subcommand) either auto-resolve or exit with a usage error. `clerk api` with no args prints usage guidance and exits 0; pass an endpoint (or `ls`) explicitly. Always pass explicit flags (`--app`, `--yes`) in scripted calls.
- **Host-sensitive operations emit a sandbox warning once per invocation.** Home-directory Clerk state, keychain access, networked Clerk calls, browser launch, and localhost OAuth callback setup can trigger the warning shown above. If it appears, rerun the same command on the host before trusting the result.
- **If your harness does not clearly present as agent mode, force it.** Use `--mode agent` or `CLERK_MODE=agent` when you want the CLI's non-interactive behavior and sandbox warning path to apply deterministically.
- **`link` supports deterministic agent flows.** In agent mode, `clerk link --app <id>` links directly. Without `--app`, the CLI will try silent key-based autolink first; if it cannot determine the app unambiguously, it exits and tells you to pass `--app`.
- **`init` never selects or creates a real Clerk app for you in agent mode unless authenticated or given a target.** Pass `--app <id>` (or pre-link the project) to authenticate and link a real app, or pass `--keyless` to use auto-generated temporary development keys when bootstrapping a new project on a keyless-capable framework. Without either, agent mode prints manual setup guidance and exits cleanly.
- **`unlink` requires `--yes` in agent mode.** It gates on `isAgent() && !options.yes` and exits with a usage error without it. This is the exception, not the pattern - see the next bullet.
- **Only `unlink` actually requires `--yes`.** Every other confirmation gate is written as `isHuman() && !options.yes`, so agent mode skips it outright: the mutation executes with no prompt and no error. Passing `--yes` is harmless but changes nothing. Do not treat it as a safety gate - `--dry-run` is the real one.
- **`impersonate` requires the `[user]` positional in agent mode.** If a search term matches multiple users, it exits `2` listing candidate user IDs — retry with a specific `user_...` ID. Output is a JSON object (`{url, id, userId, actor, ...}`); surface `url` to the user and capture `id` — it is the only chance to record the revoke handle.
- **`webhooks listen` is long-running.** Run it in the background. In agent mode (or with `--json`) it emits NDJSON: one `ready` line (`{type:"ready", relay_url, forward_to}`), then one `event` line per delivery — each event line can be fed back to `clerk webhooks verify --delivery`.
- **`doctor --fix` is ignored.** Parse `doctor --json` output's `remedy` field and act on it yourself.
- **`apps list` and `apps create` default to JSON** when piped.
- **`users` defaults to JSON when piped, like `apps`.** `clerk users list` and `clerk users create` emit JSON in agent mode. Bare `clerk users` (no subcommand) is a usage error in agent mode - pass `list`, `create`, or `open` explicitly. `clerk users open` requires the `user-id` positional in agent mode and prints a JSON descriptor instead of launching a browser.
- **`deploy` has an agent handoff plus a verification gate.** In agent mode, bare `clerk deploy` is read-only and emits a JSON handoff. It never drives the interactive wizard. Do not tell Claude or another agent to run `! clerk deploy`, because the wizard needs interactive stdin prompts. Ask the human to run `clerk deploy` in a new terminal window when needed, then run `clerk deploy status --mode agent` to verify completion. See [references/agent-mode.md](references/agent-mode.md#deploy-handoff-and-verification).
- **`--input-json <json|@file|->`** expands JSON into flags on any command (e.g. `clerk init --input-json '{"framework":"next","yes":true}'`). Stdin needs the explicit `-` marker (`echo '{"yes":true}' | clerk init --input-json -`); bare piped stdin is **not** auto-detected, so shell loops and self-reading commands (`cat body.json | clerk api …`) are untouched. Place `--input-json` after the leaf subcommand. Full rules in [references/agent-mode.md](references/agent-mode.md#passing-options-as-json---input-json).

Full matrix and sandbox details in [references/agent-mode.md](references/agent-mode.md).

## Output format and errors

- **JSON output:** `--json` on `apps list` and `doctor`. For `clerk api`, the response body is the raw API JSON, so pipe into `jq` freely.
- **Exit codes:** `0` success, `1` runtime error, `2` usage/validation error. `doctor` returns `1` if any check failed.
- **Error format:** User-facing errors print a single line to stderr and set a non-zero exit code. Use `--verbose` for stack traces when debugging.

## Safety rules for autonomous use

1. **Discover before acting:** `clerk api ls <keyword>` before `clerk api <path>`.
2. **Preview mutations:** `--dry-run` on every `config patch`, `config put`, `api -X POST/PATCH/PUT/DELETE`.
3. **Target explicitly in production:** pass `--instance prod` rather than relying on defaults, and confirm with the user before any production mutation.
4. **Never commit secrets:** `env pull` writes to `.env.local` (which should be gitignored). Don't paste secret keys into code or chat.
5. **Use `doctor --json`** to diagnose before assuming the CLI is broken.

## References

- [references/auth.md](references/auth.md) - auth flow, key resolution order, host-vs-sandbox behavior, `--app`/`--instance` targeting, Backend vs Platform API.
- [references/recipes.md](references/recipes.md) - copy-pasteable recipes for common Clerk tasks.
- [references/agent-mode.md](references/agent-mode.md) - agent-mode behavior matrix, sandbox warning semantics, exit codes, error format.

<!-- chapter:end slug=clerk-cli -->

---

<!-- chapter:begin slug=clerk-custom-ui position=5 -->

## 5. clerk-custom-ui

- **Source:** https://github.com/clerk/skills/blob/main/skills/core/clerk-custom-ui/SKILL.md
- **Raw:** https://raw.githubusercontent.com/clerk/skills/main/skills/core/clerk-custom-ui/SKILL.md
- **Markdown:** https://skillsdocs.com/clerk/skills/clerk-custom-ui.md
- **Licence:** MIT — https://spdx.org/licenses/MIT.html

Bundled files (5), referenced from this skill's directory:
  - `core-2/custom-sign-in.md` — https://raw.githubusercontent.com/clerk/skills/main/skills/core/clerk-custom-ui/core-2/custom-sign-in.md
  - `core-2/custom-sign-up.md` — https://raw.githubusercontent.com/clerk/skills/main/skills/core/clerk-custom-ui/core-2/custom-sign-up.md
  - `core-3/custom-sign-in.md` — https://raw.githubusercontent.com/clerk/skills/main/skills/core/clerk-custom-ui/core-3/custom-sign-in.md
  - `core-3/custom-sign-up.md` — https://raw.githubusercontent.com/clerk/skills/main/skills/core/clerk-custom-ui/core-3/custom-sign-up.md
  - `core-3/show-component.md` — https://raw.githubusercontent.com/clerk/skills/main/skills/core/clerk-custom-ui/core-3/show-component.md

<!-- Verbatim upstream SKILL.md follows, YAML frontmatter included. -->

---
name: clerk-custom-ui
description: Custom authentication flows and component appearance - hooks (useSignIn,
  useSignUp), themes, colors, fonts, CSS. Use for custom sign-in/sign-up flows, appearance
  styling, visual customization, branding.
allowed-tools: WebFetch
license: MIT
metadata:
  author: clerk
  version: 2.3.0
---

# Custom UI

> **Prerequisite**: Ensure `ClerkProvider` wraps your app. See `clerk-setup` skill.
>
> **Version**: Check `package.json` for the SDK version — see `clerk` skill for the version table. This determines which custom flow references to use below.

This skill covers two areas:
1. **Custom authentication flows** — build your own sign-in/sign-up UI with hooks
2. **Appearance customization** — theme, style, and brand Clerk's pre-built components

## What Do You Need?

| Task | Reference |
|------|-----------|
| Custom sign-in (Core 2 / LTS) | core-2/custom-sign-in.md |
| Custom sign-up (Core 2 / LTS) | core-2/custom-sign-up.md |
| Custom sign-in (Current SDK v7+) | core-3/custom-sign-in.md |
| Custom sign-up (Current SDK v7+) | core-3/custom-sign-up.md |
| Show component pattern (Current SDK) | core-3/show-component.md |

## Custom Flow References

| Task | Core 2 | Current |
|------|--------|---------|
| Custom sign-in (useSignIn) | `core-2/custom-sign-in.md` | `core-3/custom-sign-in.md` |
| Custom sign-up (useSignUp) | `core-2/custom-sign-up.md` | `core-3/custom-sign-up.md` |
| `<Show>` component | *(use `<SignedIn>`, `<SignedOut>`, `<Protect>`)* | `core-3/show-component.md` |

---

## Appearance Customization

Appearance customization applies to both Core 2 and the current SDK.

### Component Customization Options

| Task | Documentation |
|------|---------------|
| Appearance prop overview | https://clerk.com/docs/nextjs/guides/customizing-clerk/appearance-prop/overview |
| Options (structure, logo, buttons) | https://clerk.com/docs/nextjs/guides/customizing-clerk/appearance-prop/layout |
| Themes (pre-built dark/light) | https://clerk.com/docs/nextjs/guides/customizing-clerk/appearance-prop/themes |
| Variables (colors, fonts, spacing) | https://clerk.com/docs/nextjs/guides/customizing-clerk/appearance-prop/variables |
| CAPTCHA configuration | https://clerk.com/docs/nextjs/guides/customizing-clerk/appearance-prop/captcha |
| Bring your own CSS | https://clerk.com/docs/nextjs/guides/customizing-clerk/appearance-prop/bring-your-own-css |

### Appearance Pattern

```typescript
<SignIn
  appearance={{
    variables: {
      colorPrimary: '#0000ff',
      borderRadius: '0.5rem',
    },
    options: {
      logoImageUrl: '/logo.png',
      socialButtonsVariant: 'iconButton',
    },
  }}
/>
```

> **Core 2 ONLY (skip if current SDK):** The `options` property was named `layout`. Use `layout: { logoImageUrl: '...', socialButtonsVariant: '...' }` instead of `options`.

### variables (colors, typography, borders)

| Property | Description |
|----------|-------------|
| `colorPrimary` | Primary color throughout |
| `colorBackground` | Background color |
| `borderRadius` | Border radius (default: `0.375rem`) |

**Opacity change:** `colorRing` and `colorModalBackdrop` now render at full opacity. Use explicit `rgba()` values if you need transparency.

> **Core 2 ONLY (skip if current SDK):** `colorRing` and `colorModalBackdrop` rendered at 15% opacity by default.

### options (structure, logo, social buttons)

| Property | Description |
|----------|-------------|
| `logoImageUrl` | URL to custom logo |
| `socialButtonsVariant` | `'blockButton'` \| `'iconButton'` \| `'auto'` |
| `socialButtonsPlacement` | `'top'` \| `'bottom'` |
| `showOptionalFields` | Show optional fields (default: `false`) |

> **Core 2 ONLY (skip if current SDK):** This property is called `layout`, not `options`. Also, `showOptionalFields` defaulted to `true`.

### Themes

Install themes from `@clerk/ui`:

```bash
npm install @clerk/ui
```

```typescript
import { dark } from '@clerk/ui/themes'

<ClerkProvider appearance={{ theme: dark }} />
```

> **Core 2 ONLY (skip if current SDK):** Themes are imported from `@clerk/themes` (install `@clerk/themes` instead of `@clerk/ui`). Example: `import { dark } from '@clerk/themes'`.

**Theme stacking** — pass an array where the last theme takes precedence:

```typescript
import { dark, neobrutalism } from '@clerk/ui/themes'

<ClerkProvider appearance={{ theme: [dark, neobrutalism] }} />
```

**CSS `color-scheme` support** — the default theme respects the CSS `color-scheme` property for automatic light/dark mode toggling.

**Available themes:** `dark`, `neobrutalism`, `shadcn`, `simple`

#### shadcn Theme

> **IMPORTANT:** If the project has `components.json` (shadcn/ui installed), ALWAYS apply the shadcn theme as the FIRST customization step. This ensures Clerk components visually match the existing shadcn design system. Skip this only if the user explicitly requests a different theme.

**If the project uses shadcn/ui** (check for `components.json` in the project root), **always use the shadcn theme**:

```typescript
import { shadcn } from '@clerk/ui/themes'

<ClerkProvider appearance={{ theme: shadcn }} />
```

Also import shadcn CSS in your global styles:
```css
@import 'tailwindcss';
@import '@clerk/ui/themes/shadcn.css';
```

> **Core 2 ONLY (skip if current SDK):** Import from `@clerk/themes` and `@clerk/themes/shadcn.css`:
> ```typescript
> import { shadcn } from '@clerk/themes'
> ```
> ```css
> @import '@clerk/themes/shadcn.css';
> ```

## Workflow

1. Identify customization needs (custom flow or appearance)
2. For custom flows: check SDK version → read appropriate `core-2/` or `core-3/` reference
3. For appearance: WebFetch the appropriate documentation from table above
4. Apply appearance prop to your Clerk components or build custom flow with hooks

## Common Pitfalls

| Issue | Solution |
|-------|----------|
| Colors not applying | Use `colorPrimary` not `primaryColor` |
| Logo not showing | Put `logoImageUrl` inside `options: {}` (or `layout: {}` in Core 2) |
| Social buttons wrong | Add `socialButtonsVariant: 'iconButton'` in `options` (or `layout` in Core 2) |
| Styling not working | Use appearance prop, not direct CSS (unless with bring-your-own-css) |
| Hook returns different shape | Check SDK version — Core 2 and current have completely different `useSignIn`/`useSignUp` APIs |

## See Also

- `clerk-setup` - Initial Clerk install
- `clerk-nextjs-patterns` - Next.js patterns
- `clerk-orgs` - B2B organizations

<!-- chapter:end slug=clerk-custom-ui -->

---

<!-- chapter:begin slug=clerk-setup position=6 -->

## 6. clerk-setup

- **Source:** https://github.com/clerk/skills/blob/main/skills/core/clerk-setup/SKILL.md
- **Raw:** https://raw.githubusercontent.com/clerk/skills/main/skills/core/clerk-setup/SKILL.md
- **Markdown:** https://skillsdocs.com/clerk/skills/clerk-setup.md
- **Licence:** MIT — https://spdx.org/licenses/MIT.html

Bundled files (1), referenced from this skill's directory:
  - `evals/evals.json` — https://raw.githubusercontent.com/clerk/skills/main/skills/core/clerk-setup/evals/evals.json

<!-- Verbatim upstream SKILL.md follows, YAML frontmatter included. -->

---
name: clerk-setup
description: Add Clerk authentication to any project by following the official quickstart
  guides.
license: MIT
allowed-tools: WebFetch
compatibility: Requires NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY and CLERK_SECRET_KEY (or framework-specific equivalents like VITE_CLERK_PUBLISHABLE_KEY for Vite-based apps). Keys can be auto-generated via Keyless on first SDK initialization, or pulled from the Clerk Dashboard. Requires Node.js 20.9.0 or higher.
metadata:
  author: clerk
  version: 2.3.0
---

# Adding Clerk

> **Version**: Check `package.json` for the SDK version — see `clerk` skill for the version table. Core 2 differences are noted inline with `> **Core 2 ONLY (skip if current SDK):**` callouts.

This skill sets up Clerk for authentication by following the official quickstart documentation. For agents, the `clerk` CLI handles most of this end to end — see the next section.

## Agent-first: Provision via CLI

The `clerk` CLI replaces most Dashboard clicks. Three scenarios cover almost everything:

### Scenario A — New project, new Clerk app

```bash
clerk init --framework <next|react|vue|nuxt|astro|react-router|tanstack-react-start|expressjs|fastify|expo> -y
```

`clerk init` creates the Clerk app via PLAPI, links the project, writes the framework-specific publishable + secret keys to the right env file (e.g. `.env.local` for Next.js, `.env` for Vite-based projects), and installs the SDK package.

### Scenario B — Existing project, existing Clerk app

```bash
clerk auth login                      # one-time OAuth (skip if already logged in)
clerk link                            # autolinks if a CLERK_PUBLISHABLE_KEY is in your .env
clerk link --app app_xxx              # explicit form, required in agent mode
clerk env pull                        # writes the framework-detected env vars
```

### Scenario C — Existing project, new Clerk app

```bash
clerk auth login
clerk apps create "My App" --json     # returns the new app_id
clerk link --app app_xxx
clerk env pull
```

### Daily ops

```bash
clerk env pull                        # refresh keys (uses linked profile)
clerk env pull --instance prod        # production keys
clerk doctor --json                   # framework integration health check
```

### Rotate the secret key (replaces Dashboard rotation)

PLAPI exposes secret-key rotation directly. Use raw `clerk api` until the friendly wrapper ships:

```bash
clerk api --platform POST /v1/platform/applications/<app_id>/rotate_secret_keys \
  -d '{"delay_old_secrets_expiration_hours": 24, "reason": "scheduled rotation"}'
```

`delay_old_secrets_expiration_hours` keeps the old key valid for the grace period so deploys can roll forward without downtime.

### Notes for agents

- `clerk link` (no flags) only autolinks when a `CLERK_PUBLISHABLE_KEY` is already in `.env` / `.env.local`. Without it, agent mode errors out: "Cannot select an application in agent mode." When that happens, run `clerk apps list --json`, and ask the user which `app_id` to link rather than guessing.
- Pass `--json` on `apps list/create`, `users create`, and `doctor` for parseable output.
- The CLI auto-detects framework env var names (`VITE_CLERK_PUBLISHABLE_KEY` for Vite, `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY` for Next.js, etc.) and target file (`.env.development.local` > `.env.local` > `.env`).

## Quick Reference (Dashboard fallback)

If the CLI isn't an option (sandboxed environments, docs walkthroughs), here's the manual Dashboard path:

| Step | Action |
|------|--------|
| 1. Detect framework | Check `package.json` dependencies |
| 2. Fetch quickstart | Use WebFetch on the appropriate docs URL |
| 3. Follow instructions | Execute steps; create `proxy.ts` (Next.js <=15: `middleware.ts`) |
| 4. Get API keys | From [dashboard.clerk.com](https://dashboard.clerk.com/~/api-keys) |

> If the project has `components.json` (shadcn/ui), apply the shadcn theme after setup. See `clerk-custom-ui` skill → shadcn Theme.

## Framework Detection

Check `package.json` to identify the framework:

| Dependency | Framework | Quickstart URL |
|------------|-----------|----------------|
| `next` | Next.js | `https://clerk.com/docs/nextjs/getting-started/quickstart` |
| `@remix-run/react` | Remix (deprecated) | Migrate to React Router v7 — use the React Router quickstart below |
| `react-router` | React Router (v7+) | `https://clerk.com/docs/react-router/getting-started/quickstart` |
| `astro` | Astro | `https://clerk.com/docs/astro/getting-started/quickstart` |
| `nuxt` | Nuxt | `https://clerk.com/docs/nuxt/getting-started/quickstart` |
| `@tanstack/react-start` | TanStack Start | `https://clerk.com/docs/tanstack-react-start/getting-started/quickstart` |
| `react` (no framework) | React SPA | `https://clerk.com/docs/react/getting-started/quickstart` |
| `vue` | Vue | `https://clerk.com/docs/vue/getting-started/quickstart` |
| `express` | Express | `https://clerk.com/docs/expressjs/getting-started/quickstart` |
| `fastify` | Fastify | `https://clerk.com/docs/fastify/getting-started/quickstart` |
| `expo` | Expo | `https://clerk.com/docs/expo/getting-started/quickstart` |

For other platforms:
- **Chrome Extension**: `https://clerk.com/docs/chrome-extension/getting-started/quickstart`
- **Android**: `https://clerk.com/docs/android/getting-started/quickstart`
- **iOS**: `https://clerk.com/docs/ios/getting-started/quickstart`
- **Vanilla JavaScript**: `https://clerk.com/docs/js-frontend/getting-started/quickstart`

## Decision Tree

```
User Request: "Add Clerk" / "Add authentication"
    │
    ├─ Read package.json
    │
    ├─ Existing auth detected?
    │   ├─ YES → Audit → Migration plan
    │   └─ NO → Fresh install
    │
    ├─ Identify framework → WebFetch quickstart → Follow instructions
    │   └─ Next.js? → Create proxy.ts (Next.js <=15: middleware.ts)
    │
    └─ components.json exists? → YES → Apply shadcn theme (see clerk-custom-ui)
```

## Setup Process

### 1. Detect the Framework

Read the project's `package.json` and match dependencies to the table above.

### 2. Fetch the Quickstart Guide

Use WebFetch to retrieve the official quickstart for the detected framework:

```
WebFetch: https://clerk.com/docs/{framework}/getting-started/quickstart
Prompt: "Extract the complete setup instructions including all code snippets, file paths, and configuration steps."
```

### 3. Follow the Instructions

Execute each step from the quickstart guide:
- Install the required packages
- Set up environment variables
- Add the provider and proxy/middleware
- Create sign-in/sign-up routes if needed
- Test the integration

> **Next.js:** Create `proxy.ts` (Next.js <=15: `middleware.ts`). See the `clerk-nextjs-patterns` skill for middleware strategies.

> **shadcn/ui detected** (`components.json` exists): ALWAYS apply the shadcn theme. See `clerk-custom-ui` skill → shadcn Theme section.

### 4. Get API Keys

Two paths for development API keys:

**Keyless (Automatic)**
- On first SDK initialization, Clerk auto-generates dev keys and shows a "Configure your application" button in the bottom right of the running app
- No manual key setup required, keys are created and injected automatically
- Selecting "Configure your application" associates the auto-generated app with your Clerk account so you can edit it from the Dashboard
- Simplest path for new projects

**Manual (Dashboard)**
- Get keys from [dashboard.clerk.com](https://dashboard.clerk.com/~/api-keys) if Keyless doesn't trigger
- **Publishable Key**: Starts with `pk_test_` or `pk_live_`
- **Secret Key**: Starts with `sk_test_` or `sk_live_`
- Set as environment variables: `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY` and `CLERK_SECRET_KEY`

## Migrating from Another Auth Provider

If the project already has authentication, create a migration plan before replacing it.

### Detect Existing Auth

Check `package.json` for existing auth libraries:
- `next-auth` / `@auth/core` → NextAuth/Auth.js
- `@supabase/supabase-js` → Supabase Auth
- `firebase` / `firebase-admin` → Firebase Auth
- `@aws-amplify/auth` → AWS Cognito
- `auth0` / `@auth0/nextjs-auth0` → Auth0
- `passport` → Passport.js
- Custom JWT/session implementation

### Migration Process

1. **Audit current auth** - Identify all auth touchpoints:
   - Sign-in/sign-up pages
   - Session/token handling
   - Protected routes and middleware
   - User data storage (database tables, external IDs)
   - OAuth providers configured

2. **Create migration plan** - Consider:
   - **User data export** - Export users and import via Clerk's Backend API
   - **Password hashes** - Clerk can upgrade hashes to Bcrypt transparently
   - **External IDs** - Store legacy user IDs as `external_id` in Clerk
   - **Session handling** - Existing sessions will terminate on switch

3. **Choose migration strategy**:
   - **Big bang** - Switch all users at once (simpler, requires maintenance window)
   - **Trickle migration** - Run both systems temporarily (lower risk, higher complexity)

### Migration Reference

- **Migration Overview**: https://clerk.com/docs/guides/development/migrating/overview

## SDK Notes

### Package Names

| Package | Install |
|---------|---------|
| Next.js | `@clerk/nextjs` |
| React | `@clerk/react` |
| Expo | `@clerk/expo` |
| React Router | `@clerk/react-router` |
| TanStack Start | `@clerk/tanstack-react-start` |

> **Core 2 ONLY (skip if current SDK):** React and Expo packages have different names: `@clerk/clerk-react` and `@clerk/clerk-expo` (with `clerk-` prefix).

### ClerkProvider Placement (Next.js)

`ClerkProvider` must be placed **inside `<body>`**, not wrapping `<html>`:

```tsx
// root layout.tsx
export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <ClerkProvider>{children}</ClerkProvider>
      </body>
    </html>
  )
}
```

> **Core 2 ONLY (skip if current SDK):** `ClerkProvider` can wrap `<html>` directly.

### Dynamic Rendering (Next.js)

For dynamic rendering with auth data, use the `dynamic` prop:

```tsx
<ClerkProvider dynamic>{children}</ClerkProvider>
```

### Node.js Requirement

Requires **Node.js 20.9.0** or higher.

> **Core 2 ONLY (skip if current SDK):** Minimum Node.js 18.17.0.

### Themes Package

Themes are installed from `@clerk/ui`:

```bash
npm install @clerk/ui
```

> **Core 2 ONLY (skip if current SDK):** Themes are from `@clerk/themes` instead of `@clerk/ui`.

### shadcn Theme

If the project uses shadcn/ui (check for `components.json` in the project root), apply the shadcn theme so Clerk components match the app's design system:

```bash
npm install @clerk/ui
```

```tsx
import { shadcn } from '@clerk/ui/themes'

<ClerkProvider appearance={{ theme: shadcn }}>{children}</ClerkProvider>
```

Also import the shadcn CSS in your global styles:
```css
@import 'tailwindcss';
@import '@clerk/ui/themes/shadcn.css';
```

> **Core 2 ONLY (skip if current SDK):** Import from `@clerk/themes` and `@clerk/themes/shadcn.css` instead.

## Common Pitfalls

> **Run `clerk doctor` first.** It checks framework integration, env vars, middleware presence, and SDK install status. Fixes a lot of these in one shot.

| Issue | Solution |
|-------|----------|
| Missing `await` on `auth()` | In Next.js 15+, `auth()` is async: `const { userId } = await auth()` |
| Exposing `CLERK_SECRET_KEY` | Never use the secret key in client code; only `NEXT_PUBLIC_*` keys are safe |
| Missing middleware matcher | Include API routes: `matcher: ['/((?!.*\\..*|_next).*)', '/']` |
| ClerkProvider placement | Must be inside `<body>` in root layout (Core 2: could wrap `<html>`) |
| Auth routes not public | Allow `/sign-in`, `/sign-up` in middleware config |
| Landing page requires auth | To keep "/" public, exclude it: `matcher: ['/((?!.*\\..*|_next|^/$).*)', '/api/(.*)']` |
| Wrong import path | Server code uses `@clerk/nextjs/server`, client uses `@clerk/nextjs` |
| Wrong package name | Use `@clerk/react` not `@clerk/clerk-react` (Core 2 naming) |

## See Also

- `clerk-custom-ui` - Custom sign-in/up components
- `clerk-nextjs-patterns` - Advanced Next.js patterns
- `clerk-react-patterns` - React SPA patterns
- `clerk-react-router-patterns` - React Router patterns
- `clerk-vue-patterns` - Vue patterns
- `clerk-nuxt-patterns` - Nuxt patterns
- `clerk-astro-patterns` - Astro patterns
- `clerk-tanstack-patterns` - TanStack Start patterns
- `clerk-chrome-extension-patterns` - Chrome Extension patterns
- `clerk-orgs` - B2B multi-tenant organizations
- `clerk-webhooks` - Webhook → database sync
- `clerk-testing` - E2E testing setup
- `clerk-swift` - Native iOS auth
- `clerk-android` - Native Android auth
- `clerk-expo` - Expo / React Native auth
- `clerk-backend-api` - Backend REST API explorer

## Documentation

- **Quickstart Overview**: https://clerk.com/docs/getting-started/quickstart/overview
- **Migration Guide**: https://clerk.com/docs/guides/development/migrating/overview
- **Full Documentation**: https://clerk.com/docs

<!-- chapter:end slug=clerk-setup -->

---

<!-- chapter:begin slug=clerk position=7 -->

## 7. clerk

- **Source:** https://github.com/clerk/skills/blob/main/skills/core/clerk/SKILL.md
- **Raw:** https://raw.githubusercontent.com/clerk/skills/main/skills/core/clerk/SKILL.md
- **Markdown:** https://skillsdocs.com/clerk/skills/clerk.md
- **Licence:** MIT — https://spdx.org/licenses/MIT.html

<!-- Verbatim upstream SKILL.md follows, YAML frontmatter included. -->

---
name: clerk
description: Clerk authentication router. Use when user asks about Clerk CLI operations,
  adding authentication, setting up Clerk, custom sign-in flows, Swift or native iOS
  auth, native Android auth, Next.js patterns, React patterns, Vue patterns, Nuxt
  patterns, Astro patterns, TanStack Start patterns, Expo patterns, React Router
  patterns, Chrome Extension patterns, organizations, billing, subscriptions, payments,
  pricing, plans, seat-based pricing, feature entitlements, syncing users, testing,
  impersonating a user, or testing webhooks locally.
  Automatically routes to the specific skill based on their task.
license: MIT
metadata:
  version: 2.0.0
---

# Clerk Skills Router

## Version Detection

Check `package.json` to determine the Clerk SDK version. This determines which patterns to use:

| Package | Core 2 (LTS until Jan 2027) | Current |
|---------|----------------------------|---------|
| `@clerk/nextjs` | v5–v6 | v7+ |
| `@clerk/react` or `@clerk/clerk-react` | v5–v6 | v7+ |
| `@clerk/expo` or `@clerk/clerk-expo` | v1–v2 | v3+ |
| `@clerk/react-router` | v1–v2 | v3+ |
| `@clerk/tanstack-react-start` | < v0.26.0 | v0.26.0+ |

**Default to current** if the version is unclear or the project is new. Core 2 packages use `@clerk/clerk-react` and `@clerk/clerk-expo` (with `clerk-` prefix); current packages use `@clerk/react` and `@clerk/expo`.

All skills are written for the current SDK. When something differs in Core 2, it's noted inline with `> **Core 2 ONLY (skip if current SDK):**` callouts. The exception is `clerk-custom-ui`, which has separate `core-2/` and `core-3/` directories for custom flow hooks since those APIs are entirely different between versions.

---

## By Task

**Adding Clerk to your project** → Use `clerk-setup`
- Framework detection and quickstart
- Environment setup, API keys, Keyless flow
- Migration from other auth providers

**Operating Clerk from the CLI** → Use `clerk-cli`
- Auth, linking, `doctor`, and environment pulls
- User, org, session, app, and instance management
- Impersonating a user (`clerk impersonate`) and local webhook testing (`clerk webhooks listen`)
- Feature toggles (`clerk enable orgs`, `clerk enable billing`)
- Backend, Platform, and Frontend API calls through `clerk api`
- Deploy handoff and deploy status verification

**Custom sign-in/sign-up UI** → Use `clerk-custom-ui`
- Custom authentication flows with `useSignIn` / `useSignUp` hooks
- Appearance and styling (themes, colors, layout)
- `<Show>` component for conditional rendering

**Advanced Next.js patterns** → Use `clerk-nextjs-patterns`
- Server vs Client auth APIs
- Middleware strategies
- Server Actions, caching
- API route protection

**React patterns** → Use `clerk-react-patterns`
- Hooks (`useAuth`, `useUser`, `useClerk`)
- Protected routes, auth guards
- Router integration

**React Router patterns** → Use `clerk-react-router-patterns`
- Loaders & actions with auth
- Route protection
- SSR auth

**Vue patterns** → Use `clerk-vue-patterns`
- Composables (`useAuth`, `useUser`, `useClerk`)
- Vue Router guards
- Pinia auth store integration

**Nuxt patterns** → Use `clerk-nuxt-patterns`
- Server middleware auth
- SSR auth with composables
- Server API routes

**Astro patterns** → Use `clerk-astro-patterns`
- SSR auth pages
- Island components with React
- Middleware & API routes

**TanStack Start patterns** → Use `clerk-tanstack-patterns`
- Server functions with auth
- Route protection via loaders
- Vinxi server integration

**Expo / React Native auth** → Use `clerk-expo`
- Prebuilt native components (AuthView, UserButton)
- Custom flows: email, password, SMS/phone OTP, MFA
- OAuth/SSO and native Google/Apple sign-in
- Expo Router protected routes, token storage, push notifications

**Chrome Extension patterns** → Use `clerk-chrome-extension-patterns`
- Background scripts auth
- Popup auth flows
- Content scripts with sync host

**B2B / Organizations** → Use `clerk-orgs`
- Multi-tenant apps
- Organization slugs in URLs
- Roles, permissions, RBAC
- Member management

**Billing & Subscriptions** → Use `clerk-billing`
- `<PricingTable />` component
- Plan and feature gating with `has()`
- Seat-based B2B billing with organizations
- Subscription lifecycle webhooks
- Free trials, invoicing

**Webhooks** → Use `clerk-webhooks`
- Real-time events
- Data syncing
- Notifications & integrations

**E2E Testing** → Use `clerk-testing`
- Playwright/Cypress setup
- Auth flow testing
- Test utilities

**Swift / native iOS auth** → Use `clerk-swift`
- Native iOS Swift and SwiftUI projects
- ClerkKit and ClerkKitUI implementation guidance
- Source-driven patterns from `clerk-ios`

**Android / native mobile auth** → Use `clerk-android`
- Native Android Kotlin and Jetpack Compose projects
- `clerk-android-api` and `clerk-android-ui` implementation guidance
- Source-driven patterns from `clerk-android`
- Do not use for Expo or React Native projects

**Backend REST API** → Use `clerk-backend-api`
- Browse API tags and endpoints
- Inspect endpoint schemas
- Execute API requests with scope enforcement

## Quick Navigation

If you know your task, you can directly access:
- `/clerk-setup` - Framework setup
- `/clerk-cli` - CLI operations and Clerk resource management
- `/clerk-custom-ui` - Custom flows & appearance
- `/clerk-nextjs-patterns` - Next.js patterns
- `/clerk-react-patterns` - React patterns
- `/clerk-react-router-patterns` - React Router patterns
- `/clerk-vue-patterns` - Vue patterns
- `/clerk-nuxt-patterns` - Nuxt patterns
- `/clerk-astro-patterns` - Astro patterns
- `/clerk-tanstack-patterns` - TanStack Start patterns
- `/clerk-expo` - Expo / React Native
- `/clerk-chrome-extension-patterns` - Chrome Extension patterns
- `/clerk-orgs` - Organizations
- `/clerk-billing` - Billing & subscriptions
- `/clerk-webhooks` - Webhooks
- `/clerk-testing` - Testing
- `/clerk-swift` - Swift/native iOS
- `/clerk-android` - Native Android
- `/clerk-backend-api` - Backend REST API

Or describe what you need and I'll recommend the right one.

<!-- chapter:end slug=clerk -->

---

<!-- chapter:begin slug=clerk-billing position=8 -->

## 8. clerk-billing

- **Source:** https://github.com/clerk/skills/blob/main/skills/features/clerk-billing/SKILL.md
- **Raw:** https://raw.githubusercontent.com/clerk/skills/main/skills/features/clerk-billing/SKILL.md
- **Markdown:** https://skillsdocs.com/clerk/skills/clerk-billing.md
- **Licence:** MIT — https://spdx.org/licenses/MIT.html

Bundled files (5), referenced from this skill's directory:
  - `evals/evals.json` — https://raw.githubusercontent.com/clerk/skills/main/skills/features/clerk-billing/evals/evals.json
  - `references/b2b-patterns.md` — https://raw.githubusercontent.com/clerk/skills/main/skills/features/clerk-billing/references/b2b-patterns.md
  - `references/b2c-patterns.md` — https://raw.githubusercontent.com/clerk/skills/main/skills/features/clerk-billing/references/b2c-patterns.md
  - `references/billing-components.md` — https://raw.githubusercontent.com/clerk/skills/main/skills/features/clerk-billing/references/billing-components.md
  - `references/billing-webhooks.md` — https://raw.githubusercontent.com/clerk/skills/main/skills/features/clerk-billing/references/billing-webhooks.md

<!-- Verbatim upstream SKILL.md follows, YAML frontmatter included. -->

---
name: clerk-billing
description: Clerk Billing for subscription management - render Clerk's PricingTable
  and in-app checkout drawer, configure subscription plans, seat-limit plans for
  B2B, feature entitlements with has(), and billing webhooks. Use for SaaS
  monetization, plan gating, checkout flows, trials, invoicing, and subscription
  lifecycle management.
allowed-tools: WebFetch
license: MIT
compatibility: Requires NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY, CLERK_SECRET_KEY, and CLERK_WEBHOOK_SIGNING_SECRET. Billing must be enabled in Clerk Dashboard → Billing. Development instances can use the shared Clerk development gateway; production instances require a Stripe account for payment processing.
metadata:
  author: clerk
  version: 1.0.0
---

# Billing

> **STOP, prerequisite.** Billing must be enabled before any `<PricingTable />`, `<CheckoutButton />`, `has({ plan })`, or `has({ feature })` usage works. Two paths: (1) [Dashboard → Billing → Settings](https://dashboard.clerk.com/last-active?path=billing/settings), or (2) `clerk enable billing` (see "Agent-first: Programmatic billing config" below). Enabling auto-creates default `free_user` / `free_org` plans. Dev instances can use the shared Clerk development gateway (no Stripe account needed); production requires a Stripe account for payment processing only.
>
> **Note**: Billing APIs are still experimental. Pin your `@clerk/nextjs` and `clerk-js` package versions. See `clerk` skill for the supported version table.

## Quick Start

1. **Enable Billing**, via [Dashboard → Billing → Settings](https://dashboard.clerk.com/last-active?path=billing/settings) or `clerk enable billing` (see Agent-first section). Skipping this throws `cannot_render_billing_disabled` in dev and renders empty in prod.
2. **Create plans in the matching tab**, [Dashboard → Billing → Plans](https://dashboard.clerk.com/last-active?path=billing/plans). Two tabs, slugs scoped per tab, not movable after creation:
   - **User Plans** → `<PricingTable />` (default `for="user"`)
   - **Organization Plans** → `<PricingTable for="organization" />`

   Wrong-tab is the #1 cause of an empty `<PricingTable />`. Plans live in Clerk; not synced to Stripe.
3. **Add features inside a plan**, open the plan in Dashboard → Billing → Plans, use its Features section. Features are scoped per plan, not global. The same slug can attach to multiple plans; `has({ feature: 'export' })` matches if the active plan contains that slug.
4. **Render `<PricingTable />`** (pass `for="organization"` for B2B).
5. **Gate access** with `has({ plan })` or `has({ feature })` from `auth()`.
6. **Handle billing webhooks** for subscription lifecycle.

## Dashboard shortcuts

| Action | URL |
|---|---|
| Enable Billing | `https://dashboard.clerk.com/last-active?path=billing/settings` |
| Create / edit plans | `https://dashboard.clerk.com/last-active?path=billing/plans` |
| Membership mode (B2C + B2B coexistence) | `https://dashboard.clerk.com/last-active?path=organizations-settings` |
| Edit features | Plans → click a plan → Features section (no direct URL) |

## Agent-first: Programmatic billing config

The full billing config (enable toggles, plans, features, plan-feature attachments) is editable via PLAPI without touching the Dashboard. Useful for agents seeding plans, replicating config across instances, or version-controlling billing structure.

Pre-req: project linked to the Clerk app (`clerk auth login` + `clerk link`, see `clerk-setup`).

### Enable Billing via CLI

```bash
clerk enable billing                # both targets (default, auto-creates free_user + free_org plans)
clerk enable billing --for org      # org only
clerk enable billing --for user     # user only
```

### Pull current billing config

```bash
clerk config pull --keys billing > billing.json
```

This writes the current billing config (toggles + plans + features) for the linked instance to `billing.json`.

### Edit and apply

Edit `billing.json` to add/remove plans or features, then preview the diff and apply:

```bash
clerk config patch --file billing.json --dry-run
clerk config patch --file billing.json
```

Pass `--instance prod` to target the production instance instead of dev.

### Raw PATCH (full control)

For one-shot plan/feature updates without a config file:

```bash
clerk api --platform PATCH /v1/platform/applications/<app_id>/instances/<ins_id>/config \
  -d '{"billing":{"plans":[{"slug":"pro","name":"Pro","amount":2000,"currency":"usd","payer_type":"user","is_recurring":true}],"features":[{"slug":"export","name":"Export"}]}}'
```

### Notes

- This handles **billing config** (toggles + plans + features catalog). **Subscription lifecycle** (users picking a plan, checkout, renewal, cancellation) still flows through `<PricingTable />` + billing webhooks, see `clerk-webhooks` skill for the lifecycle events.
- Top-level `features` map manipulation and plan-feature attachments (sync) are fully supported via the PLAPI billing config handler.

## What Do You Need?

| Task | Reference |
|------|-----------|
| `<PricingTable />` props, `<CheckoutButton />`, `<Show>` billing patterns | references/billing-components.md |
| B2C patterns (individual user subscriptions, `Membership optional` prerequisite) | references/b2c-patterns.md |
| B2B patterns (org subscriptions, seat-limit plans, admin-gated billing UI) | references/b2b-patterns.md |
| Webhook event catalog, payload shapes, handler templates | references/billing-webhooks.md |

## References

| Reference | Description |
|-----------|-------------|
| `references/billing-components.md` | `<PricingTable />` and subscription UI |
| `references/b2c-patterns.md` | B2C subscription billing patterns |
| `references/b2b-patterns.md` | B2B billing with organization subscriptions and seat-limit plans |
| `references/billing-webhooks.md` | Subscription lifecycle event handling |

## Documentation

- [Billing overview](https://clerk.com/docs/guides/billing/overview)
- [B2B SaaS billing](https://clerk.com/docs/guides/billing/for-b2b)
- [B2C SaaS billing](https://clerk.com/docs/guides/billing/for-b2c)
- [Billing webhooks](https://clerk.com/docs/guides/development/webhooks/billing)

## Features vs Plans: When to Use Which

**Use `has({ feature: 'slug' })` when gating a specific capability**, export, analytics, API access, audit logs.

**Use `has({ plan: 'slug' })` when gating a tier**, showing the pro dashboard, checking org subscription level, redirecting free users.

| Scenario | Correct check |
|----------|---------------|
| Gate the "Export CSV" button | `has({ feature: 'export' })` |
| Gate the "Analytics" section | `has({ feature: 'analytics' })` |
| Gate all of /dashboard/pro | `has({ plan: 'pro' })` |
| Check if org has team subscription | `has({ plan: 'org:team' })` |
| Gate SSO configuration | `has({ feature: 'sso' })` |

When a user says "gate the export feature" or "gate analytics", always use `has({ feature })`. Only use `has({ plan })` when the gate is the plan tier itself, not a specific capability within it.

## Key Patterns

### 1. Render the Pricing Table

Show available plans to users with a single component:

```tsx
import { PricingTable } from '@clerk/nextjs'

export default function PricingPage() {
	return (
		<main>
			<h1>Choose a plan</h1>
			<PricingTable />
		</main>
	)
}
```

`<PricingTable />` automatically renders all plans configured in the Clerk Dashboard. Selecting a plan opens Clerk's in-app checkout drawer. No props needed for basic usage. For B2B, pass `for="organization"` to render org-level plans instead of user plans.

### 2. Check Feature Entitlements (Server-Side)

Gate by individual features, this is the preferred approach for specific capabilities:

```typescript
import { auth } from '@clerk/nextjs/server'

export default async function AnalyticsPage() {
	const { has } = await auth()

	const canViewAnalytics = has({ feature: 'analytics' })
	const canExport = has({ feature: 'export' })

	return (
		<div>
			{canViewAnalytics && <AnalyticsChart />}
			{canExport && <ExportButton />}
		</div>
	)
}
```

Features are configured in Clerk Dashboard → Billing → Features and assigned to plans. Use `has({ feature })` instead of `has({ plan })` when gating granular capabilities, check the feature, not the plan.

### 3. Check Feature Entitlements (Client-Side)

Use `useAuth()` for client-side feature gating. Combine with server-side checks for full coverage:

```tsx
'use client'
import { useAuth } from '@clerk/nextjs'

export function FeatureGatedUI() {
	const { has, isLoaded } = useAuth()
	if (!isLoaded) return null

	const canExport = has?.({ feature: 'export' })
	const canAnalytics = has?.({ feature: 'analytics' })

	return (
		<div>
			{canAnalytics && <AnalyticsSection />}
			{canExport ? <ExportButton /> : <UpgradeToExport />}
		</div>
	)
}
```

Server Components use `auth()`, Client Components use `useAuth()`. Both support `has({ feature })` and `has({ plan })`.

### 4. Check Subscription Plan Server-Side

Gate access by subscription plan (use this for tier-level gates, not individual features):

```typescript
import { auth } from '@clerk/nextjs/server'
import { redirect } from 'next/navigation'

export default async function ProDashboard() {
	const { has } = await auth()

	if (!has({ plan: 'pro' })) {
		redirect('/pricing')
	}

	return <ProFeatures />
}
```

### 5. Client-Side Plan Checks

Use `useAuth()` hook for client components:

```tsx
'use client'
import { useAuth } from '@clerk/nextjs'

export function UpgradePrompt() {
	const { has } = useAuth()

	if (has?.({ plan: 'pro' })) {
		return null
	}

	return (
		<div>
			<p>Upgrade to Pro to access this feature</p>
			<a href="/pricing">View plans</a>
		</div>
	)
}
```

### 6. B2B Seat-Based Billing with Organizations

Org plans can carry a **seat limit** (membership cap) that Clerk enforces at invite time. Use the `org:` slug prefix on org-side plan checks (e.g. `has({ plan: 'org:team' })`) to keep gating unambiguous. Render the B2B pricing page with `<PricingTable for="organization" />`, and use `<OrganizationProfile />` for the org account billing UI.

See `references/b2b-patterns.md` for tiered plan naming, seat-limit invariants, admin-only billing, and webhook handlers.

### 7. Display Subscription Status

Check specific plans with `has({ plan })`, or use `useSubscription()` for full subscription details in client components. Do not read plan information from `sessionClaims` directly, that is not the supported path.

Server component, check for specific plans:

```typescript
import { auth } from '@clerk/nextjs/server'

export default async function AccountPage() {
	const { has } = await auth()

	const currentPlan = has({ plan: 'pro' })
		? 'pro'
		: has({ plan: 'starter' })
			? 'starter'
			: 'free'

	return (
		<div>
			<h2>Current Plan</h2>
			<p>You are on the {currentPlan} plan</p>
			{currentPlan === 'free' && <a href="/pricing">Upgrade</a>}
		</div>
	)
}
```

Client component, full subscription details via `useSubscription()`:

```tsx
'use client'
import { useSubscription } from '@clerk/nextjs/experimental'

export function SubscriptionDetails() {
	const { data: subscription, isLoading } = useSubscription()
	if (isLoading) return null
	if (!subscription) return <a href="/pricing">Choose a plan</a>

	return (
		<div>
			<p>Status: {subscription.status}</p>
			{subscription.nextPayment && (
				<p>Next payment: {subscription.nextPayment.date.toLocaleDateString()}</p>
			)}
		</div>
	)
}
```

> `useSubscription()` is for display only. For authorization checks (gating content or routes), always use `has({ plan })` or `has({ feature })`.

### 8. Protect API Routes by Plan

Gate API routes using `auth()`:

```typescript
import { auth } from '@clerk/nextjs/server'
import { NextResponse } from 'next/server'

export async function GET() {
	const { has } = await auth()

	if (!has({ plan: 'pro' })) {
		return NextResponse.json({ error: 'Pro plan required' }, { status: 403 })
	}

	return NextResponse.json({ data: 'premium data' })
}
```

### 9. Handle Billing Webhooks

> **Clerk event names differ from Stripe event names.** Clerk billing webhooks use dot-notation and camelCase, not Stripe's underscore format.
>
> There is no `subscription.canceled` event. Cancellation fires at the item level as `subscriptionItem.canceled`.
>
> | Intent | Stripe event name | Clerk event name |
> |--------|------------------|-----------------|
> | Subscription created | `customer.subscription.created` | `subscription.created` |
> | Subscription updated | `customer.subscription.updated` | `subscription.updated` |
> | Subscription active | (none) | `subscription.active` |
> | Subscription past due | (none) | `subscription.pastDue` |
> | Subscription item canceled | `customer.subscription.deleted` | `subscriptionItem.canceled` |
> | Subscription item past due | `invoice.payment_failed` | `subscriptionItem.pastDue` |
> | Subscription item updated | (none) | `subscriptionItem.updated` |
> | Subscription item active | (none) | `subscriptionItem.active` |
> | Subscription item upcoming renewal | (none) | `subscriptionItem.upcoming` |
> | Subscription item ended | (none) | `subscriptionItem.ended` |
> | Subscription item abandoned | (none) | `subscriptionItem.abandoned` |
> | Subscription item expired | (none) | `subscriptionItem.expired` |
> | Subscription item incomplete | (none) | `subscriptionItem.incomplete` |
> | Free trial ending soon | (none) | `subscriptionItem.freeTrialEnding` |
> | Payment attempt created | (none) | `paymentAttempt.created` |
> | Payment attempt updated | (none) | `paymentAttempt.updated` |
>
> Always use Clerk's event names, never Stripe's, in `evt.type` checks.

> **Payload shape.** Clerk billing webhook payloads are nested. The subscribing entity lives under `evt.data.payer` (fields: `user_id?`, `organization_id?`). The plan info is on each item under `evt.data.items[i].plan.slug`. The subscription id is simply `evt.data.id`. Subscription items do not carry a `subscription_id` field back-reference, so in `subscriptionItem.*` handlers you identify the record by the item id (`evt.data.id`) or look up by payer plus plan.

Minimal handler to anchor the pattern (import from `@clerk/nextjs/webhooks`, verify, branch on Clerk event name):

```typescript
import { verifyWebhook } from '@clerk/nextjs/webhooks'
import { NextRequest } from 'next/server'
import { db } from '@/lib/db'

export async function POST(req: NextRequest) {
	let evt
	try {
		evt = await verifyWebhook(req)
	} catch {
		return new Response('Verification failed', { status: 400 })
	}

	if (evt.type === 'subscription.created') {
		const { id, payer, items, status } = evt.data
		const entityId = payer.organization_id ?? payer.user_id
		const plan = items[0]?.plan?.slug
		await db.subscriptions.upsert({
			where: { subscriptionId: id },
			create: { subscriptionId: id, entityId, plan, status },
			update: { entityId, plan, status },
		})
	}

	// Add more branches per the event catalog above (subscription.updated,
	// subscriptionItem.canceled, subscriptionItem.pastDue, etc.)

	return new Response('OK', { status: 200 })
}
```

For the full template covering all 15 events, the TS type declarations from `@clerk/backend`, the `proxy.ts` public-route setup, and the subscription status value table, see `references/billing-webhooks.md`.

### 10. Upgrade / Downgrade Flow

Let users manage their subscription from inside the app:

```tsx
import { PricingTable } from '@clerk/nextjs'
import { auth } from '@clerk/nextjs/server'

export default async function BillingPage() {
	const { has } = await auth()
	const isPro = has({ plan: 'pro' })

	return (
		<div>
			<h1>Billing</h1>
			{isPro ? (
				<div>
					<p>You are on the Pro plan</p>
					<PricingTable />
				</div>
			) : (
				<div>
					<p>Upgrade to access premium features</p>
					<PricingTable />
				</div>
			)}
		</div>
	)
}
```

`<PricingTable />` renders differently for subscribed users, it shows the current plan and allows upgrades or cancellations, all through Clerk's in-app checkout drawer.

## Plan and Feature Naming

Plan slugs and feature slugs are defined in Clerk Dashboard → Billing. Common conventions:

| Tier | Plan Slug | Example Features |
|------|-----------|-----------------|
| Free | (no plan check needed) | basic features |
| Starter | `starter` | `analytics`, `api_access` |
| Pro | `pro` | `analytics`, `export`, `team` |
| Enterprise | `enterprise` | all features + `sso`, `audit_logs` |

Use lowercase slugs matching what you define in the dashboard.

## B2B vs B2C Billing

| Scenario | Who subscribes | Plan check |
|----------|---------------|------------|
| B2C SaaS | Individual user | `has({ plan: 'pro' })` on user session |
| B2B SaaS | Organization | `has({ plan: 'org:team' })` on org session |
| Seat-limited B2B | Organization | Plan has a seat cap; pricing is per-plan, not per-member, tier your plans for bigger orgs |

For B2B, ensure the user has an active org session. The `has()` check evaluates the active entity (user or org).

## Checkout Flows

Clerk renders its own checkout drawer automatically through `<PricingTable />` and `<CheckoutButton />`. Plans and pricing live in Clerk. To trigger checkout from a server action, redirect to a page that renders `<PricingTable />`:

```typescript
'use server'
import { redirect } from 'next/navigation'

export async function upgradeAction() {
	redirect('/pricing')
}
```

## Error Signatures (diagnose fast)

When you see any of these errors or symptoms, the fix is almost always a Dashboard toggle, not a code change. Do not start editing components.

| Error / symptom | Root cause | Fix |
|---|---|---|
| `Clerk: 🔒 The <PricingTable/> component cannot be rendered when billing is disabled.` (code: `cannot_render_billing_disabled`, dev only) | Billing is not enabled for this instance | Enable Billing at [dashboard.clerk.com → Billing → Settings](https://dashboard.clerk.com/last-active?path=billing/settings), or run `clerk enable billing`. |
| `<PricingTable />` renders empty | No plans, OR plan in the wrong tab (User vs Organization), OR Billing not enabled | Create plan in matching tab; pass `for="organization"` for B2B; check Billing Settings |
| Users can't subscribe to a personal plan on a B2C + B2B app | Membership required mode (default since 2025-08-22) disables personal accounts, signed-in users are forced into `choose-organization` and never land on a personal-subscription state | If you need personal + org subscriptions coexisting: Dashboard → Organizations settings → *Membership optional* |
| Can't find a Features page | Features are per-plan, not global | Dashboard → Billing → Plans → click plan → Features |
| `has({ plan: 'pro' })` always returns `false` after a successful checkout | Session token hasn't been refreshed to include the new plan | `await clerk.session?.reload()` or navigate to force a new session |
| `has({ plan: 'pro' })` returns `false` before any subscribe attempt | Plan slug mismatch (case-sensitive), OR Billing not enabled, OR payment gateway not connected in production | Verify slug in Dashboard → Billing → Plans; confirm Billing → Settings shows enabled + connected gateway |
| `has({ permission: 'org:x:y' })` returns `false` for a user who has the role | The Feature tied to that permission is not included in the organization's active Plan | Add the Feature to the Plan in Dashboard → Billing → Plans → Features |
| Webhook 401 / signature verification failed | `CLERK_WEBHOOK_SIGNING_SECRET` mismatch or route protected by middleware | Copy the Signing Secret from Dashboard → Webhooks; add the webhook route to `createRouteMatcher(['/api/webhooks(.*)'])` |

## Billing Gates Permissions

When Billing is enabled, `has({ permission: 'org:posts:edit' })` returns `false` if the Feature associated with that permission is not included in the organization's active Plan, even if the user has the permission assigned via their role. This is by design: billing gates permissions at the feature level. Always ensure the required Feature is attached to the Plan in Dashboard → Billing → Plans → Features.

## See Also

- `clerk-setup` - Initial Clerk install
- `clerk-orgs` - B2B organizations (required for B2B billing and seat-limit plans)
- `clerk-webhooks` - Webhook signature verification and routing

<!-- chapter:end slug=clerk-billing -->

---

<!-- chapter:begin slug=clerk-orgs position=9 -->

## 9. clerk-orgs

- **Source:** https://github.com/clerk/skills/blob/main/skills/features/clerk-orgs/SKILL.md
- **Raw:** https://raw.githubusercontent.com/clerk/skills/main/skills/features/clerk-orgs/SKILL.md
- **Markdown:** https://skillsdocs.com/clerk/skills/clerk-orgs.md
- **Licence:** MIT — https://spdx.org/licenses/MIT.html

Bundled files (5), referenced from this skill's directory:
  - `evals/evals.json` — https://raw.githubusercontent.com/clerk/skills/main/skills/features/clerk-orgs/evals/evals.json
  - `references/enterprise-sso.md` — https://raw.githubusercontent.com/clerk/skills/main/skills/features/clerk-orgs/references/enterprise-sso.md
  - `references/invitations.md` — https://raw.githubusercontent.com/clerk/skills/main/skills/features/clerk-orgs/references/invitations.md
  - `references/nextjs-patterns.md` — https://raw.githubusercontent.com/clerk/skills/main/skills/features/clerk-orgs/references/nextjs-patterns.md
  - `references/roles-permissions.md` — https://raw.githubusercontent.com/clerk/skills/main/skills/features/clerk-orgs/references/roles-permissions.md

<!-- Verbatim upstream SKILL.md follows, YAML frontmatter included. -->

---
name: clerk-orgs
description: Clerk Organizations for B2B SaaS - create multi-tenant apps with org
  switching, role-based access, verified domains, and enterprise SSO. Use for team
  workspaces, RBAC, org-based routing, member management.
allowed-tools: WebFetch
license: MIT
compatibility: Requires NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY and CLERK_SECRET_KEY. Organizations must be enabled in Clerk Dashboard → Organizations. Membership mode (required vs optional) must match the B2B vs B2C + B2B coexistence story of your app.
metadata:
  author: clerk
  version: 3.0.0
---

# Organizations (B2B SaaS)

> **STOP — prerequisite.** Organizations must be enabled before any org-related API, hook, or component works. Two paths: (1) [Dashboard → Organizations settings](https://dashboard.clerk.com/last-active?path=organizations-settings), or (2) `clerk enable orgs` (see "Agent-first: Programmatic org management" below). Pick the Membership mode deliberately: `Membership required` (default since 2025-08-22) routes signed-in users through the `choose-organization` task and disables personal accounts, while `Membership optional` keeps personal accounts available for B2C + B2B coexistence. Pick `optional` if you need personal subscriptions alongside org subscriptions.
>
> **Version**: This skill targets current SDKs (`@clerk/nextjs` v7+, `@clerk/react` v6+ — Core 3). Core 2 differences are noted inline with `> **Core 2 ONLY (skip if current SDK):**` callouts — see `clerk` skill for the full version table.

## Quick Start

1. **Enable Organizations** — via [Dashboard → Organizations settings](https://dashboard.clerk.com/last-active?path=organizations-settings) or `clerk enable orgs` (see Agent-first section). Pick `Membership required` (B2B-only) or `Membership optional` (B2C + B2B).
2. **Create an org** — via `<OrganizationSwitcher />`, `<CreateOrganization />`, or programmatically with `clerkClient().organizations.createOrganization()`.
3. **Protect routes** — read `orgId` / `orgSlug` from `auth()` and gate with `has({ role })` or `has({ permission })`.
4. **Manage members** — send invitations via Backend API or the built-in `<OrganizationProfile />` tab.
5. **Cap membership** — set `maxAllowedMemberships` at org creation or pick a seat-limited Billing Plan (see `clerk-billing` skill).

## What Do You Need?

| Task | Reference |
|------|-----------|
| System permissions catalog, custom roles, role sets | references/roles-permissions.md |
| Invitation lifecycle (create, list, revoke, built-in UI) | references/invitations.md |
| Enterprise SSO setup, provider field access, domain verification | references/enterprise-sso.md |
| Next.js adaptations for orgs (role/permission middleware, slug invariants, orgId-scoped writes) | references/nextjs-patterns.md |

## References

| Reference | Description |
|-----------|-------------|
| `references/roles-permissions.md` | Default + custom roles, System Permissions catalog, permission naming |
| `references/invitations.md` | Backend API for invitations + built-in UI |
| `references/enterprise-sso.md` | SAML/OIDC per-org, domain verification, correct field access |
| `references/nextjs-patterns.md` | Next.js adaptations specific to orgs. For generic Next.js patterns see `clerk-nextjs-patterns` skill. |

## Dashboard shortcuts

| Action | URL |
|---|---|
| Enable Organizations + Membership mode | `https://dashboard.clerk.com/last-active?path=organizations-settings` |
| Manage roles + permissions | `https://dashboard.clerk.com/last-active?path=organizations-settings/roles` |
| Create/edit an organization | `https://dashboard.clerk.com/last-active?path=organizations` |
| Webhooks for org events | `https://dashboard.clerk.com/last-active?path=webhooks` |

## Agent-first: Programmatic org management

Org settings (enable toggle, membership cap, admin delete, domains) are patchable via PLAPI Instance Config. Org CRUD + memberships + invitations live in BAPI. Useful for agents seeding orgs, replicating settings across instances, or version-controlling org structure.

Pre-req: project linked (`clerk auth login` + `clerk link`, see `clerk-setup`).

### Enable Organizations + settings via CLI

```bash
clerk enable orgs
```

For additional settings (membership cap, verified domains, admin delete), patch the instance config:

```bash
clerk api --platform PATCH /v1/platform/applications/<app_id>/instances/<ins_id>/config \
  -d '{"organization_settings":{"max_allowed_memberships":50,"domains_enabled":true,"admin_delete_enabled":true}}'
```

### Create / list / delete orgs (BAPI)

```bash
# Create:
clerk api -X POST /v1/organizations \
  -d '{"name":"Acme","slug":"acme","created_by":"user_xxx","max_allowed_memberships":10}'

# List:
clerk api /v1/organizations --query 'limit=20'

# Get one:
clerk api /v1/organizations/<org_id>

# Update:
clerk api -X PATCH /v1/organizations/<org_id> -d '{"name":"Acme Inc."}'

# Delete:
clerk api -X DELETE /v1/organizations/<org_id>
```

### Memberships

```bash
# Add a user to an org:
clerk api -X POST /v1/organizations/<org_id>/memberships \
  -d '{"user_id":"user_xxx","role":"org:admin"}'

# List members:
clerk api /v1/organizations/<org_id>/memberships --query 'limit=50'

# Update role:
clerk api -X PATCH /v1/organizations/<org_id>/memberships/<user_id> \
  -d '{"role":"org:member"}'

# Remove:
clerk api -X DELETE /v1/organizations/<org_id>/memberships/<user_id>
```

### Invitations

```bash
# Send:
clerk api -X POST /v1/organizations/<org_id>/invitations \
  -d '{"email_address":"alice@example.com","role":"org:member","redirect_url":"https://app.com/accept"}'

# List pending:
clerk api /v1/organizations/<org_id>/invitations --query 'status=pending'

# Revoke:
clerk api -X POST /v1/organizations/<org_id>/invitations/<inv_id>/revoke \
  -d '{"requesting_user_id":"user_xxx"}'
```

### Notes

- This handles **org config + CRUD**. Subscription / billing for orgs (org plans, seat-limit pricing) flows through `clerk-billing` skill.
- Roles + permissions catalog is editable in `references/roles-permissions.md`. Custom role creation goes through `clerk config patch` (instance-level role definitions) — see Dashboard's role editor for the UX equivalent.
- For SSO / verified domain provisioning, see `references/enterprise-sso.md`.

## Documentation

- [Overview](https://clerk.com/docs/guides/organizations/overview)
- [Configure + enable](https://clerk.com/docs/guides/organizations/configure)
- [Roles and permissions](https://clerk.com/docs/guides/organizations/control-access/roles-and-permissions)
- [Check access](https://clerk.com/docs/guides/organizations/control-access/check-access)
- [Invitations](https://clerk.com/docs/guides/organizations/add-members/invitations)
- [OrganizationSwitcher](https://clerk.com/docs/reference/components/organization/organization-switcher)
- [Verified domains](https://clerk.com/docs/guides/organizations/add-members/verified-domains)
- [Enterprise SSO](https://clerk.com/docs/guides/organizations/add-members/sso)

## Key Patterns

Examples use `@clerk/nextjs` by default. For other frameworks swap the import to `@clerk/react` (Vite/CRA), `@clerk/astro/components`, `@clerk/vue`, `@clerk/expo`, `@clerk/react-router`, or `@clerk/tanstack-react-start` — the feature-level APIs (`has()`, `orgId`, `<OrganizationSwitcher />`, `<Show>`) are identical across SDKs. Framework-specific patterns (middleware, redirects) live in `references/nextjs-patterns.md`.

### 1. Read Organization from Auth

Server-side access to active organization:

```typescript
import { auth } from '@clerk/nextjs/server'

const { orgId, orgSlug, orgRole } = await auth()
if (!orgId) {
  // user has no active org — either not in any, or viewing Personal Account
}
```

`auth()` is Next.js-specific. Equivalent server-side accessors per SDK: `auth(event)` (Nuxt via `event.context.auth()`), `context.locals.auth()` (Astro), `getAuth(req)` (Express, after `clerkMiddleware()`). Client-side: `useAuth()` (React-based SDKs) or composables (Vue/Nuxt). All return the same `orgId` / `orgSlug` / `orgRole` shape.

### 2. Dynamic Routes with Org Slug

Route-per-org pattern works in any framework supporting file-based dynamic routes. Next.js example:

```
app/orgs/[slug]/page.tsx
app/orgs/[slug]/settings/page.tsx
```

Always verify the URL slug matches the active org slug — otherwise users can hit `/orgs/other-org/...` with a stale `orgSlug` in their session:

```typescript
export default async function OrgPage({ params }: { params: { slug: string } }) {
  const { orgSlug } = await auth()
  if (orgSlug !== params.slug) {
    redirect('/dashboard')  // or whatever your "no-access" flow is
  }
  return <div>Welcome to {orgSlug}</div>
}
```

### 3. Role-Based Access Control

```typescript
const { has } = await auth()

if (!has({ role: 'org:admin' })) {
  return <div>Admin access required</div>
}
```

Permission checks use the same `has()` surface:

```typescript
if (!has({ permission: 'org:sys_memberships:manage' })) {
  redirect('/unauthorized')
}
```

**Permission naming convention.** System Permissions prefix with `org:sys_`; custom Permissions use `org:<resource>:<action>`. The full System Permissions catalog lives in `references/roles-permissions.md` — the short list is:

- `org:sys_memberships:{read, manage}`
- `org:sys_profile:{manage, delete}`
- `org:sys_domains:{read, manage}`
- `org:sys_billing:{read, manage}`

Do NOT invent names like `org:create`, `org:manage_members`, `org:update_metadata` — those are not real permission slugs. See `references/roles-permissions.md` for custom roles and the permission table.

### 4. Conditional Rendering with `<Show>`

```tsx
import { Show } from '@clerk/nextjs'

<Show when={{ role: 'org:admin' }}>
  <AdminPanel />
</Show>

<Show when={{ permission: 'org:sys_memberships:manage' }}>
  <MembersTab />
</Show>
```

> **Core 2 ONLY (skip if current SDK):** Use `<Protect role="org:admin">` / `<Protect permission="...">` instead of `<Show>`. `<Show>` replaced both `<Protect>` and `<SignedIn>`/`<SignedOut>` in Core 3.

Astro template syntax for the same component (imported from `@clerk/astro/components`):

```astro
<Show when={{ role: 'org:admin' }}>
  <AdminPanel />
</Show>
```

### 5. OrganizationSwitcher

```tsx
import { OrganizationSwitcher } from '@clerk/nextjs'

<OrganizationSwitcher
  hidePersonal
  afterCreateOrganizationUrl="/orgs/:slug/dashboard"
  afterSelectOrganizationUrl="/orgs/:slug/dashboard"
/>
```

Key props:
- `hidePersonal: boolean` — hide the Personal Account option. Defaults to `false`. Pass `true` for B2B-only apps.
- `afterCreateOrganizationUrl`, `afterSelectOrganizationUrl`, `afterLeaveOrganizationUrl`, `afterSelectPersonalUrl` — navigation hooks. `:slug` is substituted at runtime.
- `createOrganizationMode`, `organizationProfileMode` — `'modal' | 'navigation'` (default `'modal'`).

The full prop list lives in the [component reference](https://clerk.com/docs/reference/components/organization/organization-switcher).

### 6. Session Task — Choose Organization

When `Membership required` is enabled (the default), users without an org are routed through a `choose-organization` session task after sign-in. Clerk handles this automatically inside `<SignIn />`, but you can host the UI yourself:

```tsx
import { ClerkProvider } from '@clerk/nextjs'

<ClerkProvider taskUrls={{ 'choose-organization': '/session-tasks/choose-organization' }}>
  {children}
</ClerkProvider>
```

```tsx
// app/session-tasks/choose-organization/page.tsx
import { TaskChooseOrganization } from '@clerk/nextjs'

export default function Page() {
  return <TaskChooseOrganization redirectUrlComplete="/dashboard" />
}
```

`TaskChooseOrganization` ships as an imported component in the React-based SDKs (`@clerk/nextjs`, `@clerk/react`, `@clerk/react-router`, `@clerk/tanstack-react-start`). For the JS Frontend SDK (`@clerk/clerk-js`) the equivalent is `clerk.mountTaskChooseOrganization(node)` / `clerk.unmountTaskChooseOrganization(node)`.

> **Core 2 ONLY (skip if current SDK):** Session tasks aren't available. Force an org selection at sign-in by redirecting to a page that renders `<OrganizationSwitcher hidePersonal />`.

## Default Roles + System Permissions

| Role | Default meaning |
|------|-------------|
| `org:admin` | Full access — all System Permissions, can manage org + memberships |
| `org:member` | Read members + Read billing Permissions only |

You can create up to 10 custom roles per instance in Dashboard → Organizations → Roles & Permissions. Role-per-org is controlled via **Role Sets** — see `references/roles-permissions.md` for the full model (custom roles, Creator/Default role settings, role sets, and the System Permissions catalog).

## Billing Checks

`has()` also supports plan and feature checks when Clerk Billing is enabled:

```typescript
const { has } = await auth()

has({ plan: 'gold' })        // subscription plan
has({ feature: 'widgets' })  // feature entitlement
```

> **Core 2 ONLY (skip if current SDK):** `has()` only supports `role` and `permission`. Billing checks aren't available.

See `clerk-billing` for the full Billing surface and seat-limit plan model.

## Enterprise SSO

Per-org SAML/OIDC. Configured in Dashboard → Configure → Enterprise Connections (or per-org: Organizations → select org → SSO Connections). The SSO connection owns its domain directly; no separate Verified Domain is required (and the two features are mutually exclusive on the same domain). Auto-join on first SSO sign-in uses JIT Provisioning, not Verified Domains. Key fact: the `provider` field lives on `enterpriseConnection`, not on `enterpriseAccounts[0]` directly. See `references/enterprise-sso.md` for the full flow and correct field access.

```typescript
// Strategy name for Enterprise SSO (Core 3)
strategy: 'enterprise_sso'
```

> **Core 2 ONLY (skip if current SDK):** Uses `strategy: 'saml'` and `user.samlAccounts` instead of `user.enterpriseAccounts`.

## Gotchas

### `maxAllowedMemberships` caps seats

```typescript
const clerk = await clerkClient()
await clerk.organizations.createOrganization({
  name: 'Acme Corp',
  createdBy: userId,
  maxAllowedMemberships: 10,
})

// Update later:
await clerk.organizations.updateOrganization(orgId, {
  maxAllowedMemberships: 25,
})
```

For tier-based seat limits tied to a subscription, use a seat-limited Billing Plan (see `clerk-billing`).

### Billing gates Permissions at the Feature level

When Clerk Billing is enabled, `has({ permission: 'org:posts:edit' })` returns `false` if the Feature associated with that permission is not included in the organization's active Plan — even if the user has the Permission assigned via their role. Ensure the Feature is attached to the active Plan in Dashboard → Billing → Plans → Features.

### Metadata updates REPLACE, not merge

`updateOrganization({ publicMetadata })` overwrites all public metadata. Read first, spread, then write:

```typescript
const org = await clerk.organizations.getOrganization({ organizationId: orgId })
await clerk.organizations.updateOrganization(orgId, {
  publicMetadata: { ...org.publicMetadata, newField: 'value' },
})
```

Applies identically to `privateMetadata` and to user metadata via `clerkClient.users.updateUser`.

## Error Signatures (diagnose fast)

Most "org-related" failures are configuration, not code. Do not edit components before checking these:

| Error / symptom | Root cause | Fix |
|---|---|---|
| `orgId` / `orgSlug` is `undefined` for a signed-in user | Organizations not enabled for this instance, OR user has no active org (personal account) | Enable in Dashboard → Organizations; check Membership mode; surface `<OrganizationSwitcher />` |
| `has({ permission: 'org:manage_members' })` always `false` | Using an invented permission slug | Use `org:sys_memberships:manage` (see roles-permissions.md catalog) |
| `has({ role })` returns `false` but user looks like an admin | Session token stale after role change | Re-sign-in, or refresh the session: `await clerk.session?.reload()` |
| `has({ permission })` `false` even with the role assigned | Feature not attached to active Plan (Billing gates permissions) | Dashboard → Billing → Plans → attach Feature |
| `<OrganizationSwitcher />` doesn't show "Personal Account" | `Membership required` mode is on (the default since Aug 22, 2025) | Dashboard → Organizations settings → `Membership optional` |
| `TaskChooseOrganization` throws "cannot render when a user doesn't have current session tasks" | Rendered outside a `choose-organization` task context | Wrap in a `choose-organization` session-task route only; don't render unconditionally |
| `enterpriseAccounts[0].provider` is `undefined` | Accessing `provider` at the wrong nesting level | Use `user.enterpriseAccounts[0].enterpriseConnection?.provider` |

## Authorization Pattern (Complete Example)

Server component protecting a slug-scoped admin page:

```typescript
import { auth } from '@clerk/nextjs/server'
import { redirect } from 'next/navigation'

export default async function AdminPage({ params }: { params: { slug: string } }) {
  const { orgSlug, has } = await auth()

  if (orgSlug !== params.slug) redirect('/dashboard')
  if (!has({ role: 'org:admin' })) redirect(`/orgs/${orgSlug}`)

  return <div>Admin settings for {orgSlug}</div>
}
```

For middleware-level protection (Next.js) see `references/nextjs-patterns.md`.

## Invitations (short form)

Send from a server action or route handler:

```typescript
import { clerkClient, auth } from '@clerk/nextjs/server'

export async function inviteMember(organizationId: string, emailAddress: string, role: string) {
  const { userId, has } = await auth()

  if (!userId) throw new Error('Not signed in')
  if (!has({ permission: 'org:sys_memberships:manage' })) {
    throw new Error('Not authorized to invite members')
  }

  const clerk = await clerkClient()
  return clerk.organizations.createOrganizationInvitation({
    organizationId,
    inviterUserId: userId,       // required per Backend API
    emailAddress,
    role,                        // e.g. 'org:admin' or 'org:member'
    redirectUrl: 'https://yourapp.com/accept-invite',
  })
}
```

The full lifecycle (list, revoke, bulk create, built-in `<OrganizationProfile />` UI) lives in `references/invitations.md`.

## Workflow

1. **Enable** — Organizations + Membership mode in Dashboard
2. **Create org** — via UI component or Backend API
3. **Invite members** — Backend API or built-in UI, with `inviterUserId`
4. **Gate access** — `has({ role })` / `has({ permission })` with canonical `org:sys_*` names
5. **Scope routes** — `orgSlug === params.slug` on every protected page
6. **Switch orgs** — `<OrganizationSwitcher />` handles the whole flow

## See Also

- `clerk-setup` — Initial Clerk install
- `clerk-billing` — Seat-limit plans, per-plan billing, `has({ plan })` / `has({ feature })`
- `clerk-webhooks` — Sync org events to your database (`organization.created`, `organizationMembership.*`)
- `clerk-backend-api` — Full Backend API reference
- `clerk-nextjs-patterns` — Framework-specific middleware, server actions, caching

<!-- chapter:end slug=clerk-orgs -->

---

<!-- chapter:begin slug=clerk-testing position=10 -->

## 10. clerk-testing

- **Source:** https://github.com/clerk/skills/blob/main/skills/features/clerk-testing/SKILL.md
- **Raw:** https://raw.githubusercontent.com/clerk/skills/main/skills/features/clerk-testing/SKILL.md
- **Markdown:** https://skillsdocs.com/clerk/skills/clerk-testing.md
- **Licence:** MIT — https://spdx.org/licenses/MIT.html

<!-- Verbatim upstream SKILL.md follows, YAML frontmatter included. -->

---
name: clerk-testing
description: E2E testing for Clerk apps. Use with Playwright or Cypress for auth flow
  tests.
allowed-tools: WebFetch
license: MIT
metadata:
  author: clerk
  version: 1.2.0
compatibility: Requires CLERK_TESTING_TOKEN from Clerk dashboard
---

# Testing

## Decision Tree

| Framework | Documentation |
|-----------|---------------|
| Overview | https://clerk.com/docs/guides/development/testing/overview |
| Playwright | https://clerk.com/docs/guides/development/testing/playwright/overview |
| Cypress | https://clerk.com/docs/guides/development/testing/cypress/overview |

## Mental Model

Test auth = isolated session state. Each test needs fresh auth context.
- `clerkSetup()` initializes test environment
- `setupClerkTestingToken()` bypasses bot detection
- `storageState` persists auth between tests for speed

## Workflow

1. Identify test framework (Playwright or Cypress)
2. WebFetch the appropriate URL from decision tree above
3. Follow official setup instructions
4. Use `pk_test_*` and `sk_test_*` keys only

## Best Practices

- Use `setupClerkTestingToken()` before navigating to auth pages
- Use test API keys: `pk_test_xxx`, `sk_test_xxx`
- Save auth state with `storageState` for faster tests
- Use `page.waitForSelector('[data-clerk-component]')` for Clerk UI

## Anti-Patterns

| Pattern | Problem | Fix |
|---------|---------|-----|
| Production keys in tests | Security risk | Use `pk_test_*` keys |
| No `setupClerkTestingToken()` | Auth fails | Call before navigation |
| UI-based sign-in every test | Slow tests | Use `storageState` |

## Framework-Specific

**Playwright**: Use `globalSetup` for auth state
**Cypress**: Add `addClerkCommands({ Cypress, cy })` to support file

## See Also

- `clerk-setup` - Install Clerk before adding tests
- `clerk-nextjs-patterns` - Next.js patterns being tested
- [Demo Repo](https://github.com/clerk/clerk-playwright-nextjs/tree/main/e2e)

<!-- chapter:end slug=clerk-testing -->

---

<!-- chapter:begin slug=clerk-webhooks position=11 -->

## 11. clerk-webhooks

- **Source:** https://github.com/clerk/skills/blob/main/skills/features/clerk-webhooks/SKILL.md
- **Raw:** https://raw.githubusercontent.com/clerk/skills/main/skills/features/clerk-webhooks/SKILL.md
- **Markdown:** https://skillsdocs.com/clerk/skills/clerk-webhooks.md
- **Licence:** MIT — https://spdx.org/licenses/MIT.html

Bundled files (2), referenced from this skill's directory:
  - `evals/evals.json` — https://raw.githubusercontent.com/clerk/skills/main/skills/features/clerk-webhooks/evals/evals.json
  - `references/frameworks.md` — https://raw.githubusercontent.com/clerk/skills/main/skills/features/clerk-webhooks/references/frameworks.md

<!-- Verbatim upstream SKILL.md follows, YAML frontmatter included. -->

---
name: clerk-webhooks
description: Clerk webhooks for real-time events and data syncing. Verify with verifyWebhook
  from the framework-specific package. Handle user, session, organization, billing, and
  payment events. Build event-driven features like database sync, notifications, and
  integrations.
allowed-tools: WebFetch
license: MIT
metadata:
  author: clerk
  version: 1.2.0
compatibility: Requires CLERK_WEBHOOK_SIGNING_SECRET (svix signing secret from Clerk dashboard)
---

# Webhooks

Output complete, working webhook handlers with `verifyWebhook(req)` verification in every handler.

## When to Use Webhooks

Webhooks are **asynchronous and eventually consistent**. Delivery is fast but not guaranteed to be immediate, and may occasionally fail (Svix retries on a fixed schedule). Use them for:

- Database sync (a separate users / orgs table that follows Clerk)
- Notifications (welcome emails, Slack pings, internal alerts)
- Integrations triggered by lifecycle events

Do NOT rely on webhook delivery as part of a synchronous flow such as onboarding ("user signs up, then we read X from our DB"). For data the user just created, read it from the [Clerk session token](https://clerk.com/docs/guides/sessions/session-tokens) or call the Backend API directly. Webhooks fill the gap when you need data about *other* users or events the session token doesn't carry.

## Verify Every Webhook

Use `verifyWebhook(req)` from the framework-specific package (`@clerk/nextjs/webhooks`, `@clerk/express/webhooks`, etc.). It reads `CLERK_WEBHOOK_SIGNING_SECRET` automatically and throws on bad signatures. Skipping verification, even for notification-only handlers, exposes the endpoint to spoofed events.

## Make the Webhook Route Public

Webhook routes must be excluded from Clerk middleware protection. Without this, Clerk returns 401.

```typescript
// proxy.ts (Next.js <=15: middleware.ts)
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'

const isPublicRoute = createRouteMatcher(['/api/webhooks(.*)'])

export default clerkMiddleware(async (auth, req) => {
  if (!isPublicRoute(req)) await auth.protect()
})
```

## Complete Webhook Handler (Next.js App Router)

```typescript
// app/api/webhooks/route.ts
import { verifyWebhook } from '@clerk/nextjs/webhooks'
import { NextRequest } from 'next/server'
import { db } from '@/lib/db'

export async function POST(req: NextRequest) {
  // ALWAYS verify - never skip, even for notification-only handlers
  let evt
  try {
    evt = await verifyWebhook(req) // uses CLERK_WEBHOOK_SIGNING_SECRET automatically
  } catch (err) {
    console.error('Webhook verification failed:', err)
    return new Response('Verification failed', { status: 400 })
  }

  if (evt.type === 'user.created') {
    const { id, email_addresses, first_name, last_name } = evt.data
    const email = email_addresses[0]?.email_address
    const name = `${first_name ?? ''} ${last_name ?? ''}`.trim()
    await db.users.create({ data: { clerkId: id, email, name } })
  }

  if (evt.type === 'user.updated') {
    const { id, email_addresses, first_name, last_name } = evt.data
    const email = email_addresses[0]?.email_address
    await db.users.update({ where: { clerkId: id }, data: { email, first_name, last_name } })
  }

  if (evt.type === 'user.deleted') {
    const { id } = evt.data
    await db.users.delete({ where: { clerkId: id } })
  }

  if (evt.type === 'organizationMembership.created') {
    const { organization, public_user_data, role } = evt.data
    const orgId = organization.id
    const userId = public_user_data.user_id
    await db.teamMembers.create({ data: { orgId, userId, role } })
  }

  if (evt.type === 'organizationMembership.deleted') {
    const { organization, public_user_data } = evt.data
    const orgId = organization.id
    const userId = public_user_data.user_id
    await db.teamMembers.delete({ where: { orgId_userId: { orgId, userId } } })
  }

  return new Response('OK', { status: 200 })
}
```

## Full Example: Welcome Email (Resend) + Slack Notification on user.created

Notification-only handlers still verify the signature. Same pattern as the database-sync handler:

```typescript
// app/api/webhooks/route.ts
import { verifyWebhook } from '@clerk/nextjs/webhooks'
import { NextRequest } from 'next/server'
import { Resend } from 'resend'

const resend = new Resend(process.env.RESEND_API_KEY)

export async function POST(req: NextRequest) {
  // Step 1: ALWAYS verify the webhook signature - NEVER skip this
  let evt
  try {
    evt = await verifyWebhook(req) // uses CLERK_WEBHOOK_SIGNING_SECRET env var
  } catch (err) {
    console.error('Webhook verification failed:', err)
    return new Response('Verification failed', { status: 400 })
  }

  // Step 2: Listen for user.created event
  if (evt.type === 'user.created') {
    // Step 3: Extract user email and name from webhook payload
    const { id, email_addresses, first_name, last_name } = evt.data
    const email = email_addresses[0]?.email_address
    const name = `${first_name ?? ''} ${last_name ?? ''}`.trim()

    // Step 4: Call Resend API to send welcome email
    await resend.emails.send({
      from: 'noreply@yourdomain.com',
      to: email,
      subject: 'Welcome!',
      html: `<p>Hi ${name}, welcome to our app!</p>`,
    })

    // Step 5: Post notification to Slack channel
    await fetch(process.env.SLACK_WEBHOOK_URL!, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        text: `New user signed up: ${name} (${email})`,
      }),
    })
  }

  // Always return 200 to acknowledge receipt
  return new Response('OK', { status: 200 })
}
```

**Also include proxy.ts (Next.js <=15: middleware.ts) to make the route public:**
```typescript
// proxy.ts (Next.js <=15: middleware.ts)
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'
const isPublicRoute = createRouteMatcher(['/api/webhooks(.*)'])
export default clerkMiddleware(async (auth, req) => {
  if (!isPublicRoute(req)) await auth.protect()
})
```

## Full Example: Organization Membership Sync to Database

```typescript
// app/api/webhooks/route.ts
import { verifyWebhook } from '@clerk/nextjs/webhooks'
import { NextRequest } from 'next/server'
import { db } from '@/lib/db' // your database client

export async function POST(req: NextRequest) {
  // ALWAYS verify signature - never skip, even for simple handlers
  let evt
  try {
    evt = await verifyWebhook(req) // uses CLERK_WEBHOOK_SIGNING_SECRET env var
  } catch (err) {
    console.error('Webhook verification failed:', err)
    return new Response('Verification failed', { status: 400 })
  }

  if (evt.type === 'organization.created') {
    const { id, name } = evt.data
    await db.workspaces.create({
      data: { orgId: id, name, createdAt: new Date() },
    })
  }

  if (evt.type === 'organizationMembership.created') {
    // Extract organization ID, user ID, and role from payload
    const { organization, public_user_data, role } = evt.data
    const orgId = organization.id
    const userId = public_user_data.user_id

    // Add to team_members table
    await db.team_members.create({
      data: { orgId, userId, role },
    })

    // Create workspace record for new member
    await db.workspaces.create({
      data: { orgId, userId, createdAt: new Date() },
    })
  }

  if (evt.type === 'organizationMembership.deleted') {
    // Extract organization ID and user ID from payload
    const { organization, public_user_data } = evt.data
    const orgId = organization.id
    const userId = public_user_data.user_id

    // Remove from team_members table
    await db.team_members.delete({
      where: { orgId, userId },
    })

    // Remove workspace record
    await db.workspaces.deleteMany({
      where: { orgId, userId },
    })
  }

  // Return 200 status on success
  return new Response('OK', { status: 200 })
}
```

## Other Frameworks

For Express, Astro, Fastify, Nuxt, React Router, and TanStack Start, use the framework-specific `verifyWebhook` adapter. Each Clerk SDK package ships its own (`@clerk/express/webhooks`, `@clerk/astro/webhooks`, `@clerk/fastify/webhooks`, etc.).

See `references/frameworks.md` for full handler examples per framework.

## Type Narrowing for `evt.data`

`verifyWebhook` returns `WebhookEvent`, a discriminated union of all event types. Narrow with `evt.type` to get type-safe access to `evt.data`:

```typescript
const evt = await verifyWebhook(req)

if (evt.type === 'user.created') {
  // evt.data is now UserJSON, autocompletes id, email_addresses, etc.
  console.log(evt.data.id)
}
```

For manual typing of nested payloads, import the JSON types from your framework's webhook subpath: `DeletedObjectJSON`, `EmailJSON`, `OrganizationInvitationJSON`, `OrganizationJSON`, `OrganizationMembershipJSON`, `SessionJSON`, `SMSMessageJSON`, `UserJSON`.

## Payload Field Reference

### User events (`user.created`, `user.updated`, `user.deleted`)
```typescript
const {
  id,                  // Clerk user ID
  email_addresses,     // array; [0].email_address is primary email
  first_name,
  last_name,
  image_url,
  public_metadata,
} = evt.data
```

### Organization events (`organization.created`, `organization.updated`, `organization.deleted`)
```typescript
const {
  id,    // org ID
  name,  // org name
  slug,
} = evt.data
```

### Organization Membership events (`organizationMembership.created`, `organizationMembership.updated`, `organizationMembership.deleted`)
```typescript
const {
  organization,        // { id, name, ... }
  public_user_data,    // { user_id, first_name, last_name, ... }
  role,                // e.g. 'org:admin', 'org:member'
} = evt.data
// Access: organization.id, public_user_data.user_id, role
```

## Supported Events (Full Catalog)

**User**: `user.created` `user.updated` `user.deleted`

**Session**: `session.created` `session.ended` `session.removed` `session.revoked`

**Organization**: `organization.created` `organization.updated` `organization.deleted`

**Organization Membership**: `organizationMembership.created` `organizationMembership.updated` `organizationMembership.deleted`

**Organization Domain**: `organizationDomain.created` `organizationDomain.updated` `organizationDomain.deleted`

**Organization Invitation**: `organizationInvitation.accepted` `organizationInvitation.created` `organizationInvitation.revoked`

**Communication**: `email.created` `sms.created`

**Waitlist**: `waitlistEntry.created` `waitlistEntry.updated`

**Permission**: `permission.created` `permission.updated` `permission.deleted`

**Role**: `role.created` `role.updated` `role.deleted`

**Subscription**: `subscription.created` `subscription.updated` `subscription.active` `subscription.pastDue`

**Subscription Item**: `subscriptionItem.created` `subscriptionItem.active` `subscriptionItem.updated` `subscriptionItem.canceled` `subscriptionItem.upcoming` `subscriptionItem.ended` `subscriptionItem.abandoned` `subscriptionItem.incomplete` `subscriptionItem.pastDue` `subscriptionItem.freeTrialEnding`

**Payment**: `paymentAttempt.created` `paymentAttempt.updated`

## Webhook Reliability

**Retries**: Svix retries failed webhooks on a set schedule (see [Svix Retry Schedule](https://docs.svix.com/retries)). Return 2xx to succeed, 4xx/5xx to retry. Use the `svix-id` header as an idempotency key to deduplicate retried events.

**Replay**: Failed webhooks can be replayed from Dashboard.

## Common Pitfalls

| Symptom | Cause | Fix |
|---------|-------|-----|
| Verification fails (Next.js) | Wrong import or usage | Use `@clerk/nextjs/webhooks`, pass `req` directly |
| Verification fails (Express) | Using `express.json()` | Use `express.raw({ type: 'application/json' })` for webhook route |
| Route not found (404) | Wrong path | Use `/api/webhooks` or preserve existing path |
| Not authorized (401) | Route is protected by middleware | Make route public in `clerkMiddleware()` |
| No data in DB | Async job pending | Wait/check logs |
| Duplicate entries | Only handling `user.created` | Also handle `user.updated` |
| Timeouts | Handler too slow | Queue async work, return 200 first |

## Testing & Deployment

**Local**: Use the Clerk CLI's first-party tunnel — no auth or linked project needed:

```sh
clerk webhooks listen --token "$(clerk webhooks token)" --forward-to http://localhost:3000/api/webhooks
```

Add the printed relay URL (`https://webhooks.clerk.com/in/c_.../`) as a webhook endpoint in the Dashboard — events don't flow until you do. `svix-*` headers are preserved, so `verifyWebhook()` works against that endpoint's signing secret as usual. Flags, offline signature checks (`clerk webhooks verify`), and agent-mode behavior are in the `clerk-cli` skill. Without the CLI, tunnel `localhost:3000` yourself (`ngrok`, `localtunnel`, `Cloudflare Tunnel`) and add the public URL to the Dashboard endpoint.

**Production**: Update webhook endpoint URL to production domain. Copy `CLERK_WEBHOOK_SIGNING_SECRET` to production env vars.

## References

| Reference | Description |
|-----------|-------------|
| `references/frameworks.md` | Webhook handler examples for Express, Astro, Fastify, Nuxt, React Router, TanStack Start |

## See Also

- `clerk-cli` - `clerk webhooks listen`/`verify` for local webhook testing
- `clerk-setup` - Initial Clerk install
- `clerk-orgs` - Org membership events
- `clerk-billing` - Subscription, subscription item, and payment attempt events
- `clerk-backend-api` - Sync via direct API calls

<!-- chapter:end slug=clerk-webhooks -->

---

<!-- chapter:begin slug=clerk-astro-patterns position=12 -->

## 12. clerk-astro-patterns

- **Source:** https://github.com/clerk/skills/blob/main/skills/frameworks/clerk-astro-patterns/SKILL.md
- **Raw:** https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-astro-patterns/SKILL.md
- **Markdown:** https://skillsdocs.com/clerk/skills/clerk-astro-patterns.md
- **Licence:** MIT — https://spdx.org/licenses/MIT.html

Bundled files (12), referenced from this skill's directory:
  - `evals/evals.json` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-astro-patterns/evals/evals.json
  - `references/api-routes.md` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-astro-patterns/references/api-routes.md
  - `references/astro-react.md` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-astro-patterns/references/astro-react.md
  - `references/island-components.md` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-astro-patterns/references/island-components.md
  - `references/middleware.md` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-astro-patterns/references/middleware.md
  - `references/ssr-pages.md` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-astro-patterns/references/ssr-pages.md
  - `templates/astro-basic-auth/astro.config.mjs` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-astro-patterns/templates/astro-basic-auth/astro.config.mjs
  - `templates/astro-basic-auth/package.json` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-astro-patterns/templates/astro-basic-auth/package.json
  - `templates/astro-basic-auth/src/layouts/Layout.astro` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-astro-patterns/templates/astro-basic-auth/src/layouts/Layout.astro
  - `templates/astro-basic-auth/src/middleware.ts` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-astro-patterns/templates/astro-basic-auth/src/middleware.ts
  - `templates/astro-basic-auth/src/pages/index.astro` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-astro-patterns/templates/astro-basic-auth/src/pages/index.astro
  - `templates/astro-basic-auth/tsconfig.json` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-astro-patterns/templates/astro-basic-auth/tsconfig.json

<!-- Verbatim upstream SKILL.md follows, YAML frontmatter included. -->

---
name: clerk-astro-patterns
description: 'Astro patterns with Clerk — middleware, SSR pages, island components,
  API routes, static vs SSR rendering. Triggers on: astro clerk, clerk astro middleware,
  astro protected page, clerk island component, astro API route auth, clerk astro
  SSR.'
license: MIT
allowed-tools: WebFetch
metadata:
  author: clerk
  version: 1.0.0
---

# Astro Patterns

SDK: `@clerk/astro` v3+. Requires Astro 4.15+.

## What Do You Need?

| Task | Reference |
|------|-----------|
| Configure middleware | references/middleware.md |
| Protect SSR pages | references/ssr-pages.md |
| Use Clerk in island components | references/island-components.md |
| Auth in API routes | references/api-routes.md |
| Use Clerk with React in Astro | references/astro-react.md |

## Mental Model

Astro has two rendering modes per page: **SSR** and **static prerender**. Clerk works differently in each:

- **SSR pages** — use `Astro.locals.auth()` which is populated by the middleware
- **Static pages** (`export const prerender = true`) — Clerk middleware skips them; use client-side hooks in islands
- **Islands** — React/Vue/Svelte components; use `useAuth()` and other hooks from `@clerk/astro/react`

```
Request → clerkMiddleware() → SSR page → Astro.locals.auth()
                                ↓
                         Island (.client) → useAuth() hook
```

## Setup

### astro.config.mjs

```ts
import { defineConfig } from 'astro/config'
import clerk from '@clerk/astro'

export default defineConfig({
  integrations: [clerk()],
  output: 'server',
})
```

### src/middleware.ts

```ts
import { clerkMiddleware, createRouteMatcher } from '@clerk/astro/server'

const isProtectedRoute = createRouteMatcher(['/dashboard(.*)'])

export const onRequest = clerkMiddleware((auth, context, next) => {
  if (isProtectedRoute(context.request) && !auth().userId) {
    return auth().redirectToSignIn()
  }
  return next()
})
```

## SSR Page Auth

```astro
---
const { userId, orgId } = Astro.locals.auth()
if (!userId) return Astro.redirect('/sign-in')
---

<h1>Dashboard</h1>
```

## Common Pitfalls

| Symptom | Cause | Fix |
|---------|-------|-----|
| `Astro.locals.auth` is undefined | Missing middleware | Add `clerkMiddleware` to `src/middleware.ts` |
| Auth works in dev but not production | `output: 'static'` globally | Set `output: 'server'` or `hybrid` for protected pages |
| Static page has no auth | Prerendered pages skip middleware | Use `export const prerender = false` or move to island |
| Island not reactive to sign-in | Missing `client:load` directive | Add `client:load` to the island component |

## Import Map

| What | Import From |
|------|-------------|
| `clerkMiddleware`, `createRouteMatcher` | `@clerk/astro/server` |
| `useAuth`, `useUser`, `UserButton` | `@clerk/astro/react` |
| Astro components (`<SignIn>`, etc.) | `@clerk/astro/components` |

## Env Variables

```
# .env
PUBLIC_CLERK_PUBLISHABLE_KEY=pk_...
CLERK_SECRET_KEY=sk_...
```

Astro uses `PUBLIC_` prefix for client-exposed variables (not `NEXT_PUBLIC_`).

## See Also

- `clerk-setup` - Initial Clerk install
- `clerk-custom-ui` - Custom flows & appearance
- `clerk-orgs` - B2B organizations

## Docs

[Astro SDK](https://clerk.com/docs/astro/getting-started/quickstart)

<!-- chapter:end slug=clerk-astro-patterns -->

---

<!-- chapter:begin slug=clerk-chrome-extension-patterns position=13 -->

## 13. clerk-chrome-extension-patterns

- **Source:** https://github.com/clerk/skills/blob/main/skills/frameworks/clerk-chrome-extension-patterns/SKILL.md
- **Raw:** https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-chrome-extension-patterns/SKILL.md
- **Markdown:** https://skillsdocs.com/clerk/skills/clerk-chrome-extension-patterns.md
- **Licence:** MIT — https://spdx.org/licenses/MIT.html

Bundled files (8), referenced from this skill's directory:
  - `evals/evals.json` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-chrome-extension-patterns/evals/evals.json
  - `references/content-scripts.md` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-chrome-extension-patterns/references/content-scripts.md
  - `references/create-clerk-client.md` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-chrome-extension-patterns/references/create-clerk-client.md
  - `references/headless-extension.md` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-chrome-extension-patterns/references/headless-extension.md
  - `references/sync-host.md` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-chrome-extension-patterns/references/sync-host.md
  - `templates/chrome-ext-basic-auth/package.json` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-chrome-extension-patterns/templates/chrome-ext-basic-auth/package.json
  - `templates/chrome-ext-basic-auth/src/popup.tsx` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-chrome-extension-patterns/templates/chrome-ext-basic-auth/src/popup.tsx
  - `templates/chrome-ext-basic-auth/tsconfig.json` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-chrome-extension-patterns/templates/chrome-ext-basic-auth/tsconfig.json

<!-- Verbatim upstream SKILL.md follows, YAML frontmatter included. -->

---
name: clerk-chrome-extension-patterns
description: 'Chrome Extension auth with @clerk/chrome-extension -- popup/sidepanel
  setup, syncHost for OAuth/SAML via web app, createClerkClient for service workers
  and headless extensions, stable CRX ID. Triggers on: Chrome extension auth, Plasmo
  clerk, popup sign-in, syncHost, background service worker token, createClerkClient,
  headless extension.'
license: MIT
allowed-tools: WebFetch
compatibility: Requires PLASMO_PUBLIC_CLERK_PUBLISHABLE_KEY (Plasmo prefix for public env vars) and CLERK_FRONTEND_API.
metadata:
  author: clerk
  version: 2.0.0
  references:
  - references/sync-host.md
  - references/create-clerk-client.md
  - references/content-scripts.md
  - references/headless-extension.md
---

# Chrome Extension Patterns

## CRITICAL RULES

1. OAuth (Google, GitHub, etc.) and SAML are NOT supported in popups or side panels -- use `syncHost` to delegate auth to your web app
2. Email links (magic links) don't work in popups -- the popup closes when the user clicks outside, resetting sign-in state
3. Side panels don't auto-refresh auth state -- users must close and reopen the side panel after signing in via the web app
4. Service workers and content scripts have NO access to Clerk React hooks -- use `createClerkClient()` or message passing
5. Extension URLs use `chrome-extension://` not `http://` -- all redirect URLs must use `chrome.runtime.getURL('.')`
6. Without a stable CRX ID, every rebuild breaks auth -- configure `key` in manifest BEFORE deploying
7. Content scripts cannot use Clerk directly due to origin restrictions -- Clerk enforces strict allowed origins
8. Bot protection must be DISABLED in Clerk Dashboard -- Cloudflare bot detection is not supported in extension environments

## Authentication Options

| Method | Popup | Side Panel | syncHost (with web app) |
|--------|-------|------------|------------------------|
| Email + OTP | Yes | Yes | Yes |
| Email + Link | No | No | Yes |
| Email + Password | Yes | Yes | Yes |
| Username + Password | Yes | Yes | Yes |
| SMS + OTP | Yes | Yes | Yes |
| OAuth (Google, GitHub, etc.) | **NO** | **NO** | **YES** |
| SAML | **NO** | **NO** | **YES** |
| Passkeys | Yes | Yes | Yes |
| Google One Tap | No | No | Yes |
| Web3 | No | No | Yes |

## Quick Start (Plasmo)

```bash
npx create-plasmo --with-tailwindcss --with-src my-extension
cd my-extension
npm install @clerk/chrome-extension
```

Enable **Native API** in Clerk Dashboard under Native applications. Required for all extension integrations.

`.env.development`:
```
PLASMO_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...
CLERK_FRONTEND_API=https://your-app.clerk.accounts.dev
```

`src/popup.tsx`:
```tsx
import { ClerkProvider, Show, SignInButton, SignUpButton, UserButton } from '@clerk/chrome-extension'

const PUBLISHABLE_KEY = process.env.PLASMO_PUBLIC_CLERK_PUBLISHABLE_KEY
const EXTENSION_URL = chrome.runtime.getURL('.')

if (!PUBLISHABLE_KEY) {
  throw new Error('Missing PLASMO_PUBLIC_CLERK_PUBLISHABLE_KEY')
}

function IndexPopup() {
  return (
    <ClerkProvider
      publishableKey={PUBLISHABLE_KEY}
      afterSignOutUrl={`${EXTENSION_URL}/popup.html`}
      signInFallbackRedirectUrl={`${EXTENSION_URL}/popup.html`}
      signUpFallbackRedirectUrl={`${EXTENSION_URL}/popup.html`}
    >
      <Show when="signed-out">
        <SignInButton mode="modal" />
        <SignUpButton mode="modal" />
      </Show>
      <Show when="signed-in">
        <UserButton />
      </Show>
    </ClerkProvider>
  )
}

export default IndexPopup
```

Use `mode="modal"` for `SignInButton` -- navigating to a separate page breaks the popup flow.

## syncHost -- Sync Auth with Web App

Use this when you need OAuth, SAML, or want the extension to reflect sign-in from your web app.

**How it works**: The extension reads the Clerk session cookie from your web app's domain via `host_permissions`.

**Step 1 -- Environment variables:**

`.env.development`:
```
PLASMO_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...
CLERK_FRONTEND_API=https://your-app.clerk.accounts.dev
PLASMO_PUBLIC_CLERK_SYNC_HOST=http://localhost
```

`.env.production`:
```
PLASMO_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_live_...
CLERK_FRONTEND_API=https://clerk.your-domain.com
PLASMO_PUBLIC_CLERK_SYNC_HOST=https://clerk.your-domain.com
```

**Step 2 -- Add `syncHost` prop:**

```tsx
const SYNC_HOST = process.env.PLASMO_PUBLIC_CLERK_SYNC_HOST

<ClerkProvider
  publishableKey={PUBLISHABLE_KEY}
  syncHost={SYNC_HOST}
  afterSignOutUrl="/"
  routerPush={(to) => navigate(to)}
  routerReplace={(to) => navigate(to, { replace: true })}
>
```

**Step 3 -- Configure `host_permissions` in `package.json`:**

```json
{
  "manifest": {
    "key": "$CRX_PUBLIC_KEY",
    "permissions": ["cookies", "storage"],
    "host_permissions": [
      "$PLASMO_PUBLIC_CLERK_SYNC_HOST/*",
      "$CLERK_FRONTEND_API/*"
    ]
  }
}
```

**Step 4 -- Add extension ID to web app's allowed origins via Clerk API:**

```bash
curl -X PATCH https://api.clerk.com/v1/instance \
  -H "Authorization: Bearer YOUR_SECRET_KEY" \
  -H "Content-type: application/json" \
  -d '{"allowed_origins": ["chrome-extension://YOUR_EXTENSION_ID"]}'
```

**Hide unsupported auth methods in popup when using syncHost:**

```tsx
<SignIn
  appearance={{
    elements: {
      socialButtonsRoot: 'plasmo-hidden',
      dividerRow: 'plasmo-hidden',
    },
  }}
/>
```

Full guide: `references/sync-host.md`

## createClerkClient() for Vanilla JS / Service Workers

Import from `@clerk/chrome-extension/client` (not `@clerk/chrome-extension`).

**Background service worker** (`src/background/index.ts`):

```typescript
import { createClerkClient } from '@clerk/chrome-extension/client'

const publishableKey = process.env.PLASMO_PUBLIC_CLERK_PUBLISHABLE_KEY

async function getToken(): Promise<string | null> {
  const clerk = await createClerkClient({
    publishableKey,
    background: true,
  })
  if (!clerk.session) return null
  return await clerk.session.getToken()
}

chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
  getToken()
    .then((token) => sendResponse({ token }))
    .catch((error) => {
      console.error('[Background] Error:', JSON.stringify(error))
      sendResponse({ token: null })
    })
  return true
})
```

The `background: true` flag keeps sessions fresh even when popup/sidepanel is closed. Without it, tokens expire after 60 seconds.

**Popup with vanilla JS** (`src/popup.ts`):

```typescript
import { createClerkClient } from '@clerk/chrome-extension/client'

const EXTENSION_URL = chrome.runtime.getURL('.')
const POPUP_URL = `${EXTENSION_URL}popup.html`

const clerk = createClerkClient({ publishableKey })

clerk.load({
  afterSignOutUrl: POPUP_URL,
  signInForceRedirectUrl: POPUP_URL,
  signUpForceRedirectUrl: POPUP_URL,
  allowedRedirectProtocols: ['chrome-extension:'],
}).then(() => {
  clerk.addListener(render)
  render()
})
```

Full guide: `references/create-clerk-client.md`

## Headless Extension (no popup, no side panel)

For extensions that run entirely in the background and sync with a web app.

Uses `syncHost` + `createClerkClient` with `background: true` to read auth state from the web app's cookies.

```typescript
import { createClerkClient } from '@clerk/chrome-extension/client'

const publishableKey = process.env.PLASMO_PUBLIC_CLERK_PUBLISHABLE_KEY
const syncHost = process.env.PLASMO_PUBLIC_CLERK_SYNC_HOST

async function getAuthenticatedUser() {
  const clerk = await createClerkClient({
    publishableKey,
    syncHost,
    background: true,
  })
  return clerk.user
}
```

Requires `host_permissions` for the sync host domain in `package.json`.

Full guide: `references/headless-extension.md`

## Content Scripts

Content scripts run in an isolated JavaScript world injected into web pages. **Clerk cannot be used directly** -- origin restrictions prevent it.

Use message passing to request auth state from the background service worker:

```typescript
// content.ts
async function getToken(): Promise<string | null> {
  return new Promise((resolve) => {
    chrome.runtime.sendMessage({ type: 'GET_TOKEN' }, (response) => {
      resolve(response?.token ?? null)
    })
  })
}

async function main() {
  const token = await getToken()
  if (!token) return
  // use token for authenticated API calls
}

main()
```

Full guide: `references/content-scripts.md`

## Stable CRX ID

Without a pinned key, Chrome derives the CRX ID from a random key at build time. This rotates every rebuild, breaking allowed origins.

**Option A -- Plasmo Itero (recommended):**
1. Visit [Plasmo Itero Generate Keypairs](https://itero.plasmo.com/ext/generate-keypairs)
2. Click "Generate KeyPairs" -- save Private Key securely, copy Public Key and CRX ID

**Option B -- OpenSSL:**
```bash
openssl genrsa -out key.pem 2048
# Use Plasmo Itero to convert or extract the public key in correct format
```

**`.env.chrome`:**
```
CRX_PUBLIC_KEY="<PUBLIC KEY from Itero>"
```

**`package.json`:**
```json
{
  "manifest": {
    "key": "$CRX_PUBLIC_KEY",
    "permissions": ["cookies", "storage"],
    "host_permissions": [
      "http://localhost/*",
      "$CLERK_FRONTEND_API/*"
    ]
  }
}
```

Add `chrome-extension://YOUR_STABLE_CRX_ID` to Clerk Dashboard > Allowed Origins.

## Token Cache (persist across popup closes)

```tsx
const tokenCache = {
  async getToken(key: string) {
    const result = await chrome.storage.local.get(key)
    return result[key] ?? null
  },
  async saveToken(key: string, token: string) {
    await chrome.storage.local.set({ [key]: token })
  },
  async clearToken(key: string) {
    await chrome.storage.local.remove(key)
  },
}

<ClerkProvider publishableKey={PUBLISHABLE_KEY} tokenCache={tokenCache}>
```

| Storage type | Scope | Clears on |
|---|---|---|
| `chrome.storage.local` | Device | Uninstall or manual clear |
| `chrome.storage.session` | Session | Browser close |
| `chrome.storage.sync` | All devices | Uninstall (size-limited, 8KB) |
| `localStorage` | Popup only | Popup close -- do not use for auth |

## Common Pitfalls

| Symptom | Cause | Fix |
|---------|-------|-----|
| Redirect loop on sign-in | Missing CRX URL in ClerkProvider props | Set `afterSignOutUrl`, `signInFallbackRedirectUrl` |
| OAuth button not working | OAuth not supported in popup | Use `syncHost` to delegate to web app |
| Auth state stale after web app sign-in | `syncHost` not configured | Add `syncHost` prop + `host_permissions` |
| Side panel shows signed-out after web sign-in | Known limitation | User must close and reopen the side panel |
| Background can't get token after 60s | Session expired, no background refresh | Use `createClerkClient({ background: true })` |
| Content script can't access Clerk | Isolated world + origin restrictions | Use message passing to background service worker |
| Auth breaks after rebuild | CRX ID rotated | Configure stable key via `.env.chrome` |
| `PLASMO_PUBLIC_` var undefined | Wrong env file | Use `.env.development`, not `.env` |
| Bot protection errors | Cloudflare not supported in extensions | Disable bot protection in Clerk Dashboard |
| Token cache not persisting | Using `localStorage` in popup | Use `chrome.storage.local` or pass `tokenCache` prop |

## Plan Requirements

| Feature | Plan |
|---------|------|
| Basic popup auth (email/password, OTP) | Free |
| Passkeys | Free |
| syncHost | Requires Pro (custom domain) |
| OAuth through syncHost | Pro + OAuth configured on web app |
| SAML through syncHost | Enterprise |
| Bot protection | N/A -- must be disabled for extensions |

## See Also

- `clerk-setup` - Initial Clerk install
- `clerk-custom-ui` - Custom flows & appearance

<!-- chapter:end slug=clerk-chrome-extension-patterns -->

---

<!-- chapter:begin slug=clerk-nextjs-patterns position=14 -->

## 14. clerk-nextjs-patterns

- **Source:** https://github.com/clerk/skills/blob/main/skills/frameworks/clerk-nextjs-patterns/SKILL.md
- **Raw:** https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-nextjs-patterns/SKILL.md
- **Markdown:** https://skillsdocs.com/clerk/skills/clerk-nextjs-patterns.md
- **Licence:** MIT — https://spdx.org/licenses/MIT.html

Bundled files (11), referenced from this skill's directory:
  - `evals/evals.json` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-nextjs-patterns/evals/evals.json
  - `references/api-routes.md` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-nextjs-patterns/references/api-routes.md
  - `references/caching-auth.md` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-nextjs-patterns/references/caching-auth.md
  - `references/middleware-strategies.md` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-nextjs-patterns/references/middleware-strategies.md
  - `references/server-actions.md` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-nextjs-patterns/references/server-actions.md
  - `references/server-vs-client.md` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-nextjs-patterns/references/server-vs-client.md
  - `templates/nextjs-basic-auth/app/layout.tsx` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-nextjs-patterns/templates/nextjs-basic-auth/app/layout.tsx
  - `templates/nextjs-basic-auth/app/page.tsx` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-nextjs-patterns/templates/nextjs-basic-auth/app/page.tsx
  - `templates/nextjs-basic-auth/package.json` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-nextjs-patterns/templates/nextjs-basic-auth/package.json
  - `templates/nextjs-basic-auth/proxy.ts` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-nextjs-patterns/templates/nextjs-basic-auth/proxy.ts
  - `templates/nextjs-basic-auth/tsconfig.json` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-nextjs-patterns/templates/nextjs-basic-auth/tsconfig.json

<!-- Verbatim upstream SKILL.md follows, YAML frontmatter included. -->

---
name: clerk-nextjs-patterns
description: Advanced Next.js patterns - middleware, Server Actions, caching with
  Clerk.
license: MIT
allowed-tools: WebFetch
compatibility: Requires NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY and CLERK_SECRET_KEY. For manual JWT verification (standalone API servers without Clerk middleware), additionally requires CLERK_JWT_KEY or CLERK_PEM_PUBLIC_KEY.
metadata:
  author: clerk
  version: 2.2.0
---

# Next.js Patterns

> **Version**: Check `package.json` for the SDK version — see `clerk` skill for the version table. Core 2 differences are noted inline with `> **Core 2 ONLY (skip if current SDK):**` callouts.

For basic setup, see `clerk-setup` skill.

## What Do You Need?

| Task | Reference |
|------|-----------|
| Server vs client auth (`auth()` vs hooks) | references/server-vs-client.md |
| Configure middleware (public-first vs protected-first) | references/middleware-strategies.md |
| Protect Server Actions | references/server-actions.md |
| API route auth (401 vs 403) | references/api-routes.md |
| Cache auth data (user-scoped caching) | references/caching-auth.md |

## References

| Reference | Description |
|-----------|-------------|
| `references/server-vs-client.md` | `await auth()` vs hooks |
| `references/middleware-strategies.md` | Public-first vs protected-first, `proxy.ts` (Next.js <=15: `middleware.ts`) |
| `references/server-actions.md` | Protect mutations |
| `references/api-routes.md` | 401 vs 403 |
| `references/caching-auth.md` | User-scoped caching |

## Mental Model

Server vs Client = different auth APIs:
- **Server**: `await auth()` from `@clerk/nextjs/server` (async!)
- **Client**: `useAuth()` hook from `@clerk/nextjs` (sync)

Never mix them. Server Components use server imports, Client Components use hooks.

Key properties from `auth()`:
- `isAuthenticated` — boolean, replaces the `!!userId` pattern
- `sessionStatus` — `'active'` | `'pending'`, for detecting incomplete session tasks
- `userId`, `orgId`, `orgSlug`, `has()`, `protect()` — unchanged

> **Core 2 ONLY (skip if current SDK):** `isAuthenticated` and `sessionStatus` are not available. Check `!!userId` instead.

## Minimal Pattern

```typescript
// Server Component
import { auth } from '@clerk/nextjs/server'

export default async function Page() {
  const { isAuthenticated, userId } = await auth()  // MUST await!
  if (!isAuthenticated) return <p>Not signed in</p>
  return <p>Hello {userId}</p>
}
```

> **Core 2 ONLY (skip if current SDK):** `isAuthenticated` is not available. Use `if (!userId)` instead.

### Conditional Rendering with `<Show>`

For client-side conditional rendering based on auth state. `<Show>` covers both authentication checks and authorization (feature, plan, role, permission) in one component.

**Authentication check:**

```tsx
import { Show } from '@clerk/nextjs'

<Show when="signed-in" fallback={<p>Please sign in</p>}>
  <Dashboard />
</Show>
```

**Authorization checks (B2B):**

```tsx
// Feature-based (preferred — features can move between plans without redeploy)
<Show when={{ feature: 'analytics' }} fallback={<UpgradePrompt />}>
  <AnalyticsDashboard />
</Show>

// Permission-based (preferred over role-based for granular access)
<Show when={{ permission: 'org:invoices:create' }}>
  <NewInvoiceButton />
</Show>

// Plan-based (tier-level gating)
<Show when={{ plan: 'pro' }}>
  <ProFeatures />
</Show>

// Role-based (use sparingly — prefer permission)
<Show when={{ role: 'org:admin' }}>
  <AdminPanel />
</Show>
```

**Callback for complex logic:**

```tsx
<Show when={(has) => has({ role: 'org:admin' }) || has({ role: 'org:billing_manager' })}>
  <BillingActions />
</Show>
```

> **Core 2 ONLY (skip if current SDK):** `<Show>` does not exist. For authentication, use `<SignedIn>` and `<SignedOut>`. For authorization (role / permission), use `<Protect>` with the same prop names (`role`, `permission`, `condition`). Feature- and plan-based variants require Core 3. See `clerk-custom-ui` skill, `core-3/show-component.md` for the full migration table.

## Common Pitfalls

| Symptom | Cause | Fix |
|---------|-------|-----|
| `undefined` userId in Server Component | Missing `await` | `await auth()` not `auth()` |
| Auth not working on API routes | Missing matcher | Add `'/(api|trpc)(.*)'` to `proxy.ts` (Next.js <=15: `middleware.ts`) |
| Cache returns wrong user's data | Missing userId in key | Include `userId` in `unstable_cache` key |
| Mutations bypass auth | Unprotected Server Action | Check `auth()` at start of action |
| Wrong HTTP error code | Confused 401/403 | 401 = not signed in, 403 = no permission |

## Session Tokens & Custom JWTs

### getToken() for external APIs

Pass a custom JWT to third-party services (Hasura, Supabase, etc.) using JWT templates defined in the Clerk dashboard.

**Server-side (Server Component or Route Handler)**:

```typescript
import { auth } from '@clerk/nextjs/server'

export default async function Page() {
  const { getToken } = await auth()
  const token = await getToken({ template: 'hasura' })
  if (!token) return <p>Not authenticated</p>

  const res = await fetch('https://api.example.com/graphql', {
    headers: { Authorization: `Bearer ${token}` },
  })
  const data = await res.json()
  return <pre>{JSON.stringify(data)}</pre>
}
```

**Client-side (Client Component)**:

```typescript
'use client'
import { useAuth } from '@clerk/nextjs'

export function DataFetcher() {
  const { getToken } = useAuth()

  async function fetchData() {
    const token = await getToken({ template: 'supabase' })
    if (!token) return

    const res = await fetch('https://api.example.com/data', {
      headers: { Authorization: `Bearer ${token}` },
    })
    return res.json()
  }

  return <button onClick={fetchData}>Fetch</button>
}
```

`getToken()` returns `null` when the user is not authenticated — always null-check before use.

### useSession() for session data

Access session metadata in client components:

```typescript
'use client'
import { useSession } from '@clerk/nextjs'

export function SessionInfo() {
  const { session } = useSession()
  if (!session) return null

  return (
    <p>
      Session {session.id} — last active: {session.lastActiveAt.toISOString()}
    </p>
  )
}
```

### Manual JWT verification (no Clerk middleware)

For standalone API servers that receive Clerk session tokens from the `Authorization` header or the `__session` cookie (same-origin).

**Using `@clerk/backend` `verifyToken`** (recommended):

```typescript
import { verifyToken } from '@clerk/backend'

const token = req.headers.authorization?.replace('Bearer ', '')
if (!token) return res.status(401).json({ error: 'No token' })

try {
  const claims = await verifyToken(token, {
    jwtKey: process.env.CLERK_JWT_KEY,
  })
  // claims.sub = userId
} catch {
  return res.status(401).json({ error: 'Invalid token' })
}
```

**Using `jsonwebtoken`** (when you can't use `@clerk/backend`):

```typescript
import jwt from 'jsonwebtoken'

const publicKey = process.env.CLERK_PEM_PUBLIC_KEY!.replace(/\\n/g, '\n')
const token = req.headers.authorization?.replace('Bearer ', '')
if (!token) return res.status(401).json({ error: 'No token' })

try {
  const claims = jwt.verify(token, publicKey, { algorithms: ['RS256'] }) as jwt.JwtPayload
  // Manually check exp and nbf (jsonwebtoken does this automatically, but verify azp if needed)
  // claims.sub = userId
} catch {
  return res.status(401).json({ error: 'Invalid or expired token' })
}
```

Token sources:
- **Same-origin requests**: `__session` cookie (Clerk sets this automatically)
- **Cross-origin / mobile / API-to-API**: `Authorization: Bearer <token>` header

> **CRITICAL**: Always check `exp` and `nbf` claims. `verifyToken` from `@clerk/backend` handles this automatically; with raw `jsonwebtoken`, set `ignoreExpiration: false` (default) and ensure `clockTolerance` is minimal.

## See Also

- `clerk-setup` - Initial Clerk install
- `clerk-orgs` - B2B patterns (active org, role/permission gating)
- `clerk-billing` - Plan and feature entitlements with `has()`
- `clerk-webhooks` - Sync user/org events to your database
- `clerk-custom-ui` - Theming and customization for built-in components

## Docs

[Next.js SDK](https://clerk.com/docs/reference/nextjs/overview)

<!-- chapter:end slug=clerk-nextjs-patterns -->

---

<!-- chapter:begin slug=clerk-nuxt-patterns position=15 -->

## 15. clerk-nuxt-patterns

- **Source:** https://github.com/clerk/skills/blob/main/skills/frameworks/clerk-nuxt-patterns/SKILL.md
- **Raw:** https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-nuxt-patterns/SKILL.md
- **Markdown:** https://skillsdocs.com/clerk/skills/clerk-nuxt-patterns.md
- **Licence:** MIT — https://spdx.org/licenses/MIT.html

Bundled files (8), referenced from this skill's directory:
  - `evals/evals.json` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-nuxt-patterns/evals/evals.json
  - `references/composables.md` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-nuxt-patterns/references/composables.md
  - `references/nuxt-middleware.md` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-nuxt-patterns/references/nuxt-middleware.md
  - `references/server-api-routes.md` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-nuxt-patterns/references/server-api-routes.md
  - `references/ssr-auth.md` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-nuxt-patterns/references/ssr-auth.md
  - `templates/nuxt-basic-auth/app.vue` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-nuxt-patterns/templates/nuxt-basic-auth/app.vue
  - `templates/nuxt-basic-auth/nuxt.config.ts` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-nuxt-patterns/templates/nuxt-basic-auth/nuxt.config.ts
  - `templates/nuxt-basic-auth/package.json` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-nuxt-patterns/templates/nuxt-basic-auth/package.json

<!-- Verbatim upstream SKILL.md follows, YAML frontmatter included. -->

---
name: clerk-nuxt-patterns
description: 'Nuxt 3 auth patterns with @clerk/nuxt - middleware, composables, server
  API routes, SSR. Triggers on: Nuxt auth, useAuth composable, clerkMiddleware Nuxt,
  server API Clerk, Nuxt route protection.'
license: MIT
allowed-tools: WebFetch
metadata:
  author: clerk
  version: 1.0.0
---

# Nuxt Patterns

## What Do You Need?

| Task | Reference |
|------|-----------|
| Protect routes with middleware | references/nuxt-middleware.md |
| Auth in server API routes (Nitro) | references/server-api-routes.md |
| useAuth / useUser in components | references/composables.md |
| SSR-safe auth patterns | references/ssr-auth.md |

## References

| Reference | Description |
|-----------|-------------|
| `references/nuxt-middleware.md` | Route protection, clerkMiddleware() |
| `references/server-api-routes.md` | Nitro server route auth |
| `references/composables.md` | useAuth, useUser, useClerk |
| `references/ssr-auth.md` | SSR hydration, server vs client |

## Setup

```
npm install @clerk/nuxt
```

`.env`:
```
NUXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_...
NUXT_CLERK_SECRET_KEY=sk_...
```

`nuxt.config.ts`:
```typescript
export default defineNuxtConfig({
  modules: ['@clerk/nuxt'],
})
```

This single line auto-configures middleware, plugins, and component auto-imports.

## Mental Model

`@clerk/nuxt` auto-imports all Clerk components and composables — no explicit imports needed in `<script setup>`.

- **Composables** (`useAuth`, `useUser`) — client-side reactive, inside `<script setup>`
- **Server routes** (`clerkClient`) — Nitro server routes, `event.context.auth`
- **Middleware** (`clerkMiddleware`) — auto-registered, use `auth().protect()` to lock routes

## Minimal Pattern

```vue
<!-- pages/dashboard.vue -->
<script setup lang="ts">
definePageMeta({ middleware: 'auth' })
const { userId } = useAuth()
</script>

<template>
  <Show when="signed-in">
    <p>Hello {{ userId }}</p>
  </Show>
</template>
```

> `definePageMeta({ middleware: 'auth' })` uses the built-in auth middleware from `@clerk/nuxt`.

## Common Pitfalls

| Symptom | Cause | Fix |
|---------|-------|-----|
| Composables return `undefined` on server | useAuth is client-only | Use `event.context.auth` in server routes |
| Route not protected | Missing `middleware: 'auth'` meta | Add `definePageMeta({ middleware: 'auth' })` |
| `clerkClient` not available | Wrong import path | Import from `@clerk/nuxt/server` |
| Hydration mismatch | Rendering auth state before mounted | Wrap in `<ClientOnly>` or check `isLoaded` |
| Env vars not picked up | Wrong prefix | Nuxt requires `NUXT_PUBLIC_` for public, `NUXT_` for server |

## Org-Aware Pattern

```vue
<script setup lang="ts">
const { orgId, orgRole } = useAuth()
</script>

<template>
  <div v-if="orgId">
    <p>Org: {{ orgId }}</p>
    <p v-if="orgRole === 'org:admin'">Admin panel</p>
  </div>
  <div v-else>
    <OrganizationSwitcher />
  </div>
</template>
```

## See Also

- `clerk-setup` - Initial Clerk install
- `clerk-custom-ui` - Custom flows & appearance
- `clerk-orgs` - B2B organizations

## Docs

[Nuxt SDK](https://clerk.com/docs/nuxt/getting-started/quickstart)

<!-- chapter:end slug=clerk-nuxt-patterns -->

---

<!-- chapter:begin slug=clerk-react-patterns position=16 -->

## 16. clerk-react-patterns

- **Source:** https://github.com/clerk/skills/blob/main/skills/frameworks/clerk-react-patterns/SKILL.md
- **Raw:** https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-react-patterns/SKILL.md
- **Markdown:** https://skillsdocs.com/clerk/skills/clerk-react-patterns.md
- **Licence:** MIT — https://spdx.org/licenses/MIT.html

Bundled files (11), referenced from this skill's directory:
  - `evals/evals.json` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-react-patterns/evals/evals.json
  - `references/custom-flows.md` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-react-patterns/references/custom-flows.md
  - `references/hooks.md` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-react-patterns/references/hooks.md
  - `references/protected-routes.md` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-react-patterns/references/protected-routes.md
  - `references/router-integration.md` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-react-patterns/references/router-integration.md
  - `templates/react-basic-auth/index.html` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-react-patterns/templates/react-basic-auth/index.html
  - `templates/react-basic-auth/package.json` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-react-patterns/templates/react-basic-auth/package.json
  - `templates/react-basic-auth/src/App.tsx` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-react-patterns/templates/react-basic-auth/src/App.tsx
  - `templates/react-basic-auth/src/main.tsx` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-react-patterns/templates/react-basic-auth/src/main.tsx
  - `templates/react-basic-auth/tsconfig.json` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-react-patterns/templates/react-basic-auth/tsconfig.json
  - `templates/react-basic-auth/vite.config.ts` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-react-patterns/templates/react-basic-auth/vite.config.ts

<!-- Verbatim upstream SKILL.md follows, YAML frontmatter included. -->

---
name: clerk-react-patterns
description: 'React SPA auth patterns with @clerk/react for Vite/CRA - ClerkProvider
  setup, useAuth/useUser/useClerk hooks, React Router protected routes, custom sign-in
  flows. Triggers on: Vite Clerk setup, React Router auth, useAuth hook, protected
  route, custom sign-in form React.'
license: MIT
allowed-tools: WebFetch
metadata:
  author: clerk
  version: 1.0.0
---

# React SPA Patterns

> This skill covers `@clerk/react` for Vite/CRA SPAs. For Next.js use `clerk-nextjs-patterns`. For TanStack Start use `clerk-tanstack-patterns`.

## What Do You Need?

| Task | Reference |
|------|-----------|
| useAuth / useUser / useClerk hooks | references/hooks.md |
| Protected routes with React Router | references/protected-routes.md |
| Custom sign-in / sign-up forms | references/custom-flows.md |
| React Router v6/v7 integration | references/router-integration.md |

## References

| Reference | Description |
|-----------|-------------|
| `references/hooks.md` | useAuth, isLoaded guard |
| `references/protected-routes.md` | ProtectedRoute pattern |
| `references/custom-flows.md` | useSignIn, useSignUp flows |
| `references/router-integration.md` | React Router v6/v7 setup |

## Setup

```
npm install @clerk/react
```

`.env`:
```
VITE_CLERK_PUBLISHABLE_KEY=pk_...
```

`src/main.tsx`:
```tsx
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { ClerkProvider } from '@clerk/react'
import App from './App.tsx'

const PUBLISHABLE_KEY = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <ClerkProvider publishableKey={PUBLISHABLE_KEY}>
      <App />
    </ClerkProvider>
  </StrictMode>,
)
```

## Mental Model

`@clerk/react` is client-only — there is no server-side `auth()`. All auth state comes from hooks.

- `isLoaded` must be `true` before trusting `isSignedIn` — always guard on `isLoaded`
- `useClerk()` gives access to `signOut`, `openSignIn`, `openUserProfile` and other methods
- `getToken()` from `useAuth()` fetches the session JWT for API calls

## Minimal Pattern

```tsx
import { useAuth } from '@clerk/react'

export function Dashboard() {
  const { isLoaded, isSignedIn, userId } = useAuth()

  if (!isLoaded) return <div>Loading...</div>
  if (!isSignedIn) return <div>Please sign in</div>

  return <div>Hello {userId}</div>
}
```

## Protected Route (React Router v6/v7)

```tsx
import { Navigate, Outlet } from 'react-router-dom'
import { useAuth } from '@clerk/react'

export function ProtectedRoute() {
  const { isLoaded, isSignedIn } = useAuth()

  if (!isLoaded) return <div>Loading...</div>
  if (!isSignedIn) return <Navigate to="/sign-in" replace />

  return <Outlet />
}
```

```tsx
<Routes>
  <Route element={<ProtectedRoute />}>
    <Route path="/dashboard" element={<Dashboard />} />
    <Route path="/settings" element={<Settings />} />
  </Route>
  <Route path="/sign-in" element={<SignIn />} />
</Routes>
```

## Token for API Calls

```tsx
import { useAuth } from '@clerk/react'

export function DataFetcher() {
  const { getToken } = useAuth()

  async function fetchData() {
    const token = await getToken()
    if (!token) return

    const res = await fetch('/api/data', {
      headers: { Authorization: `Bearer ${token}` },
    })
    return res.json()
  }

  return <button onClick={fetchData}>Load</button>
}
```

## Common Pitfalls

| Symptom | Cause | Fix |
|---------|-------|-----|
| `isSignedIn` is `undefined` | `isLoaded` is still `false` | Always check `isLoaded` first |
| `ClerkProvider` missing | Provider not at root | Wrap `<App>` in `main.tsx` |
| Env var undefined | Wrong Vite prefix | Use `VITE_CLERK_PUBLISHABLE_KEY`, access via `import.meta.env` |
| Token is `null` | User not signed in | Null-check `getToken()` result |
| Sign-in component shows blank | No `publishableKey` on provider | Pass `publishableKey` explicitly |

## See Also

- `clerk-setup` - Initial Clerk install
- `clerk-custom-ui` - Custom flows & appearance
- `clerk-orgs` - B2B organizations

## Docs

[React SDK](https://clerk.com/docs/react/getting-started/quickstart)

<!-- chapter:end slug=clerk-react-patterns -->

---

<!-- chapter:begin slug=clerk-react-router-patterns position=17 -->

## 17. clerk-react-router-patterns

- **Source:** https://github.com/clerk/skills/blob/main/skills/frameworks/clerk-react-router-patterns/SKILL.md
- **Raw:** https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-react-router-patterns/SKILL.md
- **Markdown:** https://skillsdocs.com/clerk/skills/clerk-react-router-patterns.md
- **Licence:** MIT — https://spdx.org/licenses/MIT.html

Bundled files (11), referenced from this skill's directory:
  - `evals/evals.json` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-react-router-patterns/evals/evals.json
  - `references/loaders-actions.md` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-react-router-patterns/references/loaders-actions.md
  - `references/protected-routes.md` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-react-router-patterns/references/protected-routes.md
  - `references/ssr-auth.md` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-react-router-patterns/references/ssr-auth.md
  - `templates/react-router-basic-auth/app/app.css` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-react-router-patterns/templates/react-router-basic-auth/app/app.css
  - `templates/react-router-basic-auth/app/root.tsx` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-react-router-patterns/templates/react-router-basic-auth/app/root.tsx
  - `templates/react-router-basic-auth/app/routes.ts` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-react-router-patterns/templates/react-router-basic-auth/app/routes.ts
  - `templates/react-router-basic-auth/app/routes/home.tsx` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-react-router-patterns/templates/react-router-basic-auth/app/routes/home.tsx
  - `templates/react-router-basic-auth/package.json` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-react-router-patterns/templates/react-router-basic-auth/package.json
  - `templates/react-router-basic-auth/react-router.config.ts` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-react-router-patterns/templates/react-router-basic-auth/react-router.config.ts
  - `templates/react-router-basic-auth/vite.config.ts` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-react-router-patterns/templates/react-router-basic-auth/vite.config.ts

<!-- Verbatim upstream SKILL.md follows, YAML frontmatter included. -->

---
name: clerk-react-router-patterns
description: 'React Router v7/v8 patterns with Clerk — rootAuthLoader, getAuth in loaders,
  clerkMiddleware, protected routes, SSR user data, org switching. Triggers on: react-router
  auth, rootAuthLoader, getAuth loader, react-router protected route, loader authentication,
  SSR auth react-router, useNavigate may be used only in the context of a Router.'
license: MIT
allowed-tools: WebFetch
metadata:
  author: clerk
  version: 1.1.0
---

# React Router Patterns

SDK: `@clerk/react-router` v3.5+. Supports React Router v7.9+ and v8.

## What Do You Need?

| Task | Reference |
|------|-----------|
| Auth in loaders and actions | references/loaders-actions.md |
| Protected routes and redirects | references/protected-routes.md |
| SSR user data and session | references/ssr-auth.md |

## React Router v7 vs v8

Check the installed `react-router` major version before scaffolding — the config differs:

| | v7.9+ | v8+ |
|--|--|--|
| Middleware API | Opt-in: set `future: { v8_middleware: true }` in `react-router.config.ts` | Always on — do NOT set the flag (v8 removed it) |
| `ssr.noExternal` workaround (below) | Not needed | **Required** |

## Minimal Setup

### 1. vite.config.ts (v8 only — REQUIRED)

React Router v8 ships development/production conditional exports. In `react-router dev`,
Vite externalizes `@clerk/react-router` for SSR, so Node resolves the production build of
react-router while the app code gets the development build — two module instances, two
Router contexts. Every request then fails during SSR with:

```
Error: useNavigate() may be used only in the context of a <Router> component.
```

**`npm ls react-router` shows a single copy — that does NOT rule this out.** The
duplication is per export condition, not per installed copy. Do not chase duplicate
installs; add the workaround (upstream issue:
https://github.com/remix-run/react-router/issues/15232):

```ts
import { reactRouter } from '@react-router/dev/vite'
import { defineConfig } from 'vite'

export default defineConfig({
  plugins: [reactRouter()],
  ssr: {
    noExternal: ['@clerk/react-router'],
  },
})
```

### 2. root.tsx

```tsx
import { Outlet } from 'react-router'
import { rootAuthLoader, clerkMiddleware } from '@clerk/react-router/server'
import { ClerkProvider } from '@clerk/react-router'
import type { Route } from './+types/root'

export const middleware: Route.MiddlewareFunction[] = [clerkMiddleware()]

export async function loader(args: Route.LoaderArgs) {
  return rootAuthLoader(args)
}

export default function App({ loaderData }: Route.ComponentProps) {
  return (
    <ClerkProvider loaderData={loaderData}>
      <Outlet />
    </ClerkProvider>
  )
}
```

There is no `ClerkApp` HOC in `@clerk/react-router` (that was the `@clerk/remix` API).
Render `<ClerkProvider loaderData={loaderData}>` inside the default export and pass it
the root route's `loaderData`.

### 3. react-router.config.ts (v7 only)

```ts
import type { Config } from '@react-router/dev/config'

export default {
  future: {
    v8_middleware: true,
  },
} satisfies Config
```

On v8, omit the `future` block entirely — the flag no longer exists.

> **Required**: `rootAuthLoader` must be called in `root.tsx`'s loader. Without it, `getAuth` throws in nested loaders.

## Mental Model

React Router v7/v8 uses a middleware + loader pipeline. Clerk plugs into both layers:

- **Middleware** (`clerkMiddleware()`) — runs on every request, attaches auth to context
- **`rootAuthLoader`** — required in `root.tsx` to pass Clerk state to the client
- **`getAuth(args)`** — called inside any loader/action to get the current user

```
Request → clerkMiddleware() → rootAuthLoader → page loader → component
                 ↓                   ↓               ↓
           attaches auth      injects state     getAuth(args)
           to context         to response       reads context
```

## Auth in Loaders

```tsx
import { getAuth } from '@clerk/react-router/server'
import type { Route } from './+types/dashboard'

export async function loader(args: Route.LoaderArgs) {
  const { userId } = await getAuth(args)
  if (!userId) throw redirect('/sign-in')

  const data = await fetchUserData(userId)
  return { data }
}
```

## Auth in Actions

```tsx
import { getAuth } from '@clerk/react-router/server'

export async function action(args: Route.ActionArgs) {
  const { userId, orgId } = await getAuth(args)
  if (!userId) throw new Response('Unauthorized', { status: 401 })

  const formData = await args.request.formData()
  await saveData(userId, orgId, formData)
  return redirect('/dashboard')
}
```

## Client Components

```tsx
import { useAuth, useUser } from '@clerk/react-router'

export function Profile() {
  const { userId, isSignedIn } = useAuth()
  const { user } = useUser()
  if (!isSignedIn) return null
  return <p>{user?.firstName}</p>
}
```

## Org Switching

```tsx
import { OrganizationSwitcher } from '@clerk/react-router'

export function Nav() {
  return <OrganizationSwitcher afterSelectOrganizationUrl="/dashboard" />
}
```

```tsx
export async function loader(args: Route.LoaderArgs) {
  const { userId, orgId } = await getAuth(args)
  if (!userId) throw redirect('/sign-in')
  if (!orgId) throw redirect('/select-org')

  return { data: await fetchOrgData(orgId) }
}
```

## Common Pitfalls

| Symptom | Cause | Fix |
|---------|-------|-----|
| `useNavigate() may be used only in the context of a <Router>` thrown from ClerkProvider during SSR in dev (v8) | Vite dev SSR externalizes `@clerk/react-router`, which then loads react-router's production build while the app uses the development build — two Router contexts. A single copy in `npm ls` does not rule this out. | Add `ssr: { noExternal: ['@clerk/react-router'] }` to `vite.config.ts`. Do NOT downgrade to v7 |
| Build error: `ClerkApp` is not exported | `ClerkApp` does not exist in `@clerk/react-router` | Use `<ClerkProvider loaderData={loaderData}>` in root.tsx's default export |
| `clerkMiddleware() not detected` | Missing middleware (or on v7, missing `v8_middleware` future flag) | Export `middleware = [clerkMiddleware()]` from root route; on v7 also set `future: { v8_middleware: true }` |
| Unknown future flag error/warning (v8) | `v8_middleware` flag left in `react-router.config.ts` after upgrading | Remove the `future.v8_middleware` entry — middleware is always on in v8 |
| `getAuth` returns empty userId | `rootAuthLoader` not called | Call `rootAuthLoader(args)` in `root.tsx` loader |
| Infinite redirect loop | Redirect target is also protected | Exclude `/sign-in` from protection check |
| `redirect` not working in action | Using `Response` instead of `throw redirect()` | Use `throw redirect('/path')` from `react-router` |

## Import Map

| What | Import From |
|------|-------------|
| `getAuth` | `@clerk/react-router/server` |
| `rootAuthLoader` | `@clerk/react-router/server` |
| `clerkMiddleware` | `@clerk/react-router/server` |
| `ClerkProvider` | `@clerk/react-router` |
| `useAuth`, `useUser` | `@clerk/react-router` |
| `OrganizationSwitcher` | `@clerk/react-router` |

## See Also

- `clerk-setup` - Initial Clerk install
- `clerk-custom-ui` - Custom flows & appearance
- `clerk-orgs` - B2B organizations

## Docs

[React Router SDK](https://clerk.com/docs/react-router/getting-started/quickstart)

<!-- chapter:end slug=clerk-react-router-patterns -->

---

<!-- chapter:begin slug=clerk-tanstack-patterns position=18 -->

## 18. clerk-tanstack-patterns

- **Source:** https://github.com/clerk/skills/blob/main/skills/frameworks/clerk-tanstack-patterns/SKILL.md
- **Raw:** https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-tanstack-patterns/SKILL.md
- **Markdown:** https://skillsdocs.com/clerk/skills/clerk-tanstack-patterns.md
- **Licence:** MIT — https://spdx.org/licenses/MIT.html

Bundled files (12), referenced from this skill's directory:
  - `evals/evals.json` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-tanstack-patterns/evals/evals.json
  - `references/loaders.md` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-tanstack-patterns/references/loaders.md
  - `references/router-guards.md` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-tanstack-patterns/references/router-guards.md
  - `references/server-functions.md` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-tanstack-patterns/references/server-functions.md
  - `references/vinxi-server.md` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-tanstack-patterns/references/vinxi-server.md
  - `templates/tanstack-basic-auth/package.json` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-tanstack-patterns/templates/tanstack-basic-auth/package.json
  - `templates/tanstack-basic-auth/src/router.tsx` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-tanstack-patterns/templates/tanstack-basic-auth/src/router.tsx
  - `templates/tanstack-basic-auth/src/routes/__root.tsx` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-tanstack-patterns/templates/tanstack-basic-auth/src/routes/__root.tsx
  - `templates/tanstack-basic-auth/src/routes/index.tsx` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-tanstack-patterns/templates/tanstack-basic-auth/src/routes/index.tsx
  - `templates/tanstack-basic-auth/src/start.ts` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-tanstack-patterns/templates/tanstack-basic-auth/src/start.ts
  - `templates/tanstack-basic-auth/tsconfig.json` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-tanstack-patterns/templates/tanstack-basic-auth/tsconfig.json
  - `templates/tanstack-basic-auth/vite.config.ts` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-tanstack-patterns/templates/tanstack-basic-auth/vite.config.ts

<!-- Verbatim upstream SKILL.md follows, YAML frontmatter included. -->

---
name: clerk-tanstack-patterns
description: 'TanStack React Start auth patterns with @clerk/tanstack-react-start
  - createServerFn, beforeLoad guards, loaders, Vinxi server. Triggers on: TanStack
  auth, createServerFn clerk, beforeLoad protection, TanStack Start middleware.'
license: MIT
allowed-tools: WebFetch
metadata:
  author: clerk
  version: 1.0.0
---

# TanStack React Start Patterns

## What Do You Need?

| Task | Reference |
|------|-----------|
| Protect routes with beforeLoad | references/router-guards.md |
| Auth in createServerFn | references/server-functions.md |
| Pass auth to loaders | references/loaders.md |
| Configure Vinxi + clerkMiddleware | references/vinxi-server.md |

## References

| Reference | Description |
|-----------|-------------|
| `references/router-guards.md` | beforeLoad auth redirect |
| `references/server-functions.md` | createServerFn with auth() |
| `references/loaders.md` | Auth context in loaders |
| `references/vinxi-server.md` | clerkMiddleware() setup |

## Setup

```
npm install @clerk/tanstack-react-start
```

`.env`:
```
CLERK_PUBLISHABLE_KEY=pk_...
CLERK_SECRET_KEY=sk_...
```

`src/start.ts` (Vinxi entry):
```typescript
import { clerkMiddleware } from '@clerk/tanstack-react-start/server'
import { createStart } from '@tanstack/react-start'

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

`src/routes/__root.tsx` — wrap with `<ClerkProvider>`:
```tsx
import { ClerkProvider } from '@clerk/tanstack-react-start'

function RootDocument({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <ClerkProvider>
          {children}
        </ClerkProvider>
      </body>
    </html>
  )
}
```

## Mental Model

TanStack Start runs on Vinxi. Auth flows through two layers:

1. **Server layer** — `createServerFn` + `auth()` from `@clerk/tanstack-react-start/server`
2. **Router layer** — `beforeLoad` on route definitions, throws `redirect` for unauthenticated

Both layers are server-executed. Client hooks (`useAuth`, `useUser`) are React hooks for the browser side.

## Minimal Pattern

```typescript
import { createFileRoute, redirect } from '@tanstack/react-router'
import { createServerFn } from '@tanstack/react-start'
import { auth } from '@clerk/tanstack-react-start/server'

const authStateFn = createServerFn().handler(async () => {
  const { isAuthenticated, userId } = await auth()
  if (!isAuthenticated) {
    throw redirect({ to: '/sign-in' })
  }
  return { userId }
})

export const Route = createFileRoute('/dashboard')({
  beforeLoad: async () => await authStateFn(),
})
```

## Common Pitfalls

| Symptom | Cause | Fix |
|---------|-------|-----|
| `auth()` returns empty | Missing `clerkMiddleware` in start.ts | Add to `requestMiddleware` array |
| `redirect` not thrown | Using `return` instead of `throw` | `throw redirect(...)` in TanStack |
| Wrong import for `auth` | Mixing client/server imports | Server: `@clerk/tanstack-react-start/server` |
| Loader context missing userId | Not passing from beforeLoad | Return from beforeLoad, access via `context` |
| `ClerkProvider` missing | Forgot root wrapping | Add to `__root.tsx` shell component |

## See Also

- `clerk-setup` - Initial Clerk install
- `clerk-custom-ui` - Custom flows & appearance
- `clerk-orgs` - B2B organizations

## Docs

[TanStack React Start SDK](https://clerk.com/docs/tanstack-react-start/getting-started/quickstart)

<!-- chapter:end slug=clerk-tanstack-patterns -->

---

<!-- chapter:begin slug=clerk-vue-patterns position=19 -->

## 19. clerk-vue-patterns

- **Source:** https://github.com/clerk/skills/blob/main/skills/frameworks/clerk-vue-patterns/SKILL.md
- **Raw:** https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-vue-patterns/SKILL.md
- **Markdown:** https://skillsdocs.com/clerk/skills/clerk-vue-patterns.md
- **Licence:** MIT — https://spdx.org/licenses/MIT.html

Bundled files (10), referenced from this skill's directory:
  - `evals/evals.json` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-vue-patterns/evals/evals.json
  - `references/composables.md` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-vue-patterns/references/composables.md
  - `references/pinia-integration.md` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-vue-patterns/references/pinia-integration.md
  - `references/vue-router-guards.md` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-vue-patterns/references/vue-router-guards.md
  - `templates/vue-basic-auth/index.html` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-vue-patterns/templates/vue-basic-auth/index.html
  - `templates/vue-basic-auth/package.json` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-vue-patterns/templates/vue-basic-auth/package.json
  - `templates/vue-basic-auth/src/App.vue` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-vue-patterns/templates/vue-basic-auth/src/App.vue
  - `templates/vue-basic-auth/src/main.ts` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-vue-patterns/templates/vue-basic-auth/src/main.ts
  - `templates/vue-basic-auth/src/style.css` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-vue-patterns/templates/vue-basic-auth/src/style.css
  - `templates/vue-basic-auth/vite.config.ts` — https://raw.githubusercontent.com/clerk/skills/main/skills/frameworks/clerk-vue-patterns/templates/vue-basic-auth/vite.config.ts

<!-- Verbatim upstream SKILL.md follows, YAML frontmatter included. -->

---
name: clerk-vue-patterns
description: 'Vue 3 patterns with Clerk — composables (useAuth, useUser,
  useClerk, useOrganization), Vue Router guards, Pinia auth store
  integration. Triggers on: vue clerk, useAuth vue, clerk composables,
  vue router clerk guard, pinia auth clerk. For Nuxt, use clerk-nuxt-patterns instead.'
license: MIT
allowed-tools: WebFetch
metadata:
  author: clerk
  version: 1.0.0
---

# Vue Patterns

SDK: `@clerk/vue` v2+ (Vue 3). For Nuxt, use `clerk-nuxt-patterns`.

## What Do You Need?

| Task | Reference |
|------|-----------|
| Composables: useAuth, useUser, useOrganization | references/composables.md |
| Vue Router navigation guards | references/vue-router-guards.md |
| Pinia store with auth state | references/pinia-integration.md |

## Mental Model

Vue uses composables from `@clerk/vue`:

- **`useAuth()`** — reactive `isSignedIn`, `userId`, `signOut`
- **`useUser()`** — reactive `user` object
- **`useClerk()`** — full Clerk instance for advanced operations
- **`useOrganization()`** — reactive `organization`, `membership`

## Setup

### Vue (Plain)

```ts
// main.ts
import { clerkPlugin } from '@clerk/vue'
import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)
app.use(clerkPlugin, {
  publishableKey: import.meta.env.VITE_CLERK_PUBLISHABLE_KEY,
})
app.mount('#app')
```

## Composables Usage

```vue
<script setup lang="ts">
import { useAuth, useUser } from '@clerk/vue'

const { isSignedIn, userId, signOut } = useAuth()
const { user } = useUser()
</script>

<template>
  <div v-if="isSignedIn">
    <p>Hello {{ user?.firstName }}</p>
    <button @click="signOut()">Sign Out</button>
  </div>
  <SignInButton v-else />
</template>
```

## Org Switching

```vue
<script setup lang="ts">
import { useOrganizationList } from '@clerk/vue'

const { userMemberships, setActive } = useOrganizationList()
</script>

<template>
  <button
    v-for="mem in userMemberships.data ?? []"
    :key="mem.organization.id"
    @click="setActive({ organization: mem.organization.id })"
  >
    {{ mem.organization.name }}
  </button>
</template>
```

## Common Pitfalls

| Symptom | Cause | Fix |
|---------|-------|-----|
| Composables return `undefined` | Not inside `ClerkProvider` tree | Ensure `app.use(clerkPlugin, { publishableKey })` is called |
| `userId` reactive but not updating | Destructuring loses reactivity | Use `const { userId } = useAuth()` (toRefs-style composable, reactive) |
## Import Map

| What | Import |
|------|--------|
| Composables | `@clerk/vue` |
| Plugin setup | `@clerk/vue` |
| Components | `@clerk/vue` |

## See Also

- `clerk-setup` - Initial Clerk install
- `clerk-custom-ui` - Custom flows & appearance
- `clerk-orgs` - B2B organizations

## Docs

- [Vue SDK](https://clerk.com/docs/vue/getting-started/quickstart)

<!-- chapter:end slug=clerk-vue-patterns -->

---

<!-- chapter:begin slug=clerk-android position=20 -->

## 20. clerk-android

- **Source:** https://github.com/clerk/skills/blob/main/skills/mobile/clerk-android/SKILL.md
- **Raw:** https://raw.githubusercontent.com/clerk/skills/main/skills/mobile/clerk-android/SKILL.md
- **Markdown:** https://skillsdocs.com/clerk/skills/clerk-android.md
- **Licence:** MIT — https://spdx.org/licenses/MIT.html

Bundled files (2), referenced from this skill's directory:
  - `references/custom.md` — https://raw.githubusercontent.com/clerk/skills/main/skills/mobile/clerk-android/references/custom.md
  - `references/prebuilt.md` — https://raw.githubusercontent.com/clerk/skills/main/skills/mobile/clerk-android/references/prebuilt.md

<!-- Verbatim upstream SKILL.md follows, YAML frontmatter included. -->

---
name: clerk-android
description: Implement Clerk authentication for native Android apps using Kotlin and
  Jetpack Compose with clerk-android source-guided patterns. Use for prebuilt AuthView/UserButton
  or custom API-driven auth flows. Do not use for Expo or React Native projects.
license: MIT
allowed-tools: WebFetch
metadata:
  author: clerk
  version: 1.2.0
compatibility: Requires Android Studio and clerk-android Gradle dependency
---

# Clerk Android (Native)

This skill implements Clerk in native Android projects by following current `clerk-android` SDK and docs patterns.

## Activation Rules

Activate this skill when either condition is true:
- The user explicitly asks for Android, Kotlin, Jetpack Compose, or native mobile Clerk implementation on Android.
- The project appears to be native Android (for example `build.gradle(.kts)` with Android plugins, `AndroidManifest.xml`, `app/src/main/java`, Compose UI files).

Do not activate this skill when either condition is true:
- The project is Expo.
- The project is React Native.

If Expo/React Native signals are present, route to the general setup skill instead.

## What Do You Need?

| Task | Reference |
|------|-----------|
| Prebuilt AuthView / UserButton (fastest) | references/prebuilt.md |
| Custom API-driven auth flows (full control) | references/custom.md |

## Quick Start

| Step | Action |
|------|--------|
| 1 | Confirm project type is native Android and not Expo/React Native |
| 2 | Determine flow type (`prebuilt` or `custom`) and load the matching reference file |
| 3 | Ensure a real Clerk publishable key exists (or ask developer) |
| 4 | Ensure correct Clerk artifacts are installed for the selected flow |
| 5 | Read official Android quickstart and verify required setup (Native API, min SDK/Java, manifest, initialization) |
| 6 | Inspect `clerk-android` source/sample patterns relevant to selected flow |
| 7 | Implement flow by following only the selected reference checklist |

## Decision Tree

```text
User asks for Clerk in Android/Kotlin
    |
    +-- Expo/React Native project detected?
    |     |
    |     +-- YES -> Do not use this skill
    |     |
    |     +-- NO -> Continue
    |
    +-- Existing auth UI detected?
    |     |
    |     +-- Prebuilt views detected -> Load references/prebuilt.md
    |     |
    |     +-- Custom flow detected -> Load references/custom.md
    |     |
    |     +-- New implementation -> Ask developer prebuilt/custom, then load matching reference
    |
    +-- Ensure publishable key and SDK initialization path
    |
    +-- Ensure correct Android artifacts are installed
    |
    +-- Verify quickstart prerequisites in project
    |
    +-- Implement using selected flow reference
```

## Flow References

After flow type is known, load exactly one:
- Prebuilt flow: [references/prebuilt.md](references/prebuilt.md)
- Custom flow: [references/custom.md](references/custom.md)

Do not blend the two references in a single implementation unless the developer explicitly asks for a hybrid approach.

## Interaction Contract

Before any implementation edits, the agent must have both:
- flow choice: `prebuilt` or `custom`
- a real Clerk publishable key

If either value is missing from the user request/context:
- ask the user for the missing value(s)
- pause and wait for the answer
- do not edit files or install dependencies yet

Only skip asking when the user has already explicitly provided the value in this conversation.

## Source-Driven Templates

Do not hardcode implementation examples in this skill. Inspect current `clerk-android` source/docs for the installed SDK version before implementing.

| Use Case | Source of Truth |
|----------|-----------------|
| SDK artifacts and dependency split (`clerk-android-api` vs `clerk-android-ui`) | `clerk-android` README and Android install docs |
| SDK initialization and publishable key wiring | Android quickstart and `source/api/.../Clerk.kt` |
| Prebuilt auth and profile behavior | `source/ui/.../AuthView.kt`, `source/ui/.../UserButton.kt`, and prebuilt sample |
| Custom auth sequencing and factor handling | `source/ui/auth/*`, `source/api/auth/*`, and custom-flows sample |
| Capability/feature gating from instance settings | `Clerk` public fields (for example `enabledFirstFactorAttributes`, `socialProviders`, `isGoogleOneTapEnabled`, `mfaIsEnabled`) and environment model source |
| Required Android setup checklist | Official Android quickstart (`/docs/android/getting-started/quickstart`) |

## Execution Gates (Do Not Skip)

1. No implementation edits before prerequisites
- Do not edit project files until flow type is confirmed and a valid publishable key is available.

2. Missing flow or key must trigger a question
- If flow choice is missing, explicitly ask: prebuilt views or custom flow.
- If publishable key is missing/placeholder/invalid, explicitly ask for a real key.
- Do not continue until both answers are provided.

3. Publishable key wiring mode is mandatory
- By default, wire the developer-provided key directly in `Clerk.initialize(...)`.
- Do not introduce secret-management indirection unless explicitly requested.

4. Artifact install policy is mandatory
- Prebuilt flow: use `clerk-android-ui` (includes API).
- Custom flow: use `clerk-android-api` unless prebuilt components are explicitly requested.
- If Clerk artifacts are missing, add the latest stable release available.

5. Android quickstart compliance is mandatory
- Verify Native API is enabled for the Clerk app.
- Verify Android requirements from quickstart are implemented in project (minimum SDK and Java target, manifest internet permission, app-level Clerk initialization).
- Verify app waits for SDK initialization (`Clerk.isInitialized`) before assuming auth-ready state.

6. Capability-driven behavior is mandatory
- Use Clerk runtime capability/settings state (for example enabled factors/social providers/MFA flags) to gate flow behavior.
- Do not hardcode factor assumptions that may conflict with dashboard configuration.

7. Reference-file discipline is mandatory
- Once flow is selected, follow only that flow reference file for implementation and verification.

8. Custom-flow structure parity is mandatory
- For `custom` flow, preserve multi-step auth progression and factor-specific handling (no single all-fields form by default).
- Keep UI, state orchestration, and Clerk API integration in separate modules.

9. Prebuilt preference is mandatory when selected
- For `prebuilt` flow, do not rebuild auth forms with custom API calls unless explicitly requested.
- Use `AuthView`/`UserButton` as default building blocks.

## Workflow

1. Detect native Android vs Expo/React Native.
2. If flow type is not explicitly provided, ask user for `prebuilt` or `custom`.
3. If publishable key is not explicitly provided, ask user for it.
4. Wait for both answers before changing files.
5. Load matching flow reference file.
6. Ensure `Clerk.initialize(...)` path and publishable key wiring are valid.
7. Ensure dependencies/artifacts match selected flow.
8. Review Android quickstart requirements and apply missing setup in project.
9. Implement using selected reference checklist.
10. Verify using selected reference checklist plus shared gates.

## Common Pitfalls

| Level | Issue | Prevention |
|-------|-------|------------|
| CRITICAL | Not asking for missing flow choice before implementation | Ask for `prebuilt` vs `custom` and wait before edits |
| CRITICAL | Not asking for missing publishable key before implementation | Ask for key and wait before edits |
| CRITICAL | Starting implementation before flow type is confirmed | Confirm flow first and load matching reference |
| CRITICAL | Skipping Android quickstart prerequisites | Verify and apply required setup from official Android quickstart |
| CRITICAL | Missing app-level `Clerk.initialize(...)` call | Initialize Clerk from `Application` startup path |
| HIGH | Wrong artifact for chosen flow | Prebuilt: `clerk-android-ui`; custom: `clerk-android-api` |
| HIGH | Rendering auth UI before SDK initialization completes | Gate UI with `Clerk.isInitialized` state |
| HIGH | Hardcoding auth factors/social providers | Drive behavior from Clerk runtime capability fields |
| HIGH | Using this skill for Expo/React Native | Detect and route away before implementation |

## See Also

- `clerk` skill for top-level Clerk routing
- `clerk-setup` skill for cross-framework quickstart setup
- `https://github.com/clerk/clerk-android`
- `https://clerk.com/docs/android/getting-started/quickstart`

<!-- chapter:end slug=clerk-android -->

---

<!-- chapter:begin slug=clerk-expo position=21 -->

## 21. clerk-expo

- **Source:** https://github.com/clerk/skills/blob/main/skills/mobile/clerk-expo/SKILL.md
- **Raw:** https://raw.githubusercontent.com/clerk/skills/main/skills/mobile/clerk-expo/SKILL.md
- **Markdown:** https://skillsdocs.com/clerk/skills/clerk-expo.md
- **Licence:** MIT — https://spdx.org/licenses/MIT.html

Bundled files (7), referenced from this skill's directory:
  - `evals/evals.json` — https://raw.githubusercontent.com/clerk/skills/main/skills/mobile/clerk-expo/evals/evals.json
  - `references/custom-flows.md` — https://raw.githubusercontent.com/clerk/skills/main/skills/mobile/clerk-expo/references/custom-flows.md
  - `references/prebuilt-components.md` — https://raw.githubusercontent.com/clerk/skills/main/skills/mobile/clerk-expo/references/prebuilt-components.md
  - `references/protected-routes.md` — https://raw.githubusercontent.com/clerk/skills/main/skills/mobile/clerk-expo/references/protected-routes.md
  - `references/recipes.md` — https://raw.githubusercontent.com/clerk/skills/main/skills/mobile/clerk-expo/references/recipes.md
  - `references/setup.md` — https://raw.githubusercontent.com/clerk/skills/main/skills/mobile/clerk-expo/references/setup.md
  - `references/sso-and-native-auth.md` — https://raw.githubusercontent.com/clerk/skills/main/skills/mobile/clerk-expo/references/sso-and-native-auth.md

<!-- Verbatim upstream SKILL.md follows, YAML frontmatter included. -->

---
name: clerk-expo
description: Add Clerk authentication to Expo and React Native apps using @clerk/expo.
  Use for Expo setup, prebuilt native components (AuthView, UserButton), custom sign-in/sign-up
  flows (email, password, SMS/phone OTP, MFA), OAuth/SSO, native Google/Apple sign-in,
  Expo Router protected routes, biometrics, and push notifications. Do not use for
  native Swift/iOS, native Android/Kotlin, or web-only framework projects.
license: MIT
allowed-tools: WebFetch
metadata:
  author: clerk
  version: 2.0.0
compatibility: Requires @clerk/expo v3.4+ (written against v3.6.x, July 2026). Expo SDK 53-56, React Native 0.75+.
---

# Clerk Expo (React Native)

Implement Clerk in Expo / React Native projects. This skill inlines verified patterns for the stable surface (provider, token cache, flows) and requires source inspection of the installed `@clerk/expo` package for anything volatile (component props, hook signatures).

## Activation Rules

Activate when either is true:
- The user asks for auth in an Expo or React Native app, or mentions `@clerk/expo`, `ClerkProvider`, Expo Router auth, or Clerk hooks in a native app.
- The project is Expo/React Native (`app.json` / `app.config.js`, `expo` in `package.json`, `metro.config.js`, `@clerk/expo` dependency).

Route away when:
- Native iOS/Swift project (`.xcodeproj`, `Package.swift`) → `clerk-swift`
- Native Android/Kotlin project (`build.gradle` without React Native) → `clerk-android`
- Web-only framework (Next.js, Remix, plain React, etc.) → the matching framework skill

## Intent Map

Match what the user asked for, then load the reference(s) listed. Load only what the task needs.

| User intent (examples) | Path | Reference |
|------------------------|------|-----------|
| "Add auth to my app" / "add sign-in with Clerk" | Prebuilt native components (default) | references/setup.md + references/prebuilt-components.md |
| "Add auth" but Expo Go / web / custom UI required | Custom flows | references/setup.md + references/custom-flows.md |
| "Add phone / SMS auth", "email OTP", "passwordless" | Custom flow, `phoneCode` / `emailCode` | references/custom-flows.md |
| "Sign in with Google/Apple/GitHub", "social login", "SSO" | Browser SSO or native buttons | references/sso-and-native-auth.md |
| "MFA / 2FA / TOTP", "forgot password", "email link" | Custom flow additions | references/custom-flows.md |
| "Protect routes/screens", "redirect if signed out" | Expo Router guards | references/protected-routes.md |
| "Show user profile", "org switching", "push notifications", "sign out", "call my backend" | App recipes | references/recipes.md |
| "Biometric login", "Face ID", "passkeys" | Device features | references/recipes.md |

## Default Path Decision

When the user says "add auth" without specifying UI:

1. **Default to prebuilt native components** (`AuthView` + `UserButton` from `@clerk/expo/native`). Fastest to working auth; UI is maintained by Clerk. Tell the developer they are in beta and require a development build.
2. **Fall back to custom flows** when any of these hold — say why when you switch:
   - The project must run in Expo Go (no dev build).
   - The app targets web (native components don't render on web).
   - The developer wants their own UI or a specific brand experience beyond theming.
3. If the developer has an existing auth UI, extend what's there — don't rip out custom flows to insert `AuthView` (or vice versa) without being asked.

Do not blend prebuilt components and custom flows for the same auth step (e.g. `AuthView` plus a custom password form). Blending is allowed only when the developer explicitly asks.

## Quick Workflow

1. Confirm project type (Expo/RN) and pick the path per the Intent Map / Default Path rules.
2. Follow references/setup.md: install, env key, provider, token cache, config plugin, build type.
3. Verify dashboard prerequisites (Gate 2 and Gate 3 below).
4. Implement from the selected reference only.
5. Verify by building, not just by writing:
   - Run the project's typecheck (`npx tsc --noEmit` or equivalent).
   - Build and launch: `npx expo run:ios` / `run:android` for native features, `npx expo start` for Expo Go flows. If the build fails, fix and rebuild iteratively — build errors against the installed SDK are the ground truth when this skill and the SDK disagree. After ~5 failed fix attempts, stop and ask the developer how to proceed instead of thrashing.
   - Walk the developer through one real sign-in, then confirm the session survives an app restart (token cache working).

## Execution Gates (Do Not Skip)

1. **Publishable key** — Read from `process.env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY` (`.env` file). Never `NEXT_PUBLIC_`, never hardcoded. If no key exists, ask the developer for one (or run `npx clerk@latest init --framework expo`, which installs the SDK and writes the env file) and wait before editing files.
2. **Native API dashboard toggle** — Clerk's Native API must be enabled for the instance: Clerk Dashboard → **Native applications** (`https://dashboard.clerk.com/~/native-applications`). Tell the developer to verify this during setup; it is required for any native integration.
3. **Factor availability** — Before implementing a specific strategy (SMS, email code, social provider), confirm it's enabled for the instance. Derive the Frontend API URL from the publishable key (base64-decode the middle segment) and fetch `<frontendApiUrl>/v1/environment?_is_native=true`, or ask the developer to check the dashboard (**User & authentication**). SMS in particular is instance-configuration-dependent — code written for a disabled factor fails at runtime, not build time.
4. **Current custom-flows API only** — `useSignIn()` / `useSignUp()` from `@clerk/expo` (v3.4+) return `{ signIn, errors, fetchStatus }` and use method-based flows: `signIn.password()`, `signIn.phoneCode.sendCode()`, `signIn.finalize()`. Never generate the legacy pattern: destructuring `isLoaded`/`setActive` from `useSignIn()`/`useSignUp()` (the current hooks don't return them), or `signIn.create()` chained with `prepareFirstFactor()`/`attemptFirstFactor()` + `setActive({ session })`. That pattern lives at `@clerk/expo/legacy` and is only for maintaining existing legacy code, never for new work. Scope notes: `isLoaded` from `useAuth()`/`useUser()` is current API and required in guards; `signIn.create()` itself still exists for advanced cases — prefer the factor-specific methods.
5. **`useSSO()`, never `useOAuth()`** — `useOAuth` is deprecated. Note the asymmetry: `startSSOFlow()` still returns `{ createdSessionId, setActive }` and requires `setActive({ session: createdSessionId })` — SSO does not use `finalize()`.
6. **Token cache** — `tokenCache` from `@clerk/expo/token-cache` on `ClerkProvider`. Never use `expo-secure-store` directly for session tokens, never AsyncStorage.
7. **`resourceCache`, never `secureStore`** — if offline resource caching comes up, `@clerk/expo/secure-store` is deprecated; use `resourceCache` from `@clerk/expo/resource-cache`.
8. **Build-type gating** — Native components (`@clerk/expo/native`) and native hooks (`useSignInWithGoogle`, `useSignInWithApple`, `useLocalCredentials`) require a development build (`npx expo run:ios` / `run:android`), not Expo Go, and don't exist on web. For web targets use `@clerk/expo/web` components or custom flows. State the build requirement before implementing a native-only feature.
9. **Combined sign-in-or-up default** — one combined flow unless the developer asks for separate sign-in and sign-up screens.
10. **Bot protection** — custom sign-up screens must render `<View nativeID="clerk-captcha" />`; Clerk's bot protection is on by default and needs this mount point.
11. **Source verification for volatile surfaces** — before using native component props or native hook options, confirm against the installed package: `node_modules/@clerk/expo/dist/native/*.d.ts` and `package.json` `exports`. The installed version wins over this skill if they disagree.
12. **Freshness gate** — this skill was verified against `@clerk/expo` 3.6.x. Check the installed version (`node_modules/@clerk/expo/package.json`). If it is a newer minor or major, treat this skill's code snippets as suspect: re-verify against the docs URL cited next to each snippet (every reference section carries one) or the installed `.d.ts` before using them. If it is older than 3.4, the method-based custom-flows API may not exist — offer an upgrade instead of writing legacy code.

## Version Notes (v3.5–v3.6, June 2026)

- Minimum React Native raised to **0.75** in v3.5.0 (iOS SDK now links via SPM podspec). Peer range: `expo >=53 <57`.
- Native components matured: iOS moved to Expo Modules; native↔JS session sync is automatic and bidirectional — never call `setActive()` after native-component auth.
- The config plugin accepts a `theme` JSON file for native component styling (see references/prebuilt-components.md).
- Native Google sign-in will move to a separate `@clerk/expo-google-signin` package in the next major (the `@clerk/expo/google` import keeps working in v3; a dev warning announces the migration). Don't preinstall the new package on v3.

## Common Pitfalls

| Level | Issue | Prevention |
|-------|-------|------------|
| CRITICAL | Generating legacy custom-flow code (`signIn.create` + `prepareFirstFactor` + `setActive`) | Use the current method-based API (Gate 4) |
| CRITICAL | Using `useOAuth()` | Use `useSSO()` (Gate 5) |
| CRITICAL | Implementing SMS/social auth without checking the factor is enabled | Check environment/dashboard first (Gate 3) |
| CRITICAL | Native components targeted at Expo Go or web | Require a dev build; offer custom flows otherwise (Gate 8) |
| CRITICAL | Sign-up screen missing `<View nativeID="clerk-captcha" />` | Always include it (Gate 10) |
| HIGH | `NEXT_PUBLIC_` env prefix, or env var read inside `node_modules` | `EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY`, passed explicitly to `ClerkProvider` |
| HIGH | Session lost on restart | `tokenCache` from `@clerk/expo/token-cache` on the provider |
| HIGH | Calling `setActive()` after `AuthView` / `UserButton` auth | Native components sync sessions automatically |
| HIGH | Pairing `AuthView` with `useSignInWithGoogle`/`useSignInWithApple` | `AuthView` renders enabled social providers itself |
| HIGH | Calling `WebBrowser.maybeCompleteAuthSession()` manually | `ClerkProvider` handles it |
| HIGH | Splitting sign-in / sign-up without being asked | Combined flow by default (Gate 9) |
| MEDIUM | Missing `isLoaded` check before `isSignedIn` in guards | Always gate on `isLoaded` first |
| MEDIUM | Using `yalc`/`pnpm link` for local `@clerk/expo` development | Use Verdaccio or pkg.pr.new |

## See Also

- `clerk` — top-level router
- `clerk-swift` / `clerk-android` — native mobile SDKs
- `clerk-orgs`, `clerk-billing`, `clerk-webhooks` — feature skills (hooks work the same in Expo)
- Installed package source: `node_modules/@clerk/expo/`
- https://clerk.com/docs/getting-started/quickstart (Expo SDK tab)
- https://clerk.com/docs/reference/expo/overview
- https://github.com/clerk/clerk-expo-quickstart — three official example apps: JS-only (Expo Go), JS + native sign-in buttons, native components

<!-- chapter:end slug=clerk-expo -->

---

<!-- chapter:begin slug=clerk-swift position=22 -->

## 22. clerk-swift

- **Source:** https://github.com/clerk/skills/blob/main/skills/mobile/clerk-swift/SKILL.md
- **Raw:** https://raw.githubusercontent.com/clerk/skills/main/skills/mobile/clerk-swift/SKILL.md
- **Markdown:** https://skillsdocs.com/clerk/skills/clerk-swift.md
- **Licence:** MIT — https://spdx.org/licenses/MIT.html

Bundled files (2), referenced from this skill's directory:
  - `references/custom.md` — https://raw.githubusercontent.com/clerk/skills/main/skills/mobile/clerk-swift/references/custom.md
  - `references/prebuilt.md` — https://raw.githubusercontent.com/clerk/skills/main/skills/mobile/clerk-swift/references/prebuilt.md

<!-- Verbatim upstream SKILL.md follows, YAML frontmatter included. -->

---
name: clerk-swift
description: Implement Clerk authentication for native Swift and iOS apps using ClerkKit
  and ClerkKitUI source-guided patterns. Use for prebuilt AuthView or custom native
  flows. Do not use for Expo or React Native projects.
license: MIT
allowed-tools: WebFetch
metadata:
  author: clerk
  version: 1.2.0
compatibility: Requires Xcode and ClerkKit Swift package
---

# Clerk Swift (Native iOS)

This skill implements Clerk in native Swift/iOS projects by reading installed package source and mirroring current ClerkKit/ClerkKitUI behavior.

## Activation Rules

Activate this skill when either condition is true:
- The user explicitly asks for Swift, SwiftUI, UIKit, or native iOS Clerk implementation.
- The project appears to be native iOS/Swift (for example `.xcodeproj`, `.xcworkspace`, `Package.swift`, Swift targets).

Do not activate this skill when either condition is true:
- The project is Expo.
- The project is React Native.

If Expo/React Native signals are present, route to the general setup skill instead of this one.

## What Do You Need?

| Task | Reference |
|------|-----------|
| Prebuilt AuthView / UserButton (fastest) | references/prebuilt.md |
| Custom API-driven auth flows (full control) | references/custom.md |

## Quick Start

| Step | Action |
|------|--------|
| 1 | Confirm project type is native Swift/iOS and not Expo/React Native |
| 2 | Determine flow type (`prebuilt` or `custom`) and load the matching reference file |
| 3 | Ensure a valid publishable key exists (or ask developer) and wire it directly in configuration |
| 4 | Ensure `clerk-ios` package is installed with correct products for selected flow; if missing, install latest available release using an up-to-next-major version requirement |
| 5 | Inspect installed `ClerkKitUI` source to identify which `Environment` fields drive feature/step gating |
| 6 | Call `/v1/environment` after step 5 and evaluate only against the `ClerkKitUI`-aligned field map |
| 7 | Find the iOS quickstart URL in the installed `clerk-ios` package README, append `.md`, then visit and read the markdown URL to compile a required-step checklist |
| 8 | Verify and complete all quickstart prerequisites for this project (for example associated domains and required capabilities) |
| 9 | Implement flow by following only the selected reference checklist |

## Decision Tree

```text
User asks for Clerk in Swift/iOS
    |
    +-- Expo/React Native project detected?
    |     |
    |     +-- YES -> Do not use this skill
    |     |
    |     +-- NO -> Continue
    |
    +-- Existing auth UI detected?
    |     |
    |     +-- Prebuilt views detected -> Load references/prebuilt.md
    |     |
    |     +-- Custom flow detected -> Load references/custom.md
    |     |
    |     +-- New implementation -> Ask developer prebuilt/custom, then load matching reference
    |
    +-- Ensure publishable key and direct wiring
    |
    +-- Ensure clerk-ios is installed
    |
    +-- Inspect ClerkKitUI Environment field usage
    |
    +-- Call /v1/environment using that field map
    |
    +-- Visit/read quickstart URL from installed clerk-ios package README
    |
    +-- Verify all quickstart prerequisites are completed
    |
    +-- Implement using selected flow reference
```

## Flow References

After flow type is known, load exactly one:
- Prebuilt flow: [references/prebuilt.md](references/prebuilt.md)
- Custom flow: [references/custom.md](references/custom.md)

Do not blend the two references in a single implementation unless the developer explicitly asks for a hybrid approach.

## Interaction Contract

Before any implementation edits, the agent must have both:
- flow choice: `prebuilt` or `custom`
- a real Clerk publishable key

If either value is missing from the user request/context:
- ask the user for the missing value(s)
- pause and wait for the answer
- do not edit files or install dependencies yet

Only skip asking when the user has already explicitly provided the value in this conversation.

## Source-Driven Templates

Do not hardcode implementation examples in this skill. Inspect current installed package source before implementing.

| Use Case | Source of Truth in Installed Package |
|----------|--------------------------------------|
| SDK package products, platform support, and dependency constraints | Package manifest and target product definitions for `ClerkKit` and `ClerkKitUI`, plus package requirement style (up-to-next-major) |
| Publishable key validation and frontend API derivation | Clerk configuration logic (search symbols: `configure(publishableKey`, `frontendApiUrl`, `invalidPublishableKeyFormat`) |
| Environment endpoint contract and field semantics | Environment request path and request construction plus `ClerkKitUI` `Environment` field usage for gating (search symbols: `/v1/environment`, `Request<Clerk.Environment>`, `Environment` usage in `ClerkKitUI`) |
| iOS quickstart requirements | Installed `clerk-ios` package README quickstart link plus the visited/read quickstart page checklist steps (including project setup prerequisites) |
| Native Sign in with Apple implementation | Apple capability and native sign-in behavior in selected flow reference |

## Execution Gates (Do Not Skip)

1. No implementation edits before prerequisites
- Do not edit project files until flow type is confirmed and a valid publishable key is available.

2. Missing flow or key must trigger a question
- If flow choice is missing, explicitly ask: prebuilt views or custom flow.
- If publishable key is missing/placeholder/invalid, explicitly ask for a real key.
- Do not continue until both answers are provided.

3. Publishable key wiring mode is mandatory
- Use the developer-provided publishable key plainly in app configuration passed to `Clerk.configure`.
- Do not introduce plist/local-secrets/env-file/build-setting indirection unless explicitly requested.

4. Package install/version policy is mandatory
- If `clerk-ios` is not installed, add it using the latest available release with an up-to-next-major requirement.
- Do not pin an exact package version unless the developer explicitly asks for exact pinning.

5. ClerkKitUI Environment field inspection is mandatory
- After package install, inspect installed `ClerkKitUI` source and identify which `Environment` fields gate auth behavior for the selected flow.
- Build an agent-internal field map before any `/v1/environment` call.

6. Environment call is mandatory (both flows)
- Make a direct HTTP call to `/v1/environment` only after package install and step 5 field-map inspection.
- Pass the response into the selected reference workflow using the `ClerkKitUI`-aligned field map:
  - prebuilt: use it to determine whether Apple is enabled and capability changes are needed
  - custom: perform full normalization/matrix handling as agent-internal analysis only (never persist matrix artifacts in project code)

7. Reference-file discipline is mandatory
- Once flow is selected, follow only that flow reference file for implementation and verification.

8. Quickstart compliance is mandatory
- Find the iOS quickstart URL in the installed `clerk-ios` package README, append `.md`, then visit and read that markdown URL.
- Audit the project against all quickstart setup steps before finishing.
- If required quickstart setup is missing, implement it before completing the task.
- This includes adding any missing Associated Domains entries and any other required app capabilities from the quickstart.
- Explicitly execute the quickstart step `Add associated domain capability` (`https://clerk.com/docs/ios/getting-started/quickstart#add-associated-domain-capability`) and ensure the associated-domain entry matches quickstart requirements (`webcredentials:{YOUR_FRONTEND_API_URL}`).

9. Custom-flow AuthView structure parity is mandatory
- For `custom` flow, layout and flow structure must remain materially close to ClerkKitUI `AuthView` defaults.
- If the developer did not explicitly request a different UX, do not introduce major structural/layout deviations from `AuthView`.
- If unsure/confused about custom sequencing, gating, or `Environment` usage/semantics, defer to installed `ClerkKitUI` behavior and mirror it.

## Workflow

1. Detect native iOS/Swift vs Expo/React Native.
2. If flow type is not explicitly provided, ask user for `prebuilt` or `custom`.
3. If publishable key is not explicitly provided, ask user for it.
4. Wait for both answers before changing files.
5. Load matching flow reference file.
6. Ensure publishable key is valid and directly wired in `Clerk.configure`.
7. Ensure package install/products match selected flow and package requirement follows latest up-to-next-major policy when newly added.
8. Inspect installed `ClerkKitUI` source to map `Environment` fields used for gating/required behavior in the selected flow.
9. Call `/v1/environment` and interpret response through the step 8 field map.
10. Find iOS quickstart URL from installed `clerk-ios` package README, append `.md`, then visit and read it.
11. Build quickstart checklist from the visited markdown quickstart, detect missing required setup, and apply the missing setup in the current project.
12. Ensure the quickstart associated-domain capability step is fully applied (`webcredentials:{YOUR_FRONTEND_API_URL}` when missing).
13. Implement using selected reference checklist.
14. Verify using selected reference checklist plus shared gates.

## Common Pitfalls

| Level | Issue | Prevention |
|-------|-------|------------|
| CRITICAL | Not asking for missing flow choice before implementation | Ask for `prebuilt` vs `custom` and wait before edits |
| CRITICAL | Not asking for missing publishable key before implementation | Ask for key and wait before edits |
| CRITICAL | Starting implementation before flow type is confirmed | Confirm flow first and load matching reference |
| CRITICAL | Using plist/local/env indirection for publishable key without request | Wire key directly in configuration by default |
| CRITICAL | Skipping `/v1/environment` call before implementation | Always call environment endpoint for both prebuilt and custom flows |
| CRITICAL | Calling `/v1/environment` before package install + ClerkKitUI `Environment` field inspection | Install `clerk-ios` first, inspect ClerkKitUI `Environment` usage, then call endpoint |
| HIGH | Installing `clerk-ios` with exact/stale version by default | If missing, install latest available release using up-to-next-major requirement |
| CRITICAL | Skipping quickstart prerequisite audit | Visit/read quickstart URL from installed `clerk-ios` package README and verify all required setup steps are completed |
| CRITICAL | Detecting missing quickstart capabilities/domains but not applying them | Add all missing required quickstart capabilities and Associated Domains before completing |
| CRITICAL | Skipping quickstart associated-domain capability step | Execute quickstart `Add associated domain capability` and ensure `webcredentials:{YOUR_FRONTEND_API_URL}` is present |
| CRITICAL | Writing capability/required-field matrices into app code | Keep matrices agent-internal and only apply resulting behavior in UI/auth flow code |
| CRITICAL | Custom flow layout diverges from `AuthView` without explicit request | Keep custom screens materially close to `AuthView` structure and step composition by default |
| CRITICAL | Collapsing custom auth into a single all-fields screen | Follow `AuthView`-style multi-step progression and step-specific field collection |
| CRITICAL | Guessing custom sequencing/gating/`Environment` usage when uncertain | Reference installed `ClerkKitUI` behavior and mirror it for final implementation |
| HIGH | Using this skill for Expo/React Native | Detect and route away before implementation |

## See Also

- `clerk` skill for top-level Clerk routing
- `clerk-setup` skill for non-native or cross-framework setup
- installed `clerk-ios` package `README.md` (source for current iOS quickstart link)
- `https://github.com/clerk/clerk-ios`

<!-- chapter:end slug=clerk-swift -->
