Skip to content

Clerk Cheat Sheet

Overview

Clerk is a developer-first authentication and user management platform that provides drop-in UI components for sign-up, sign-in, user profiles, and organization management. It handles the entire authentication lifecycle including email/password, social login, multi-factor authentication, magic links, passkeys, and SMS verification out of the box.

Clerk offers first-class support for React, Next.js, Remix, Expo, and JavaScript, with backend SDKs for Node.js, Python, Go, and Ruby. Its pre-built components are fully customizable and handle edge cases like session management, device tracking, and account linking, while its backend APIs provide full control over user data and authentication flows.

Installation

# Next.js
npm install @clerk/nextjs

# React
npm install @clerk/clerk-react

# Remix
npm install @clerk/remix

# Express.js
npm install @clerk/express

# Node.js backend
npm install @clerk/backend

# Expo (React Native)
npm install @clerk/clerk-expo

Next.js Setup

// app/layout.tsx
import { ClerkProvider } from "@clerk/nextjs";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <ClerkProvider>
      <html lang="en">
        <body>{children}</body>
      </html>
    </ClerkProvider>
  );
}
// middleware.ts
import { clerkMiddleware, createRouteMatcher } from "@clerk/nextjs/server";

const isProtectedRoute = createRouteMatcher([
  "/dashboard(.*)",
  "/api/private(.*)",
]);

export default clerkMiddleware(async (auth, req) => {
  if (isProtectedRoute(req)) {
    await auth.protect();
  }
});

export const config = {
  matcher: ["/((?!.*\\..*|_next).*)", "/", "/(api|trpc)(.*)"],
};

Pre-Built Components

import {
  SignIn,
  SignUp,
  UserButton,
  UserProfile,
  OrganizationSwitcher,
  SignedIn,
  SignedOut,
} from "@clerk/nextjs";

// Sign-in page
export default function SignInPage() {
  return <SignIn />;
}

// Sign-up page
export default function SignUpPage() {
  return <SignUp />;
}

// Navigation with auth state
function Header() {
  return (
    <nav>
      <SignedIn>
        <UserButton afterSignOutUrl="/" />
        <OrganizationSwitcher />
      </SignedIn>
      <SignedOut>
        <a href="/sign-in">Sign In</a>
      </SignedOut>
    </nav>
  );
}

// Full user profile page
export default function ProfilePage() {
  return <UserProfile />;
}

Hooks and Client-Side Auth

import { useAuth, useUser, useSignIn, useOrganization } from "@clerk/nextjs";

function Dashboard() {
  const { isLoaded, userId, sessionId, getToken } = useAuth();
  const { user } = useUser();

  if (!isLoaded) return <div>Loading...</div>;
  if (!userId) return <div>Not signed in</div>;

  const callApi = async () => {
    const token = await getToken();
    const res = await fetch("/api/data", {
      headers: { Authorization: `Bearer ${token}` },
    });
    return res.json();
  };

  return (
    <div>
      <h1>Welcome, {user?.firstName}</h1>
      <p>Email: {user?.primaryEmailAddress?.emailAddress}</p>
      <button onClick={callApi}>Fetch Data</button>
    </div>
  );
}

Server-Side Auth (Next.js App Router)

// app/api/route.ts
import { auth, currentUser } from "@clerk/nextjs/server";
import { NextResponse } from "next/server";

export async function GET() {
  const { userId } = await auth();
  if (!userId) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }

  const user = await currentUser();
  return NextResponse.json({
    userId,
    email: user?.emailAddresses[0]?.emailAddress,
  });
}
// Server component
import { auth, currentUser } from "@clerk/nextjs/server";

export default async function ServerPage() {
  const { userId } = await auth();
  const user = await currentUser();

  return (
    <div>
      <p>User ID: {userId}</p>
      <p>Name: {user?.firstName} {user?.lastName}</p>
    </div>
  );
}

Backend API (Node.js)

