# Clerk (with Next.js) (/docs/guides/authentication/clerk/nextjs) > For the complete Prisma documentation index, see [llms.txt](https://www.prisma.io/docs/llms.txt). A markdown version of any docs page is available by appending `.md` to its URL. Add Clerk authentication to a Next.js app, sync users to Prisma Postgres with a webhook, build a posts API, and deploy to Prisma Compute. Location: Guides > Authentication > Clerk > Clerk (with Next.js) ## Introduction [#introduction] [Clerk](https://clerk.com/) is an auth provider: it handles sign-up, sign-in, user management, and webhooks. In this guide you wire Clerk into a new [Next.js](https://nextjs.org/) app, keep a copy of each Clerk user in a [Prisma Postgres](https://www.prisma.io/docs/postgres) database, expose a small posts API that only signed-in users can call, and deploy the app to [Prisma Compute](https://www.prisma.io/docs/compute). Every command below was run end to end against a live database. > [!NOTE] > Using Prisma ORM 7? > > Prisma ORM 8 is a [release candidate](https://www.prisma.io/docs/orm/release-status). Prisma ORM 7 remains fully supported; the Prisma ORM 7 version of this guide is at [/guides/v7/authentication/clerk/nextjs](https://www.prisma.io/docs/guides/v7/authentication/clerk/nextjs). ## Prerequisites [#prerequisites] * [Node.js](https://nodejs.org) 22.18 or newer on the 22 line, 24.11 or newer on the 24 line, or 26 or newer (24 recommended) * A PostgreSQL connection string, or nothing at all: `npx create-db@latest` creates a [Prisma Postgres](https://www.prisma.io/docs/postgres) database for you * A [Clerk account](https://dashboard.clerk.com/sign-up) * [ngrok](https://ngrok.com/docs/getting-started) installed and signed in (`ngrok config add-authtoken`), or any other tool that gives your local server a public URL, so Clerk can reach your webhook while you develop ## 1. Scaffold the project [#1-scaffold-the-project] Create a Next.js app with Prisma ORM already wired in. The `next` template generates the app, installs dependencies, and sets up the files that Prisma Compute deploys later. `--no-deploy` skips the scaffold's offer to deploy right away; you deploy in step 8: #### bun ```bash bun create prisma@latest clerk-nextjs-prisma --template next --provider postgres --no-deploy cd clerk-nextjs-prisma ``` #### pnpm ```bash pnpm create prisma@latest clerk-nextjs-prisma --template next --provider postgres --no-deploy cd clerk-nextjs-prisma ``` #### yarn ```bash yarn create prisma@latest clerk-nextjs-prisma --template next --provider postgres --no-deploy cd clerk-nextjs-prisma ``` #### npm ```bash npm create prisma@latest -- clerk-nextjs-prisma --template next --provider postgres --no-deploy cd clerk-nextjs-prisma ``` The scaffold asks three questions, and this guide answers them as follows: write your models in Prisma Schema Language, the `.prisma` syntax you know from Prisma ORM 7 (Prisma ORM 8 calls the file the contract, and step 2 edits it); use npm, to match the commands on this page; and install the Prisma agent skills for the coding agents you use, or none. Next, create a Prisma Postgres database with `create-db`. It needs no account and prints a connection string and a claim URL; the database is deleted after 24 hours unless you open the claim URL, which adds it to your Prisma Data Platform account: #### bun ```bash bunx create-db@latest ``` #### pnpm ```bash pnpm dlx create-db@latest ``` #### yarn ```bash yarn dlx create-db@latest ``` #### npm ```bash npx create-db@latest ``` Export the connection string in the shell you work in. The Prisma CLI, and the `npm run` scripts in `package.json` that call it, read the environment variable, not `.env`: ```bash export DATABASE_URL="" ``` ## 2. Define the contract [#2-define-the-contract] ### 2.1. Replace the models [#21-replace-the-models] The contract holds only models: the database connection lives in `prisma.config.ts`, and there are no `datasource` or `generator` blocks. The template ships demo models with seeded users. Replace the whole file with a `User` model that stores the Clerk user id next to the email, and a `Post` model that belongs to a user. Keep the `// use prisma-8` line: every Prisma ORM 8 contract file starts with it. In Prisma ORM 8, `DateTime` reads and writes `Temporal.Instant` objects. `TimestamptzString` is the same timestamp with time zone column read and written as a plain string, which is enough for a timestamp you only sort by. ```prisma title="src/prisma/contract.prisma" // use prisma-8 model User { id Int @id @default(autoincrement()) clerkId String @unique email String @unique name String? posts Post[] } model Post { id Int @id @default(autoincrement()) title String content String? author User @relation(fields: [authorId], references: [id]) authorId Int createdAt TimestamptzString @default(now()) } ``` The template's seed helpers refer to fields that no longer exist, so delete them: ```bash rm src/prisma/seed.ts src/prisma/users.ts ``` ### 2.2. Emit the contract [#22-emit-the-contract] Emitting the contract replaces `prisma generate`: it writes the types your queries are checked against into `src/prisma/`: #### bun ```bash bun run contract:emit ``` #### pnpm ```bash pnpm run contract:emit ``` #### yarn ```bash yarn contract:emit ``` #### npm ```bash npm run contract:emit ``` ### 2.3. Plan the migration [#23-plan-the-migration] The template also ships a migration for its demo models. Delete it and plan one that creates your models. `migration plan` writes the migration file without touching the database; the deploy in step 8 builds the production database by replaying `migrations/`, so it needs this file: ```bash rm -rf migrations ``` #### bun ```bash bunx prisma migration plan --name init ``` #### pnpm ```bash pnpm prisma migration plan --name init ``` #### yarn ```bash yarn prisma migration plan --name init ``` #### npm ```bash npx prisma migration plan --name init ``` ```json no-copy "dir": "migrations/app/20261019T0955_init" ``` ### 2.4. Create the tables [#24-create-the-tables] Now create the tables in the database from step 1. `db:init` applies the contract directly and records in the database which contract it matches (the output calls this signing). It does not replay the migration you just planned; that file is for the deploy: #### bun ```bash bun run db:init ``` #### pnpm ```bash pnpm run db:init ``` #### yarn ```bash yarn db:init ``` #### npm ```bash npm run db:init ``` ```text no-copy "summary": "Applied 6 operation(s) across 1 space(s), database signed" ``` If `db:init` stops with `Connection terminated unexpectedly`, a database you just created is still starting; wait a few seconds and run it again. The command is safe to repeat. ## 3. Set up Clerk [#3-set-up-clerk] ### 3.1. Create a Clerk application [#31-create-a-clerk-application] [Sign in](https://dashboard.clerk.com/sign-in) to Clerk and create an application, or open one you already have. On the application's overview page, select **Next.js** and the **Manual setup** tab: it shows the install command and your API keys. Under **Configure** > **User & authentication** > **Email**, turn on **Sign-up with email**, **Require email address**, and **Sign-in with email**. The sign-up form only shows the fields an application requires, so without **Require email address** the form offers social sign-in buttons only. Install the Clerk Next.js SDK: #### bun ```bash bun add @clerk/nextjs ``` #### pnpm ```bash pnpm add @clerk/nextjs ``` #### yarn ```bash yarn add @clerk/nextjs ``` #### npm ```bash npm install @clerk/nextjs ``` Create a `.env.local` file in the project root and paste your keys from the dashboard. Next.js loads this file in development and at build time; the Prisma CLI does not, which is why `DATABASE_URL` stays exported in your shell. Use `.env.local` rather than `.env`: with `output: "standalone"`, `next build` copies `.env` into the build output that step 8 uploads, and `.env.local` stays out of it: ```bash title=".env.local" NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY= CLERK_SECRET_KEY= ``` The template's `.gitignore` lists only `.env`, so add the new file to it: ```bash title=".gitignore" .env.local # [!code ++] ``` ### 3.2. Add `clerkMiddleware()` as the Next.js proxy [#32-add-clerkmiddleware-as-the-nextjs-proxy] `clerkMiddleware()` reads the Clerk session on every request so that `auth()` and `currentUser()` work in your routes. Next.js 16 runs this kind of code from a `proxy.ts` file (the file was called `middleware.ts` in Next.js 15 and older). The template puts the app in `src/`, so create the file there: ```ts title="src/proxy.ts" import { clerkMiddleware } from "@clerk/nextjs/server"; export default clerkMiddleware(); export const config = { matcher: [ // Skip Next.js internals and all static files, unless found in search params "/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)", // Always run for API routes "/(api|trpc)(.*)", // Always run for Clerk-specific frontend API routes "/__clerk/(.*)", ], }; ``` > [!NOTE] > On Next.js 15 or older, name the file `middleware.ts` instead. The code is the same; only the file name changes. `clerkMiddleware()` leaves every route public, and the routes in this guide check for a user themselves, which is where Clerk recommends the check lives. ### 3.3. Add the Clerk provider and sign-in buttons [#33-add-the-clerk-provider-and-sign-in-buttons] Replace `src/app/layout.tsx` so that `ClerkProvider` wraps the app and a header shows sign-in, sign-up, and user buttons. `Show` renders its children only when the condition in `when` holds, so the header shows the sign-in and sign-up buttons to visitors and the user button to signed-in users; it replaces the `SignedIn` and `SignedOut` components from earlier Clerk versions. `mode="modal"` opens the sign-in and sign-up forms on your page instead of redirecting to Clerk's hosted pages: ```tsx title="src/app/layout.tsx" import type { Metadata } from "next"; import { ClerkProvider, Show, SignInButton, SignUpButton, UserButton } from "@clerk/nextjs"; import "./globals.css"; export const metadata: Metadata = { title: "clerk-nextjs-prisma", description: "Generated by create-prisma", }; export default function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { return (
{children}
); } ``` Append styles for the header, and for the post form you build in step 5, to the template's stylesheet: ```css title="src/app/globals.css" .navbar { display: flex; justify-content: flex-end; align-items: center; gap: 1rem; height: 4rem; padding: 0 1rem; } .post-form { display: grid; gap: 0.5rem; margin-bottom: 1.5rem; } .post-form input, .post-form textarea, .post-form button { padding: 0.5rem; border: 1px solid #e5e5e5; border-radius: 0.375rem; font: inherit; } ``` ## 4. Build a posts API [#4-build-a-posts-api] Create a route that stores a post for the signed-in user. `auth()` returns the Clerk user id from the session; a request without one gets `401`. The route then looks up the matching database user, so a post always points at a `User` row: ```ts title="src/app/api/posts/route.ts" import { auth } from "@clerk/nextjs/server"; import { db } from "@/src/prisma/db"; export async function POST(req: Request) { const { userId: clerkId } = await auth(); if (!clerkId) return new Response("Unauthorized", { status: 401 }); const { title, content } = await req.json(); const user = await db.orm.public.User.where({ clerkId }).first(); if (!user) return new Response("User not found", { status: 404 }); const post = await db.orm.public.Post.create({ title, content, authorId: user.id, }); return Response.json(post, { status: 201 }); } ``` `db` is the Prisma ORM client the template created in `src/prisma/db.ts`, and the template maps `@/` to the project root, so the import path is `@/src/prisma/db`. `db.orm` is the ORM query API, and on PostgreSQL every model sits under its schema, so the `User` table is `db.orm.public.User`. `.first()` returns one matching record or `null`, and `.create()` takes the fields directly and returns the inserted row. [Reading data](https://www.prisma.io/docs/orm/fundamentals/reading-data) and [Writing data](https://www.prisma.io/docs/orm/fundamentals/writing-data) cover the rest of the query API. ## 5. Add a post form [#5-add-a-post-form] Create a client component with a title field, a content field, and a submit button. On submit it calls the posts API and, if the API accepted the post, clears the fields and reloads the page so the new post appears in the list; a rejected post keeps the draft in the fields: ```tsx title="src/app/components/PostInputs.tsx" "use client"; import { useState } from "react"; export default function PostInputs() { const [title, setTitle] = useState(""); const [content, setContent] = useState(""); async function createPost(e: React.FormEvent) { e.preventDefault(); if (!title || !content) return; const res = await fetch("/api/posts", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ title, content }), }); if (!res.ok) return; setTitle(""); setContent(""); location.reload(); } return (
setTitle(e.target.value)} />