---
title: Convex Integration
description: Integrate Better Auth with Convex.
---

In this guide, we'll walk through the steps to integrate Better Auth with [Convex](https://www.convex.dev).

<Steps>
<Step>
## Prerequisites

### Create a Convex project

To use Better Auth with Convex, you need a Convex project. If you don’t have one, run the following command to create a new project. For more details, see the [Convex documentation](https://docs.convex.dev/home).

```package-install
npm create convex@latest

# Choose user authentication:
# > none
```

### Run `convex dev`

Running the CLI during setup will initialize your Convex deployment
if it doesn't already exist, and keeps generated types current through the process. Keep it running.

```package-install
npx convex dev
```
</Step>

<Step>
## Install packages

Install a pinned version of Better Auth and the Convex component for Better Auth, and make sure you are using the latest version of Convex.

```package-install
npm install better-auth@1.4.9 --save-exact
npm install @convex-dev/better-auth
```

<Callout type="info">
`@convex-dev/better-auth` is maintained by Convex. For issues or more details, please visit [here](https://github.com/get-convex/better-auth).
</Callout>
</Step>

<Step>
## Set environment variables

Generate a secret for encryption and generating hashes. Use the command below if you have openssl installed, or use `@better-auth/cli secret` to generate one.

```package-install
npx convex env set BETTER_AUTH_SECRET=$(openssl rand -base64 32)
```

Add your site URL to your Convex deployment.

```package-install
npx convex env set SITE_URL http://localhost:3000
```

Add environment variables to the `.env.local` file created by `npx convex dev`.
It will be picked up by your framework dev server.

```sh title=".env.local" tab="Cloud"
# Deployment used by `npx convex dev`
CONVEX_DEPLOYMENT=dev:adjective-animal-123 # team: team-name, project: project-name

NEXT_PUBLIC_CONVEX_URL=https://adjective-animal-123.convex.cloud

# Same as NEXT_PUBLIC_CONVEX_URL but ends in .site // [!code ++]
NEXT_PUBLIC_CONVEX_SITE_URL=https://adjective-animal-123.convex.site # [!code ++]

# Your local site URL // [!code ++]
NEXT_PUBLIC_SITE_URL=http://localhost:3000 # [!code ++]
```

```sh title=".env.local" tab="Self hosted"
# Deployment used by `npx convex dev`
CONVEX_DEPLOYMENT=dev:adjective-animal-123 # team: team-name, project: project-name

NEXT_PUBLIC_CONVEX_URL=http://127.0.0.1:3210

# Will generally be one number higher than NEXT_PUBLIC_CONVEX_URL,
# so if your convex url is :3212, your site url will be :3213
NEXT_PUBLIC_CONVEX_SITE_URL=http://127.0.0.1:3211 # [!code ++]

# Your local site URL // [!code ++]
NEXT_PUBLIC_SITE_URL=http://localhost:3000 # [!code ++]
```

<Callout type="info">
Since the Better Auth instance runs on Convex, environment variables used by the auth instance should be configured through the Convex CLI or dashboard, not in `.env.local`.

e.g. `BETTER_AUTH_SECRET`, `GITHUB_CLIENT_ID`, `GITHUB_CLIENT_SECRET` ...
</Callout>
</Step>

<Step>
## Add Convex auth config

Add a `convex/auth.config.ts` file to configure Better Auth as an authentication provider.

```ts title="convex/auth.config.ts"
import { getAuthConfigProvider } from "@convex-dev/better-auth/auth-config";
import type { AuthConfig } from "convex/server";

export default {
  providers: [getAuthConfigProvider()],
} satisfies AuthConfig;
```
</Step>

<Step>
## Create the Better Auth Convex Component

Convex components can be installed from NPM or a local folder. While the NPM version is available [here](https://www.convex.dev/components/better-auth), this guide uses a local folder setup to unlock the full potential of Better Auth.

### Create the component definition

Create a `convex/betterAuth/convex.config.ts` file to define the component. This will signal to Convex that the `convex/betterAuth` directory is a locally installed component.

```ts title="convex/betterAuth/convex.config.ts"
import { defineComponent } from "convex/server";

const component = defineComponent("betterAuth");

export default component;
```
### Register the component

Register the Better Auth component in your Convex project.

```ts title="convex/convex.config.ts"
import { defineApp } from "convex/server";
import betterAuth from "./betterAuth/convex.config";

const app = defineApp();

app.use(betterAuth);

export default app;
```

### Create a Better Auth instance

Create a Better Auth instance and initialize the component.

<Callout type="info">
- This file is not yet complete before the next step.
- Some TypeScript errors will show until you save the file.
</Callout>

```ts title="convex/betterAuth/auth.ts"
import { createClient } from "@convex-dev/better-auth";
import { convex } from "@convex-dev/better-auth/plugins";
import type { GenericCtx } from "@convex-dev/better-auth/utils";
import type { BetterAuthOptions } from "better-auth";
import { betterAuth } from "better-auth";
import { components } from "../_generated/api";
import type { DataModel } from "../_generated/dataModel";
import authConfig from "../auth.config";
import schema from "./schema";

// Better Auth Component
export const authComponent = createClient<DataModel, typeof schema>(
  components.betterAuth,
  {
    local: { schema },
    verbose: false,
  },
);

// Better Auth Options
export const createAuthOptions = (ctx: GenericCtx<DataModel>) => {
  return {
    appName: "My App",
    baseURL: process.env.SITE_URL,
    secret: process.env.BETTER_AUTH_SECRET,
    database: authComponent.adapter(ctx),
    emailAndPassword: {
      enabled: true,
    },
    plugins: [convex({ authConfig })],
  } satisfies BetterAuthOptions;
};

// For `@better-auth/cli`
export const options = createAuthOptions({} as GenericCtx<DataModel>);

// Better Auth Instance
export const createAuth = (ctx: GenericCtx<DataModel>) => {
  return betterAuth(createAuthOptions(ctx));
};
```

### Generate the schema

After configuring your Better Auth instance, create a `convex/betterAuth/schema.ts` file and run the command below to generate the schema.

```package-install
npx @better-auth/cli generate --config ./convex/betterAuth/auth.ts --output ./convex/betterAuth/schema.ts
```

<Callout type="info">
If you need to modify your Better Auth instance, use this command to update the schema.
</Callout>

### Export adapter functions

Export adapter functions for the Better Auth component.

```ts title="convex/betterAuth/adapter.ts"
import { createApi } from "@convex-dev/better-auth";
import { createAuthOptions } from "./auth";
import schema from "./schema";

export const {
  create,
  findOne,
  findMany,
  updateOne,
  updateMany,
  deleteOne,
  deleteMany,
} = createApi(schema, createAuthOptions);
```
</Step>

<Step>
## Create the Better Auth client instance

Create the Better Auth client instance for interacting with the Better Auth server from your client.

```ts title="lib/auth-client.ts"
import { convexClient } from "@convex-dev/better-auth/client/plugins";
import { createAuthClient } from "better-auth/react";

export const authClient = createAuthClient({
  plugins: [convexClient()],
});
```
</Step>

<Step>
## Configure Next.js server helpers

Configure a set of helper functions for authenticated SSR, server functions, and route handlers.

```ts title="lib/auth-server.ts"
import { convexBetterAuthNextJs } from "@convex-dev/better-auth/nextjs";

export const {
  handler,
  preloadAuthQuery,
  isAuthenticated,
  getToken,
  fetchAuthQuery,
  fetchAuthMutation,
  fetchAuthAction,
} = convexBetterAuthNextJs({
  convexUrl: process.env.NEXT_PUBLIC_CONVEX_URL!,
  convexSiteUrl: process.env.NEXT_PUBLIC_CONVEX_SITE_URL!,
});
```
</Step>

<Step>
## Mount handlers

Register Better Auth route handlers on your Convex deployment.

```ts title="convex/http.ts"
import { httpRouter } from "convex/server";
import { authComponent, createAuth } from "./betterAuth/auth";

const http = httpRouter();

authComponent.registerRoutes(http, createAuth);

export default http;
```

Set up route handlers to proxy auth requests from Next.js to your Convex deployment.

```ts title="app/api/auth/[...all]/route.ts"
import { handler } from "@/lib/auth-server";

export const { GET, POST } = handler;
```
</Step>

<Step>
## Set up Convex client provider

Create a `ConvexClientProvider` component.

```tsx title="components/ConvexClientProvider.tsx"
"use client";

import { ConvexBetterAuthProvider } from "@convex-dev/better-auth/react";
import { ConvexReactClient } from "convex/react";
import { authClient } from "@/lib/auth-client";

const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!);

export function ConvexClientProvider({
  children,
  initialToken,
}: {
  children: React.ReactNode;
  initialToken?: string | null;
}) {
  return (
    <ConvexBetterAuthProvider
      client={convex}
      authClient={authClient}
      initialToken={initialToken}
    >
      {children}
    </ConvexBetterAuthProvider>
  );
}
```

Wrap your app with the `ConvexClientProvider` component.

```tsx title="app/layout.tsx"
import { ConvexClientProvider } from "@/components/ConvexClientProvider";
import { getToken } from "@/lib/auth-server";

export default async function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  const token = await getToken();
  return (
    <html>
      <body>
        <ConvexClientProvider initialToken={token}>
          {children}
        </ConvexClientProvider>
      </body>
    </html>
  );
}
```
</Step>
</Steps>

## Done 🎉

You're now ready to start using Better Auth with Convex.

---

## Usage

### Create your functions

Better Auth's `auth.api` methods would normally run in your server, but with Convex being your backend, these methods need to run in a Convex function. The Convex function can then be called from the client via hooks like `useMutation` or in server functions and other server code using one of the auth-server utilities like `fetchAuthMutation`.

Convex has a convention of creating functions in the `convex/` directory. For example, you can create auth-related functions in the `convex/auth.ts` file like this:

```ts title="convex/auth.ts"
import { query } from "./_generated/server";

export const getCurrentUser = query({
  args: {},
  handler: async (ctx) => {
    const identity = await ctx.auth.getUserIdentity();
    return identity;
  },
});

// ... other functions
```

### Using the Better Auth client

You can use the `authClient` like the Better Auth client normally.

```tsx title="app/sign-in/page.tsx"
"use client";

import { Button } from "@/components/ui/button";
import { authClient } from "@/lib/auth-client";

const Page = () => {
  return (
    <div className="flex flex-col items-center justify-center h-screen">
      <Button
        onClick={async () => {
          await authClient.signIn.social({
            provider: "github",
            callbackURL: "/dashboard",
          });
        }}
      >
        Sign in with GitHub
      </Button>
    </div>
  );
};

export default Page;
```

### Using the Convex React hooks

If you've created Convex functions, you can use the Convex React hooks to query or mutate data in the client side like the usual way.

```tsx title="app/dashboard/page.tsx"
"use client";

import { useQuery } from "convex/react";
import { api } from "@/convex/_generated/api";

const Page = () => {
  const user = useQuery(api.auth.getCurrentUser);

  if (user === undefined) {
    return <div>Loading...</div>;
  }

  if (user === null) {
    return <div>Unauthorized</div>;
  }

  return (
    <div>
      <pre>{JSON.stringify(user, null, 2)}</pre>
    </div>
  );
};

export default Page;
```

### Using the server helpers

You can use the server helpers to perform authentication related operations in the server side.

```tsx title="app/protected/page.tsx"
import { isAuthenticated } from "@/lib/auth-server";

const Page = async () => {
  const hasToken = await isAuthenticated();
  if (!hasToken) {
    return <div>Unauthorized</div>;
  }

  return (
    <div>
      <p>Hello</p>
    </div>
  );
};

export default Page;
```

### SSR with server components

Convex queries can be preloaded in server components and rendered in client components via `preloadAuthQuery` and `usePreloadedAuthQuery`.

<Callout type="info">
`preloadAuthQuery` is recommended for use when you want to handle different UI based on the data state, rather than for protecting resources.
</Callout>

*Preloading in a server component:*

```tsx title="app/landing/page.tsx"
import { api } from "@/convex/_generated/api";
import { preloadAuthQuery } from "@/lib/auth-server";
import Header from "./header";

const Page = async () => {
  const preloadedUserQuery = await preloadAuthQuery(api.auth.getCurrentUser);

  return (
    <div>
      <Header preloadedUserQuery={preloadedUserQuery} />
    </div>
  );
};

export default Page;
```

*Rendering preloaded data in a client component:*

```tsx title="app/landing/header.tsx"
"use client";

import { usePreloadedAuthQuery } from "@convex-dev/better-auth/nextjs/client";
import type { Preloaded } from "convex/react";
import Link from "next/link";
import { Button } from "@/components/ui/button";
import type { api } from "@/convex/_generated/api";

export const Header = ({
  preloadedUserQuery,
}: {
  preloadedUserQuery: Preloaded<typeof api.auth.getCurrentUser>;
}) => {
  const user = usePreloadedAuthQuery(preloadedUserQuery);
  return (
    <div>
      {user ? (
        <Button asChild>
          <Link href="/dashboard">Dashboard</Link>
        </Button>
      ) : (
        <Button asChild>
          <Link href="/sign-in">Sign in</Link>
        </Button>
      )}
    </div>
  );
};

export default Header;
```

## Additional Resources

- [Convex Documentation](https://docs.convex.dev/home)
- [`@convex-dev/better-auth` Documentation](https://labs.convex.dev/better-auth)
- [`@convex-dev/better-auth` GitHub Repository](https://github.com/get-convex/better-auth)