# Integrate InstantDB with Clerk

**Example Repository**

- [Clerk + InstantDB + Next.js Demo](https://github.com/clerk/clerk-instantdb-nextjs)

**Before you start**

- [Set up a Clerk application](https://clerk.com/docs/getting-started/quickstart/setup-clerk.md)
- [Create an InstantDB account](https://instantdb.com)
- [Integrate the appropriate Clerk SDK in your local project](https://clerk.com/docs/getting-started/quickstart/overview.md)

Integrating [InstantDB](https://www.instantdb.com/) with Clerk gives you the benefits of using an InstantDB database while leveraging Clerk's authentication features.

This tutorial will walk you through the steps to integrate InstantDB with Clerk.

1. ## Configure your Clerk session token

   InstantDB uses Clerk's [session token](https://clerk.com/docs/guides/sessions/session-tokens.md) to authenticate users. To use InstantDB with Clerk, you need to include the `email` claim in your Clerk session token.

   1. In the Clerk Dashboard, navigate to the [**Sessions**](https://dashboard.clerk.com/~/sessions) page.
   2. Under **Customize session token**, in the **Claims** editor, enter the following JSON and select **Save**. If you have already customized your session token, you may need to merge this with what you currently have.

      ```json
      {
        "email": "{{user.primary_email_address}}"
      }
      ```

   You can have additional claims as long as the `email` claim is set to `{{user.primary_email_address}}`.
2. ## Get your Clerk Publishable Key

   1. In the Clerk Dashboard, navigate to the [**API keys**](https://dashboard.clerk.com/~/api-keys) page.
   2. In the **Quick Copy** section, copy your Clerk Publishable Key.
3. ## Configure InstantDB

   1. In the InstantDB dashboard, navigate to the [**Auth**](https://www.instantdb.com/dash?t=auth) tab.
   2. At the top of the page, save the **Public App ID** somewhere as you'll need this later.
   3. Select **Setup Clerk**.
   4. Add the Clerk Publishable Key you copied in the previous step.
   5. Confirm the **The session token has the "email" claim.** message.
   6. Select **Add Clerk app**. Save the **Client Name** somewhere as you'll need this later.
4. ## Install the InstantDB library

   If you haven't already added InstantDB to your app, run the following command to install it:

   ```npm
   npm install @instantdb/react
   ```
5. ## Set your InstantDB credentials

   In your `.env` file, set the following environment variables to your InstantDB App ID and Clerk Client Name that you saved earlier:

   filename: .env

   ```env
   NEXT_PUBLIC_INSTANTDB_APP_ID=
   NEXT_PUBLIC_CLERK_CLIENT_NAME=
   ```
6. ## Initialize InstantDB in your app

   To [initialize InstantDB](https://www.instantdb.com/docs/init) in your app:

   1. Create a `db` directory.
   2. In the `db` directory, create a `instant.ts` file with the following code. It initializes InstantDB with your App ID and schema. The schema used below is necessary for this tutorial, but you can customize it as needed. Read more about InstantDB schemas in the [InstantDB docs](https://www.instantdb.com/docs/modeling-data).

   filename: db/instant.ts

   ```tsx
   import { i, init } from '@instantdb/react'

   const APP_ID = process.env.NEXT_PUBLIC_INSTANTDB_APP_ID

   if (!APP_ID) {
     throw new Error('Missing NEXT_PUBLIC_INSTANTDB_APP_ID in your .env file')
   }

   // Optional: Declare your schema
   export const schema = i.schema({
     entities: {
       todos: i.entity({
         text: i.string(),
         done: i.boolean(),
         createdAt: i.number(),
       }),
     },
   })

   export const db = init({ appId: APP_ID, schema })
   ```
7. ## Manage the Clerk and InstantDB auth sessions

   Integrating InstantDB with Clerk means that your users will sign in to your app using Clerk, and then Clerk's session token will be used to sign the user in to InstantDB. This means that your user will have two sessions: one with Clerk and one with InstantDB. In order to handle both sessions, the following component uses Clerk to check if the user is signed in, and if they are, it uses Clerk's session token to sign the user in to InstantDB. If the user is not signed in to Clerk, it signs them out of InstantDB, ensuring that if the Clerk session ends, the InstantDB session will end as well.

   filename: components/InstantDBAuthSync.tsx

   ```tsx
   'use client'

   import { db } from '@/db/instant'
   import { useAuth, useUser } from '@clerk/nextjs'
   import { useEffect } from 'react'

   // If a user is signed in with Clerk, sign them in with InstantDB
   export default function InstantDBAuthSync() {
     const { isSignedIn } = useUser()
     const { getToken } = useAuth()

     useEffect(() => {
       if (isSignedIn) {
         getToken()
           .then((token) => {
             // Create a long-lived session with Instant for your Clerk user
             // It will look up the user by email or create a new user with
             // the email address in the session token.
             db.auth.signInWithIdToken({
               clientName: process.env.NEXT_PUBLIC_CLERK_CLIENT_NAME as string,
               idToken: token as string,
             })
           })
           .catch((error) => {
             console.error('Error signing in with Instant', error)
           })
       } else {
         db.auth.signOut()
       }
     }, [isSignedIn])

     return null
   }
   ```

   It's important to use this component in your **root** `layout.tsx` file because the `useEffect()` hook that syncs the Clerk and InstantDB sessions needs to run on every page load in order to manage the sessions properly.

   filename: app/layout.tsx

   ```tsx
   import type { Metadata } from 'next'
   import { ClerkProvider, SignInButton, SignUpButton, Show, UserButton } from '@clerk/nextjs'
   import { Geist, Geist_Mono } from 'next/font/google'
   import './globals.css'
   import InstantDBAuthSync from '@/component/InstantDBAuthSync'

   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 async function RootLayout({
     children,
   }: Readonly<{
     children: React.ReactNode
   }>) {
     return (
       <>
         <InstantDBAuthSync />
         <html lang="en">
           <body className={`${geistSans.variable} ${geistMono.variable} antialiased`}>
             <ClerkProvider>
               <header className="flex justify-end items-center p-4 gap-4 h-16">
                 <Show when="signed-out">
                   <SignInButton />
                   <SignUpButton />
                 </Show>
                 <Show when="signed-in">
                   <UserButton />
                 </Show>
               </header>
               {children}
             </ClerkProvider>
           </body>
         </html>
       </>
     )
   }
   ```
8. ## Update your homepage

   Update your `page.tsx` file with the following code to see InstantDB in action. This code was copied from the [InstantDB quickstart](https://www.instantdb.com/docs#quick-start). The only difference is that the initialization code was moved to `/db/instant.ts` so that the InstantDB connection could be reused across the app.

   filename: app/page.tsx

   ```tsx
   'use client'

   import { id, InstaQLEntity } from '@instantdb/react'
   import { db, schema } from '@/db/instant'

   type Todo = InstaQLEntity<typeof schema, 'todos'>

   function App() {
     // Use Instant's `useQuery()` hook to get the todos
     const { isLoading, error, data } = db.useQuery({ todos: {} })

     if (isLoading) {
       return (
         <div className="-mt-16 font-mono min-h-screen flex justify-center items-center flex-col space-y-4">
           Loading...
         </div>
       )
     }

     if (error) {
       return (
         <div className="text-red-500 p-4 -mt-16 font-mono min-h-screen flex justify-center items-center flex-col space-y-4">
           Error: {error.message}
         </div>
       )
     }

     const { todos } = data

     return (
       <div className="-mt-16 font-mono min-h-screen flex justify-center items-center flex-col space-y-4">
         <h2 className="tracking-wide text-5xl text-gray-300">todos</h2>
         <div className="border border-gray-300 max-w-xs w-full">
           <TodoForm todos={todos} />
           <TodoList todos={todos} />
           <ActionBar todos={todos} />
         </div>
         <div className="text-xs text-center">Open another tab to see todos update in realtime!</div>
       </div>
     )
   }

   // Write Data
   // ---------
   function addTodo(text: string) {
     db.transact(
       db.tx.todos[id()].update({
         text,
         done: false,
         createdAt: Date.now(),
       }),
     )
   }

   function deleteTodo(todo: Todo) {
     db.transact(db.tx.todos[todo.id].delete())
   }

   function toggleDone(todo: Todo) {
     db.transact(db.tx.todos[todo.id].update({ done: !todo.done }))
   }

   function deleteCompleted(todos: Todo[]) {
     const completed = todos.filter((todo) => todo.done)
     const txs = completed.map((todo) => db.tx.todos[todo.id].delete())
     db.transact(txs)
   }

   function toggleAll(todos: Todo[]) {
     const newVal = !todos.every((todo) => todo.done)
     db.transact(todos.map((todo) => db.tx.todos[todo.id].update({ done: newVal })))
   }

   // Components
   // ----------
   function ChevronDownIcon() {
     return (
       <svg viewBox="0 0 20 20">
         <path d="M5 8 L10 13 L15 8" stroke="currentColor" fill="none" strokeWidth="2" />
       </svg>
     )
   }

   function TodoForm({ todos }: { todos: Todo[] }) {
     return (
       <div className="flex items-center h-10 border-b border-gray-300">
         <button
           className="h-full px-2 border-r border-gray-300 flex items-center justify-center"
           onClick={() => toggleAll(todos)}
         >
           <div className="w-5 h-5">
             <ChevronDownIcon />
           </div>
         </button>
         <form
           className="flex-1 h-full"
           onSubmit={(e) => {
             e.preventDefault()
             const input = e.currentTarget.input as HTMLInputElement
             addTodo(input.value)
             input.value = ''
           }}
         >
           <input
             className="w-full h-full px-2 outline-none bg-transparent"
             autoFocus
             placeholder="What needs to be done?"
             type="text"
             name="input"
           />
         </form>
       </div>
     )
   }

   function TodoList({ todos }: { todos: Todo[] }) {
     return (
       <div className="divide-y divide-gray-300">
         {todos.map((todo) => (
           <div key={todo.id} className="flex items-center h-10">
             <div className="h-full px-2 flex items-center justify-center">
               <div className="w-5 h-5 flex items-center justify-center">
                 <input
                   type="checkbox"
                   className="cursor-pointer"
                   checked={todo.done}
                   onChange={() => toggleDone(todo)}
                 />
               </div>
             </div>
             <div className="flex-1 px-2 overflow-hidden flex items-center">
               {todo.done ? (
                 <span className="line-through">{todo.text}</span>
               ) : (
                 <span>{todo.text}</span>
               )}
             </div>
             <button
               className="h-full px-2 flex items-center justify-center text-gray-300 hover:text-gray-500"
               onClick={() => deleteTodo(todo)}
             >
               X
             </button>
           </div>
         ))}
       </div>
     )
   }

   function ActionBar({ todos }: { todos: Todo[] }) {
     return (
       <div className="flex justify-between items-center h-10 px-2 text-xs border-t border-gray-300">
         <div>Remaining todos: {todos.filter((todo) => !todo.done).length}</div>
         <button className=" text-gray-300 hover:text-gray-500" onClick={() => deleteCompleted(todos)}>
           Delete Completed
         </button>
       </div>
     )
   }

   export default App
   ```

---

## Sitemap

[Overview of all docs pages](https://clerk.com/docs/llms.txt)
