# Clerk (with Astro) (/docs/guides/authentication/clerk/astro)

Location: Guides > Authentication > Clerk > Clerk (with Astro)

Introduction [#introduction]

[Clerk](https://clerk.com/) is a drop-in auth provider that handles sign-up, sign-in, user management, and webhooks so you don't have to.

In this guide you'll wire Clerk into a brand-new [Astro](https://astro.build/) app and persist users in a [Prisma Postgres](https://prisma.io/postgres) database. You can find a complete example of this guide on [GitHub](https://github.com/prisma/prisma-examples/tree/latest/orm/clerk-astro).

Prerequisites [#prerequisites]

* [Node.js 20+](https://nodejs.org)
* [Clerk account](https://clerk.com)
* [ngrok account](https://ngrok.com)

1. Set up your project [#1-set-up-your-project]

Create a new Astro project:

  

#### npm

```bash
npm create astro@latest
```

#### pnpm

```bash
pnpm create astro@latest
```

#### yarn

```bash
yarn create astro@latest
```

#### bun

```bash
bun create astro@latest
```

It will prompt you to customize your setup. Choose the defaults:

> [!NOTE]
> * *How would you like to start your new project?* `Empty`
> * *Install dependencies?* `Yes`
> * *Initialize a new git repository?* `Yes`

Navigate into the newly created project directory:

```bash
cd <your-project-name>
```

2. Set up Clerk [#2-set-up-clerk]

2.1. Create a new Clerk application [#21-create-a-new-clerk-application]

[Sign in](https://dashboard.clerk.com/sign-in) to Clerk and navigate to the home page. From there, press the `Create Application` button to create a new application. Enter a title, select your sign-in options, and click `Create Application`.

> [!NOTE]
> For this guide, the Google, Github, and Email sign in options will be used.

Install the Clerk Astro SDK and Node adapter:

  

#### npm

```bash
npm install @clerk/astro @astrojs/node
```

#### pnpm

```bash
pnpm add @clerk/astro @astrojs/node
```

#### yarn

```bash
yarn add @clerk/astro @astrojs/node
```

#### bun

```bash
bun add @clerk/astro @astrojs/node
```

In the Clerk Dashboard, navigate to the **API keys** page. In the **Quick Copy** section, copy your Clerk Publishable and Secret Keys. Paste your keys into `.env` in the root of your project:

```bash title=".env"
PUBLIC_CLERK_PUBLISHABLE_KEY=<your-publishable-key>
CLERK_SECRET_KEY=<your-secret-key>
```

2.2. Configure Astro with Clerk [#22-configure-astro-with-clerk]

Astro needs to be configured for server-side rendering (SSR) with the Node adapter to work with Clerk. Update your `astro.config.mjs` file to include the Clerk integration and enable SSR:

```javascript title="astro.config.mjs"
import { defineConfig } from "astro/config";
import node from "@astrojs/node"; // [!code ++]
import clerk from "@clerk/astro"; // [!code ++]

export default defineConfig({
  integrations: [clerk()], // [!code ++]
  adapter: node({ mode: "standalone" }), // [!code ++]
  output: "server", // [!code ++]
});
```

2.3. Set up Clerk middleware [#23-set-up-clerk-middleware]

The `clerkMiddleware` helper enables authentication across your entire application. Create a `middleware.ts` file in the `src` directory:

```typescript title="src/middleware.ts"
import { clerkMiddleware } from "@clerk/astro/server";

export const onRequest = clerkMiddleware();
```

2.4. Add Clerk UI to your page [#24-add-clerk-ui-to-your-page]

Update your `src/pages/index.astro` file to import the Clerk authentication components:

```html title="src/pages/index.astro"
---
import { // [!code ++]
SignedIn, // [!code ++]
SignedOut, // [!code ++]
UserButton, // [!code ++]
SignInButton, // [!code ++]
} from "@clerk/astro/components"; // [!code ++]
---

<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
    <meta name="viewport" content="width=device-width" />
    <meta name="generator" content="{Astro.generator}" />
    <title>Astro</title>
  </head>
  <body></body>
</html>
```

Now add a header with conditional rendering to show sign-in buttons for unauthenticated users and a user button for authenticated users:

```html title="src/pages/index.astro"
---
import {
SignedIn,
SignedOut,
UserButton,
SignInButton,
} from "@clerk/astro/components";
---

<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
    <meta name="viewport" content="width=device-width" />
    <meta name="generator" content="{Astro.generator}" />
    <title>Astro</title>
  </head>
  <body>
    <header>
      // [!code ++]
      <SignedOut> // [!code ++] <SignInButton mode="modal" /> // [!code ++] </SignedOut> // [!code
      ++] <SignedIn> // [!code ++] <UserButton /> // [!code ++] </SignedIn> // [!code ++]
    </header>
    // [!code ++]
  </body>
</html>
```

3. Install and configure Prisma [#3-install-and-configure-prisma]

3.1. Install dependencies [#31-install-dependencies]

To get started with Prisma, you'll need to install a few dependencies:

  

#### npm

```bash
npm install prisma tsx @types/pg --save-dev
```

#### pnpm

```bash
pnpm add prisma tsx @types/pg --save-dev
```

#### yarn

```bash
yarn add prisma tsx @types/pg --dev
```

#### bun

```bash
bun add prisma tsx @types/pg --dev
```

  

#### npm

```bash
npm install @prisma/client @prisma/adapter-pg dotenv pg
```

#### pnpm

```bash
pnpm add @prisma/client @prisma/adapter-pg dotenv pg
```

#### yarn

```bash
yarn add @prisma/client @prisma/adapter-pg dotenv pg
```

#### bun

```bash
bun add @prisma/client @prisma/adapter-pg dotenv pg
```

> [!NOTE]
> If you are using a different database provider (MySQL, SQL Server, SQLite), install the corresponding driver adapter package instead of `@prisma/adapter-pg`. For more information, see [Database drivers](/orm/core-concepts/supported-databases/database-drivers).

Once installed, initialize Prisma in your project:

  

#### npm

```bash
npx prisma init
```

#### pnpm

```bash
pnpm dlx prisma init
```

#### yarn

```bash
yarn dlx prisma init
```

#### bun

```bash
bunx --bun prisma init
```

> [!NOTE]
> `prisma init` creates the Prisma scaffolding and a local `DATABASE_URL`. In the next step, you will create a Prisma Postgres database and replace that value with a direct `postgres://...` connection string.

This will create:

* A `prisma/` directory with a `schema.prisma` file
* A `prisma.config.ts` file with your Prisma configuration
* A `.env` file with a local `DATABASE_URL` already set

Create a Prisma Postgres database and replace the generated `DATABASE_URL` in your `.env` file with the `postgres://...` connection string from the CLI output:

  

#### npm

```bash
npx create-db
```

#### pnpm

```bash
pnpm dlx create-db
```

#### yarn

```bash
yarn dlx create-db
```

#### bun

```bash
bunx --bun create-db
```

3.2. Define your Prisma Schema [#32-define-your-prisma-schema]

Add a `User` model that will store authenticated user information from Clerk. The `clerkId` field uniquely links each database user to their Clerk account:

```prisma title="prisma/schema.prisma"
generator client {
  provider = "prisma-client"
  output   = "../src/generated/prisma"
}

datasource db {
  provider = "postgresql"
}

model User { // [!code ++]
  id      Int     @id @default(autoincrement()) // [!code ++]
  clerkId String  @unique // [!code ++]
  email   String  @unique // [!code ++]
  name    String? // [!code ++]
} // [!code ++]
```

Run the following command to create the database tables:

  

#### npm

```bash
npx prisma migrate dev --name init
```

#### pnpm

```bash
pnpm dlx prisma migrate dev --name init
```

#### yarn

```bash
yarn dlx prisma migrate dev --name init
```

#### bun

```bash
bunx --bun prisma migrate dev --name init
```

After the migration is complete, generate the Prisma Client:

  

#### npm

```bash
npx prisma generate
```

#### pnpm

```bash
pnpm dlx prisma generate
```

#### yarn

```bash
yarn dlx prisma generate
```

#### bun

```bash
bunx --bun prisma generate
```

This generates the Prisma Client in the `src/generated/prisma` directory.

3.3. Create TypeScript environment definitions [#33-create-typescript-environment-definitions]

Create an `env.d.ts` file in your `src` directory to provide TypeScript definitions for your environment variables:

```bash
touch src/env.d.ts
```

Add type definitions for all the environment variables your application uses:

```typescript title="src/env.d.ts"
interface ImportMetaEnv {
  readonly DATABASE_URL: string;
  readonly CLERK_WEBHOOK_SIGNING_SECRET: string;
  readonly CLERK_SECRET_KEY: string;
  readonly PUBLIC_CLERK_PUBLISHABLE_KEY: string;
}

interface ImportMeta {
  readonly env: ImportMetaEnv;
}
```

3.4. Create a reusable Prisma Client [#34-create-a-reusable-prisma-client]

In the `src` directory, create a `lib` directory and a `prisma.ts` file inside it:

```bash
mkdir src/lib
touch src/lib/prisma.ts
```

Initialize the Prisma Client with the PostgreSQL adapter:

```typescript title="src/lib/prisma.ts"
import { PrismaClient } from "../generated/prisma/client";
import { PrismaPg } from "@prisma/adapter-pg";

const adapter = new PrismaPg({
  connectionString: import.meta.env.DATABASE_URL,
});

const prisma = new PrismaClient({
  adapter,
});

export default prisma;
```

4. Wire Clerk to the database [#4-wire-clerk-to-the-database]

4.1. Create a Clerk webhook endpoint [#41-create-a-clerk-webhook-endpoint]

Webhooks allow Clerk to notify your application when events occur, such as when a user signs up. You'll create an API route to handle these webhooks and sync user data to your database.

Create the directory structure and file for the webhook endpoint:

```bash
mkdir -p src/pages/api/webhooks
touch src/pages/api/webhooks/clerk.ts
```

Import the necessary dependencies:

```typescript title="src/pages/api/webhooks/clerk.ts"
import { verifyWebhook } from "@clerk/astro/webhooks";
import type { APIRoute } from "astro";
import prisma from "../../../lib/prisma";
```

Create the `POST` handler that Clerk will call. The `verifyWebhook` function validates that the request actually comes from Clerk using the signing secret:

```typescript title="src/pages/api/webhooks/clerk.ts"
import { verifyWebhook } from "@clerk/astro/webhooks";
import type { APIRoute } from "astro";
import prisma from "../../../lib/prisma";

export const POST: APIRoute = async ({ request }) => {
  // [!code ++]
  try {
    // [!code ++]
    const evt = await verifyWebhook(request, {
      // [!code ++]
      signingSecret: import.meta.env.CLERK_WEBHOOK_SIGNING_SECRET, // [!code ++]
    }); // [!code ++]
    const { id } = evt.data; // [!code ++]
    const eventType = evt.type; // [!code ++]
    console.log(
      // [!code ++]
      `Received webhook with ID ${id} and event type of ${eventType}`, // [!code ++]
    ); // [!code ++]
  } catch (err) {
    // [!code ++]
    console.error("Error verifying webhook:", err); // [!code ++]
    return new Response("Error verifying webhook", { status: 400 }); // [!code ++]
  } // [!code ++]
}; // [!code ++]
```

When a new user is created, they need to be stored in the database.

You'll do that by checking if the event type is `user.created` and then using Prisma's `upsert` method to create a new user if they don't exist:

```typescript title="src/pages/api/webhooks/clerk.ts"
import { verifyWebhook } from "@clerk/astro/webhooks";
import type { APIRoute } from "astro";
import prisma from "../../../lib/prisma";

export const POST: APIRoute = async ({ request }) => {
  try {
    const evt = await verifyWebhook(request, {
      signingSecret: import.meta.env.CLERK_WEBHOOK_SIGNING_SECRET,
    });
    const { id } = evt.data;
    const eventType = evt.type;
    console.log(`Received webhook with ID ${id} and event type of ${eventType}`);

    if (eventType === "user.created") {
      // [!code ++]
      const { id, email_addresses, first_name, last_name } = evt.data; // [!code ++]
      await prisma.user.upsert({
        // [!code ++]
        where: { clerkId: id }, // [!code ++]
        update: {}, // [!code ++]
        create: {
          // [!code ++]
          clerkId: id, // [!code ++]
          email: email_addresses[0].email_address, // [!code ++]
          name: `${first_name} ${last_name}`, // [!code ++]
        }, // [!code ++]
      }); // [!code ++]
    } // [!code ++]
  } catch (err) {
    console.error("Error verifying webhook:", err);
    return new Response("Error verifying webhook", { status: 400 });
  }
};
```

Finally, return a response to Clerk to confirm the webhook was received:

```typescript title="src/pages/api/webhooks/clerk.ts"
import { verifyWebhook } from "@clerk/astro/webhooks";
import type { APIRoute } from "astro";
import prisma from "../../../lib/prisma";

export const POST: APIRoute = async ({ request }) => {
  try {
    const evt = await verifyWebhook(request, {
      signingSecret: import.meta.env.CLERK_WEBHOOK_SIGNING_SECRET,
    });
    const { id } = evt.data;
    const eventType = evt.type;
    console.log(`Received webhook with ID ${id} and event type of ${eventType}`);

    if (eventType === "user.created") {
      const { id, email_addresses, first_name, last_name } = evt.data;
      await prisma.user.upsert({
        where: { clerkId: id },
        update: {},
        create: {
          clerkId: id,
          email: email_addresses[0].email_address,
          name: `${first_name} ${last_name}`,
        },
      });
    }

    return new Response("Webhook received", { status: 200 }); // [!code ++]
  } catch (err) {
    console.error("Error verifying webhook:", err);
    return new Response("Error verifying webhook", { status: 400 });
  }
};
```

4.2. Expose your local app for webhooks [#42-expose-your-local-app-for-webhooks]

You'll need to expose your local app for webhooks with [ngrok](https://ngrok.com/). This will allow Clerk to reach your `/api/webhooks/clerk` route to push events like `user.created`.

Start your development server:

  

#### npm

```bash
npm run dev
```

#### pnpm

```bash
pnpm run dev
```

#### yarn

```bash
yarn dev
```

#### bun

```bash
bun run dev
```

In a separate terminal window, install ngrok globally and expose your local app:

  

#### npm

```bash
npm install --global ngrok
ngrok http 4321
```

#### pnpm

```bash
pnpm add --global ngrok
ngrok http 4321
```

#### yarn

```bash
yarn global add ngrok
ngrok http 4321
```

#### bun

```bash
bun add --global ngrok
ngrok http 4321
```

Copy the ngrok `Forwarding URL` (e.g., `https://a65a60261342.ngrok-free.app`). This will be used to configure the webhook URL in Clerk.

4.3. Configure Astro to allow ngrok connections [#43-configure-astro-to-allow-ngrok-connections]

Astro needs to be configured to accept connections from the ngrok domain. Update your `astro.config.mjs` to include the ngrok host in the allowed hosts list:

```javascript title="astro.config.mjs"
import { defineConfig } from "astro/config";
import node from "@astrojs/node";
import clerk from "@clerk/astro";

export default defineConfig({
  integrations: [clerk()],
  adapter: node({ mode: "standalone" }),
  output: "server",
  server: {
    // [!code ++]
    allowedHosts: ["localhost", "<your-ngrok-subdomain>.ngrok-free.app"], // [!code ++]
  }, // [!code ++]
});
```

> [!NOTE]
> Replace `<your-ngrok-subdomain>` with the subdomain from your ngrok URL. For example, if your ngrok URL is `https://a65a60261342.ngrok-free.app`, use `a65a60261342.ngrok-free.app`.

4.4. Register the webhook in Clerk [#44-register-the-webhook-in-clerk]

Navigate to the ***Webhooks*** section of your Clerk application located near the bottom of the ***Configure*** tab under ***Developers***.

Click ***Add Endpoint*** and paste the ngrok URL into the ***Endpoint URL*** field and add `/api/webhooks/clerk` to the end. It should look similar to this:

```text
https://a65a60261342.ngrok-free.app/api/webhooks/clerk
```

Subscribe to the **user.created** event by checking the box next to it under ***Message Filtering***.

Click ***Create*** to save the webhook endpoint.

Copy the ***Signing Secret*** and add it to your `.env` file:

```bash title=".env"
# Prisma
DATABASE_URL=<your-database-url>

# Clerk
PUBLIC_CLERK_PUBLISHABLE_KEY=<your-publishable-key>
CLERK_SECRET_KEY=<your-secret-key>
CLERK_WEBHOOK_SIGNING_SECRET=<your-signing-secret> # [!code ++]
```

Restart your dev server to pick up the new environment variable:

  

#### npm

```bash
npm run dev
```

#### pnpm

```bash
pnpm run dev
```

#### yarn

```bash
yarn dev
```

#### bun

```bash
bun run dev
```

4.5. Test the integration [#45-test-the-integration]

Navigate to `http://localhost:4321` in your browser and sign in using any of the sign-up options you configured in Clerk.

Open Prisma Studio to verify that the user was created in your database:

  

#### npm

```bash
npx prisma studio
```

#### pnpm

```bash
pnpm dlx prisma studio
```

#### yarn

```bash
yarn dlx prisma studio
```

#### bun

```bash
bunx --bun prisma studio
```

You should see a new user record with the Clerk ID, email, and name from your sign-up.

> [!NOTE]
> If you don't see a user record, there are a few things to check:
> 
> * Delete your user from the Users tab in Clerk and try signing up again.
> * Check your ngrok URL and ensure it's correct *(it will change every time you restart ngrok)*.
> * Verify your Clerk webhook is pointing to the correct ngrok URL.
> * Make sure you've added `/api/webhooks/clerk` to the end of the webhook URL.
> * Ensure you've subscribed to the **user.created** event in Clerk.
> * Confirm you've added the ngrok host to `allowedHosts` in `astro.config.mjs` and removed `https://`.
> * Check the terminal running `npm run dev` for any error messages.

You've successfully built an Astro application with Clerk authentication and Prisma, creating a foundation for a secure and scalable full-stack application that handles user management and data persistence with ease.

Next steps [#next-steps]

Now that you have a working Astro app with Clerk authentication and Prisma connected to a Prisma Postgres database, you can:

* Add user profile management and update functionality
* Build protected API routes that require authentication
* Extend your schema with additional models related to users
* Deploy to your preferred hosting platform and set your production webhook URL in Clerk

More info [#more-info]

* [Prisma Documentation](/orm)
* [Astro Documentation](https://docs.astro.build)
* [Clerk Documentation](https://clerk.com/docs)

## Related pages

- [`Clerk (with Next.js)`](https://www.prisma.io/docs/guides/authentication/clerk/nextjs): Learn how to use Prisma ORM in a Next.js app with Clerk Auth