11ai Clerk cheatsheet
Version baseline: Clerk Core 3 and Clerk API 2026-05-12; each Clerk SDK has its own compatible semver (for example @clerk/nextjs v7.5.2 or newer). Inspect the installed SDK and the API-version compatibility table before editing.
A lookup surface for Clerk. Give the call, name what it changes and whether it is server-only, and stop. For wiring flows, changing users, or diagnosing a failure, hand off to the matching operation skill.
Keys and their scope
| Value | Scope |
|---|---|
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY |
Browser. Safe to expose |
CLERK_SECRET_KEY |
Server only. Full API access to the instance |
CLERK_WEBHOOK_SIGNING_SECRET |
Server only. Verifies incoming events |
pk_test and sk_test are a development instance; pk_live and sk_live are production. They are separate instances with separate users, organizations, and webhook endpoints — an object in one does not exist in the other.
Components
import {
ClerkProvider, Show, SignIn, SignUp, SignInButton,
UserButton, UserProfile, OrganizationSwitcher, OrganizationProfile,
} from "@clerk/nextjs"
<ClerkProvider>{children}</ClerkProvider>
<Show when="signed-in"><UserButton /></Show>
<Show when="signed-out"><SignInButton /></Show>
<Show when={{ permission: "org:billing:manage" }} fallback={<NoAccess />}>
<BillingPanel />
</Show>
Core 3's Show controls what renders. It is not access control — anything it hides is still reachable by calling the route directly.
Middleware and route protection
// middleware.ts
import { clerkMiddleware, createRouteMatcher } from "@clerk/nextjs/server"
const isPublic = createRouteMatcher(["/", "/pricing", "/sign-in(.*)", "/sign-up(.*)"])
export default clerkMiddleware(async (auth, request) => {
if (!isPublic(request)) await auth.protect()
})
export const config = {
matcher: ["/((?!_next|.*\\..*).*)", "/(api|trpc)(.*)"],
}
Protecting everything except a listed set is safer than listing protected routes, because a new page is protected by default.
Server helpers
import { auth, currentUser, clerkClient } from "@clerk/nextjs/server"
const { userId, orgId, orgRole, sessionClaims, has } = await auth()
const { userId } = await auth.protect()
const user = await currentUser()
if (!has({ permission: "org:invoices:read" })) return forbidden()
auth() reads the verified session. currentUser() makes an API call, so prefer auth() when the id is enough.
Client hooks
"use client"
import { useAuth, useUser, useSession, useOrganization, useOrganizationList, useClerk } from "@clerk/nextjs"
const { isLoaded, isSignedIn, userId, orgId, getToken } = useAuth()
const { user } = useUser()
const { organization, membership } = useOrganization()
const { signOut, openSignIn } = useClerk()
const token = await getToken()
Check isLoaded before reading anything, or the first render sees a signed-out state for a signed-in user.
Backend API
const client = await clerkClient()
await client.users.getUser(userId)
await client.users.getUserList({ emailAddress: ["a@example.com"], limit: 10 })
await client.users.updateUser(userId, { firstName: "Ada" })
await client.users.updateUserMetadata(userId, { publicMetadata: { plan: "pro" } })
await client.users.deleteUser(userId)
await client.users.banUser(userId)
await client.organizations.createOrganization({ name: "Acme", createdBy: userId })
await client.organizations.getOrganizationList({ limit: 10 })
await client.organizations.createOrganizationMembership({ organizationId, userId, role: "org:admin" })
await client.organizations.updateOrganizationMembership({ organizationId, userId, role: "org:member" })
await client.organizations.deleteOrganizationMembership({ organizationId, userId })
await client.organizations.deleteOrganization(organizationId)
await client.invitations.createInvitation({ emailAddress: "a@example.com" })
await client.sessions.revokeSession(sessionId)
Every one of these needs the secret key and must run server-side only.
Metadata
| Field | Readable by | Writable by |
|---|---|---|
publicMetadata |
anyone, including the browser | backend only |
privateMetadata |
backend only | backend only |
unsafeMetadata |
anyone | the user |
Never read a role, plan, or entitlement from unsafeMetadata. The user can set it to anything.
Webhooks
import { verifyWebhook } from "@clerk/nextjs/webhooks"
const event = await verifyWebhook(request)
Verify against the raw body before parsing. Common types: user.created, user.updated, user.deleted, session.created, organization.created, organizationMembership.created, organizationMembership.deleted.
Answer format
Lead with the call. Add one line on what it changes, whether it is server-only, and which instance it acts on. Name the operation skill when the task goes beyond a lookup: sign-in flows to 11ai-operator-clerk-core-3-authentication, route protection to 11ai-operator-clerk-core-3-sessions, user records to 11ai-operator-clerk-core-3-users, organizations to 11ai-operator-clerk-core-3-organizations, events to 11ai-operator-clerk-core-3-webhooks, and failures to 11ai-operator-clerk-core-3-troubleshooting.