# convex-clerk
> Clerk authentication integration for Convex. Use when setting up Clerk auth, configuring ConvexProviderWithClerk, implementing Clerk webhooks for user sync, or troubleshooting Clerk-specific auth issues.
- Author: Razeen Ali
- Repository: micic-mihajlo/bullpen
- Version: 20260209122040
- Stars: 0
- Forks: 0
- Last Updated: 2026-02-09
- Source: https://github.com/micic-mihajlo/bullpen
- Web: https://mule.run/skillshub/@@micic-mihajlo/bullpen~convex-clerk:20260209122040
---
---
name: convex-clerk
description: 'Clerk authentication integration for Convex. Use when setting up Clerk auth, configuring ConvexProviderWithClerk, implementing Clerk webhooks for user sync, or troubleshooting Clerk-specific auth issues.'
---
# Convex + Clerk Authentication
Provider-specific patterns for integrating Clerk with Convex.
## Required Configuration
### 1. auth.config.ts
```typescript
// convex/auth.config.ts
import { AuthConfig } from 'convex/server';
export default {
providers: [
{
domain: process.env.CLERK_JWT_ISSUER_DOMAIN!,
applicationID: 'convex'
}
]
} satisfies AuthConfig;
```
**CRITICAL:** JWT template in Clerk MUST be named exactly `convex`.
### 2. Environment Variables
```bash
# .env.local (Vite)
VITE_CLERK_PUBLISHABLE_KEY=pk_test_...
# .env.local (Next.js)
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...
CLERK_SECRET_KEY=sk_test_...
# Convex Dashboard Environment Variables
CLERK_JWT_ISSUER_DOMAIN=https://verb-noun-00.clerk.accounts.dev
CLERK_WEBHOOK_SECRET=whsec_... # If using webhooks
```
## Client Setup
### React (Vite)
```typescript
// src/main.tsx
import { ClerkProvider, useAuth } from "@clerk/clerk-react";
import { ConvexProviderWithClerk } from "convex/react-clerk";
import { ConvexReactClient } from "convex/react";
const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL);
ReactDOM.createRoot(document.getElementById("root")!).render(
);
```
### Next.js App Router
```typescript
// components/ConvexClientProvider.tsx
'use client';
import { ReactNode } from 'react';
import { ConvexReactClient } from 'convex/react';
import { ConvexProviderWithClerk } from 'convex/react-clerk';
import { useAuth } from '@clerk/nextjs';
const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!);
export default function ConvexClientProvider({ children }: { children: ReactNode }) {
return (
{children}
);
}
```
```typescript
// app/layout.tsx
import { ClerkProvider } from '@clerk/nextjs';
import ConvexClientProvider from '@/components/ConvexClientProvider';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
{children}
);
}
```
### Next.js Middleware
```typescript
// middleware.ts
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server';
const isProtectedRoute = createRouteMatcher(['/dashboard(.*)']);
export default clerkMiddleware(async (auth, req) => {
if (isProtectedRoute(req)) {
await auth.protect();
}
});
export const config = {
matcher: ['/((?!.*\\..*|_next).*)', '/', '/(api|trpc)(.*)']
};
```
## UI Components
Use Convex auth components, NOT Clerk's:
```typescript
// ✅ Correct
import { Authenticated, Unauthenticated, AuthLoading } from 'convex/react';
// ❌ Don't use these for conditional rendering
import { SignedIn, SignedOut } from '@clerk/clerk-react';
```
```typescript
import { SignInButton, UserButton } from "@clerk/clerk-react";
import { Authenticated, Unauthenticated } from "convex/react";
function App() {
return (
<>
>
);
}
```
## Clerk Webhooks for User Sync
See [WEBHOOKS.md](references/WEBHOOKS.md) for complete implementation.
**Setup in Clerk Dashboard:**
1. Webhooks > Add Endpoint
2. URL: `https://your-deployment.convex.site/clerk-users-webhook`
3. Events: Select all `user.*` events
4. Copy Signing Secret → Convex Dashboard env vars as `CLERK_WEBHOOK_SECRET`
## Accessing User Info
### Client-side (Clerk SDK)
```typescript
import { useUser } from "@clerk/clerk-react";
function Profile() {
const { user } = useUser();
return Hello, {user?.fullName};
}
```
### Server-side (Convex functions)
```typescript
export const myQuery = query({
handler: async (ctx) => {
const identity = await ctx.auth.getUserIdentity();
// identity.name, identity.email, etc.
// Fields depend on Clerk JWT template claims config
}
});
```
## Dev vs Prod Configuration
| Environment | Publishable Key | Issuer Domain |
| ----------- | --------------- | ----------------------------------------- |
| Development | `pk_test_...` | `https://verb-noun-00.clerk.accounts.dev` |
| Production | `pk_live_...` | `https://clerk.your-domain.com` |
Set different values in Convex Dashboard for dev vs prod deployments.
## Clerk-Specific Troubleshooting
| Issue | Cause | Fix |
| ------------------- | ------------------------------- | -------------------------------------- |
| Token not generated | JWT template not named "convex" | Rename template to exactly `convex` |
| `aud` mismatch | Wrong applicationID | Use `applicationID: "convex"` |
| `iss` mismatch | Wrong domain | Copy Frontend API URL from Clerk |
| Webhook fails | Wrong secret | Copy Signing Secret from Clerk webhook |
## DO ✅
- Name JWT template exactly `convex`
- Use `ConvexProviderWithClerk` with `useAuth` from Clerk
- Use `useConvexAuth()` not Clerk's `useAuth()` for auth state
- Use Convex's `` not Clerk's ``
- Set CLERK_JWT_ISSUER_DOMAIN in Convex Dashboard
## DON'T ❌
- Rename the JWT template from "convex"
- Use Clerk's auth hooks to gate Convex queries
- Hardcode the issuer domain (use env var)
- Forget to deploy after changing auth.config.ts