Integrate Convex with Clerk
With Convex, you can build a backend with a provided realtime database, file storage, text search, scheduling and more. Paired with Clerk's user authentication and management features, you can build a powerful application with minimal effort. This tutorial will show you how to integrate Clerk into your Convex application. It assumes that you have already integrated both Convex and one of Clerk's SDKs into your app.
Set up Clerk as a Convex auth provider
For your Clerk session token to work with Convex, you need to set up the Convex integration in Clerk.
- In the Clerk Dashboard, navigate to the Convex integration setup.
- Choose your configuration options, and then select Activate Convex integration. This will reveal the for your Clerk instance.
- Save the URL. In development, its format is
https://verb-noun-00.clerk.accounts.dev. In production, its format ishttps://clerk.<your-domain>.com.
Map additional claims (optional)
If you need to map additional claims, navigate to the Sessions page in the Clerk Dashboard.
In the Claims section, the default audience (aud) claim required by Convex is pre-mapped. You can include additional claims as necessary. Shortcodes are available to make adding dynamic user values easy.
Configure Convex with Clerk's Frontend API URL
-
Convex backend functions don't read your app's
.envfile. In your Convex project, run the following command to set your Clerk :terminal npx convex env set CLERK_FRONTEND_API_URL 'YOUR_FRONTEND_API_URL' -
In your Convex project's
convexfolder, create anauth.config.tsfile with the following configuration:convex /auth.config.ts import type { AuthConfig } from 'convex/server' export default { providers: [ { domain: process.env.CLERK_FRONTEND_API_URL!, applicationID: 'convex', }, ], } satisfies AuthConfig
Sync your auth configuration
In your Convex project, run the following command to sync your auth configuration to your Convex backend:
npx convex devConfigure the Clerk and Convex providers
Both Clerk and Convex have provider components that are required to provide authentication and client context. You should already have Clerk's provider component, <ClerkProvider>, in your app. Convex offers a provider that is specifically for integrating with Clerk called <ConvexProviderWithClerk>.
<ConvexProviderWithClerk> must run on the client: it needs a ConvexReactClient instance and Clerk's useAuth() hook, neither of which a Server Component can provide. Your app/layout.tsx, where you would use <ConvexProviderWithClerk>, is a Server Component, so you must first create a wrapper Client Component around <ConvexProviderWithClerk>.
'use client'
import { ReactNode } from 'react'
import { ConvexReactClient } from 'convex/react'
import { ConvexProviderWithClerk } from 'convex/react-clerk'
import { useAuth } from '@clerk/nextjs'
if (!process.env.NEXT_PUBLIC_CONVEX_URL) {
throw new Error('Missing NEXT_PUBLIC_CONVEX_URL in your .env file')
}
const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL)
export default function ConvexClientProvider({ children }: { children: ReactNode }) {
return (
<ConvexProviderWithClerk client={convex} useAuth={useAuth}>
{children}
</ConvexProviderWithClerk>
)
}Now, your Server Component, app/layout.tsx, can use the wrapper component, <ConvexClientProvider>. It's important that <ClerkProvider> wraps <ConvexClientProvider>, and not the other way around, as Convex needs to be able to access the Clerk context.
import type { Metadata } from 'next'
import { Geist, Geist_Mono } from 'next/font/google'
import './globals.css'
import { ClerkProvider } from '@clerk/nextjs'
import ConvexClientProvider from '@/components/ConvexClientProvider'
const geistSans = Geist({
variable: '--font-geist-sans',
subsets: ['latin'],
})
const geistMono = Geist_Mono({
variable: '--font-geist-mono',
subsets: ['latin'],
})
export const metadata: Metadata = {
title: 'Clerk Next.js Quickstart',
description: 'Generated by create next app',
}
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode
}>) {
return (
<html lang="en" className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}>
<body className="min-h-full flex flex-col">
<ClerkProvider>
<ConvexClientProvider>{children}</ConvexClientProvider>
</ClerkProvider>
</body>
</html>
)
}The following example demonstrates how to configure Clerk and Convex's providers. Clerk's useAuth() hook must be passed to Convex's <ConvexProviderWithClerk> and Clerk's <ClerkProvider> must be wrapped around it.
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import './index.css'
import { ClerkProvider, useAuth } from '@clerk/react'
import { ConvexProviderWithClerk } from 'convex/react-clerk'
import { ConvexReactClient } from 'convex/react'
// Import your Publishable Key
const PUBLISHABLE_KEY = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY
if (!PUBLISHABLE_KEY) {
throw new Error('Add your Clerk Publishable Key to the .env file')
}
if (!import.meta.env.VITE_CONVEX_URL) {
throw new Error('Missing VITE_CONVEX_URL in your .env file')
}
const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL)
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<ClerkProvider publishableKey={PUBLISHABLE_KEY} afterSignOutUrl="/">
<ConvexProviderWithClerk client={convex} useAuth={useAuth}>
<App />
</ConvexProviderWithClerk>
</ClerkProvider>
</React.StrictMode>,
)Show UI based on auth state
You can control which UI is shown when the user is signed in or signed out using Convex's <Authenticated>, <Unauthenticated> and <AuthLoading> helper components. These should be used instead of Clerk's <Show when="signed-in">, <Show when="signed-out"> and <ClerkLoading> components, respectively.
It's important to use the useConvexAuth() hook instead of Clerk's useAuth() hook when you need to check whether the user is signed in or
not. The useConvexAuth() hook makes sure that the browser has fetched the auth token needed to make authenticated requests to your Convex backend, and that the Convex backend has validated it.
In the following example, the <Content /> component is a child of <Authenticated>, so its content and any of its child components are guaranteed to have an authenticated user, and Convex queries can require authentication.
'use client'
import { Authenticated, Unauthenticated } from 'convex/react'
import { SignInButton, UserButton } from '@clerk/nextjs'
import { useQuery } from 'convex/react'
import { api } from '../convex/_generated/api'
export default function Home() {
return (
<>
<Authenticated>
<UserButton />
<Content />
</Authenticated>
<Unauthenticated>
<SignInButton />
</Unauthenticated>
</>
)
}
function Content() {
const messages = useQuery(api.messages.getForCurrentUser)
return <div>Authenticated content: {messages?.length}</div>
}import { SignInButton, UserButton } from '@clerk/react'
import { Authenticated, Unauthenticated, AuthLoading, useQuery } from 'convex/react'
import { api } from '../convex/_generated/api'
function App() {
return (
<main>
<Unauthenticated>
<SignInButton />
</Unauthenticated>
<Authenticated>
<UserButton />
<Content />
</Authenticated>
<AuthLoading>
<p>Still loading</p>
</AuthLoading>
</main>
)
}
function Content() {
const messages = useQuery(api.messages.getForCurrentUser)
return <div>Authenticated content: {messages?.length}</div>
}
export default AppUse auth state in your Convex functions
If the client is authenticated, you can access its verified token claims by calling ctx.auth.getUserIdentity().
If the client isn't authenticated, ctx.auth.getUserIdentity() returns null.
import { query } from './_generated/server'
export const getForCurrentUser = query({
args: {},
handler: async (ctx) => {
const identity = await ctx.auth.getUserIdentity()
if (identity === null) {
throw new Error('Not authenticated')
}
return await ctx.db
.query('messages')
.filter((q) => q.eq(q.field('author'), identity.email))
.collect()
},
})Deploy to production
Set the from your Clerk production instance on your Convex production deployment. In your Convex project, run:
npx convex env --prod set CLERK_FRONTEND_API_URL 'https://clerk.<your-domain>.com'Then, run the following command to sync your auth configuration to your Convex production deployment:
npx convex deployNext steps
Be aware that Convex may require usage of their custom hooks and methods rather than Clerk's, such as using Convex's useConvexAuth() hook instead of Clerk's useAuth() hook in some cases. For more information on how to use Convex with Clerk, see the Convex docs.
Feedback
Last updated on