import { createClerkClient } from "@clerk/backend";

const clerk = createClerkClient({
  secretKey: process.env.CLERK_SECRET_KEY,
});

// List users
const users = await clerk.users.getUserList({
  limit: 10,
  orderBy: "-created_at",
});

// Get single user
const user = await clerk.users.getUser("user_abc123");

// Update user metadata
await clerk.users.updateUserMetadata("user_abc123", {
  publicMetadata: { role: "admin" },
  privateMetadata: { stripeCustomerId: "cus_xxx" },
});

// Create user
await clerk.users.createUser({
  emailAddress: ["user@example.com"],
  password: "SecureP@ss123!",
  firstName: "John",
  lastName: "Doe",
});

// Delete user
await clerk.users.deleteUser("user_abc123");

Organizations (Multi-Tenancy)

import { useOrganization, useOrganizationList } from "@clerk/nextjs";

function OrgDashboard() {
  const { organization, membership } = useOrganization();
  const { createOrganization, setActive, userMemberships } = useOrganizationList({
    userMemberships: { infinite: true },
  });

  const handleCreate = async () => {
    const org = await createOrganization({ name: "Acme Corp" });
    await setActive({ organization: org.id });
  };

  return (
    <div>
      <p>Current org: {organization?.name}</p>
      <p>Role: {membership?.role}</p>
      <button onClick={handleCreate}>Create Org</button>
    </div>
  );
}

Webhooks

// app/api/webhooks/clerk/route.ts
import { Webhook } from "svix";
import { headers } from "next/headers";

export async function POST(req: Request) {
  const WEBHOOK_SECRET = process.env.CLERK_WEBHOOK_SECRET;
  const headerPayload = await headers();
  const svix_id = headerPayload.get("svix-id");
  const svix_timestamp = headerPayload.get("svix-timestamp");
  const svix_signature = headerPayload.get("svix-signature");

  const body = await req.text();
  const wh = new Webhook(WEBHOOK_SECRET);
  const evt = wh.verify(body, {
    "svix-id": svix_id,
    "svix-timestamp": svix_timestamp,
    "svix-signature": svix_signature,
  });

  const { type, data } = evt;

  switch (type) {
    case "user.created":
      await handleUserCreated(data);
      break;
    case "user.updated":
      await handleUserUpdated(data);
      break;
    case "session.created":
      await handleSessionCreated(data);
      break;
  }

  return new Response("OK", { status: 200 });
}

Advanced Usage

Custom Sign-In Flow

import { useSignIn } from "@clerk/nextjs";

function CustomSignIn() {
  const { signIn, setActive } = useSignIn();

  const handleSubmit = async (email: string, password: string) => {
    const result = await signIn.create({
      identifier: email,
      password,
    });

    if (result.status === "complete") {
      await setActive({ session: result.createdSessionId });
    }
  };
}

JWT Templates

// Get custom JWT for external services
const { getToken } = useAuth();

// Use a JWT template configured in Clerk dashboard
const supabaseToken = await getToken({ template: "supabase" });
const hasuraToken = await getToken({ template: "hasura" });

Configuration

# .env.local
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...
CLERK_SECRET_KEY=sk_test_...
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
CLERK_WEBHOOK_SECRET=whsec_...

Troubleshooting

IssueSolution
Middleware not protecting routesVerify matcher config; ensure clerkMiddleware is exported as default
Hydration mismatchWrap auth-dependent content in SignedIn/SignedOut components
Token not refreshingCheck getToken() is called fresh before each API request
Webhook verification failsEnsure raw body is passed to Svix; check CLERK_WEBHOOK_SECRET
Organization not switchingCall setActive after creating or selecting an organization
CORS errors on API routesClerk handles CORS; ensure middleware runs on API routes
Social login redirect failsVerify redirect URLs in Clerk dashboard match your app
Metadata not updatingUse publicMetadata for client-readable data, privateMetadata for server-only