#Step by step implementation setup "Better Auth" with Convex.

22 messages · Page 1 of 1 (latest)

spring flume
#

Give me a step by step implementation plan how to setup "Better Auth" with Convex. I need a good plan that I can give to my LLM AI.

sand larkBOT
#

Hi @spring flume. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:

#

Here’s a concrete, step‑by‑step implementation plan you can hand to an LLM. It’s written to be framework‑agnostic first, then branches by framework.


1. Create or open a Convex project

  1. If starting from scratch, run:

    npm create convex@latest
    
  2. Choose your frontend framework (React, Next.js, etc.). [Getting started]

  3. Keep the Convex dev server running:

    npx convex dev
    ``` [[Getting started](<https://labs.convex.dev/better-auth>)]
    
    

2. Install Better Auth + Convex component

In your project root:

npm install convex@latest @convex-dev/better-auth
npm install [email protected] --save-exact

Use the same commands for all supported frameworks (React Vite, Next.js, SvelteKit, Expo, TanStack Start). [React install; Next install; SvelteKit install; Expo install; TanStack install]


3. Register the Better Auth Convex component

Edit convex/convex.config.ts (or src/convex/convex.config.ts for SvelteKit):

import { defineApp } from "convex/server";
import betterAuth from "@convex-dev/better-auth/convex.config";

const app = defineApp();
app.use(betterAuth);

export default app;
#

[React install; Next install; SvelteKit install; Expo install; TanStack install]


4. Configure Convex Auth to use Better Auth

Create convex/auth.config.ts (or src/convex/auth.config.ts for SvelteKit):

import { getAuthConfigProvider } from "@convex-dev/better-auth/auth-config";
import type { AuthConfig } from "convex/server";

export default {
  providers: [getAuthConfigProvider()],
} satisfies AuthConfig;

[React install; Next install; SvelteKit install; Expo install; TanStack install]


5. Set required environment variables

  1. Generate the Better Auth secret:

    npx convex env set BETTER_AUTH_SECRET=$(openssl rand -base64 32)
    ``` [[React install](<https://labs.convex.dev/better-auth/framework-guides/react#installation>); [Next install](<https://labs.convex.dev/better-auth/framework-guides/next>); [SvelteKit install](<https://labs.convex.dev/better-auth/framework-guides/sveltekit#installation>); [Expo install](<https://labs.convex.dev/better-auth/framework-guides/expo>); [TanStack install](<https://labs.convex.dev/better-auth/framework-guides/tanstack-start#installation>)]
#
  1. Set SITE_URL in Convex (adjust port per framework):

    • React Vite:

      npx convex env set SITE_URL <http://localhost:5173>
      ``` [[React install](<https://labs.convex.dev/better-auth/framework-guides/react#installation>)]
      
      
    • Next.js / TanStack Start:

      npx convex env set SITE_URL <http://localhost:3000>
      ``` [[Next install](<https://labs.convex.dev/better-auth/framework-guides/next>); [TanStack install](<https://labs.convex.dev/better-auth/framework-guides/tanstack-start#installation>)]
      
      
    • SvelteKit:

      npx convex env set SITE_URL <http://localhost:5173>
      ``` [[SvelteKit install](<https://labs.convex.dev/better-auth/framework-guides/sveltekit#installation>)]
      
      
  2. In .env.local (created by npx convex dev), set Convex URLs and site URL for your framework:

    • Next.js:

      CONVEX_DEPLOYMENT=dev:adjective-animal-123
      
      NEXT_PUBLIC_CONVEX_URL=<https://adjective-animal-123.convex.cloud>
      NEXT_PUBLIC_CONVEX_SITE_URL=<https://adjective-animal-123.convex.site>
      NEXT_PUBLIC_SITE_URL=<http://localhost:3000>
      ``` [[Next install](<https://labs.convex.dev/better-auth/framework-guides/next>)]
      
      
    • React Vite:

      CONVEX_DEPLOYMENT=dev:adjective-animal-123
      
      VITE_CONVEX_URL=<https://adjective-animal-123.convex.cloud>
      VITE_CONVEX_SITE_URL=<https://adjective-animal-123.convex.site>
      VITE_SITE_URL=<http://localhost:5173>
      ``` [[React install](<https://labs.convex.dev/better-auth/framework-guides/react#installation>)]
      
      
    • SvelteKit:

      CONVEX_DEPLOYMENT=dev:adjective-animal-123
#

PUBLIC_CONVEX_URL=https://adjective-animal-123.convex.cloud
PUBLIC_CONVEX_SITE_URL=https://adjective-animal-123.convex.site
PUBLIC_SITE_URL=http://localhost:5173
``` [SvelteKit install]

  • Expo:

    CONVEX_DEPLOYMENT=dev:adjective-animal-123
    
    EXPO_PUBLIC_CONVEX_URL=<https://adjective-animal-123.convex.cloud>
    EXPO_PUBLIC_CONVEX_SITE_URL=<https://adjective-animal-123.convex.site>
    ``` [[Expo install](<https://labs.convex.dev/better-auth/framework-guides/expo>)]
    
    
  • TanStack Start:

    CONVEX_DEPLOYMENT=dev:adjective-animal-123
    
    VITE_CONVEX_URL=<https://adjective-animal-123.convex.cloud>
    VITE_CONVEX_SITE_URL=<https://adjective-animal-123.convex.site>
    VITE_SITE_URL=<http://localhost:3000>
    ``` [[TanStack install](<https://labs.convex.dev/better-auth/framework-guides/tanstack-start#installation>)]
    
    

6. Create the Better Auth server instance (convex/auth.ts)

This file wires Better Auth to Convex via the component client and plugin.

6.1. Common pattern

All frameworks follow the same pattern:

  1. Import createClient from @convex-dev/better-auth.
  2. Import convex plugin from @convex-dev/better-auth/plugins.
  3. Import betterAuth from better-auth/minimal.
  4. Create authComponent using components.betterAuth.
  5. Define createAuth(ctx) that calls betterAuth with:
    • baseURL or trustedOrigins / siteUrl
    • database: authComponent.adapter(ctx)
    • emailAndPassword config
    • plugins: [convex({ authConfig }), ...]
  6. Optionally export a getCurrentUser query using authComponent.getAuthUser(ctx).

Next.js example

#
// convex/auth.ts
import { createClient, type GenericCtx } from "@convex-dev/better-auth";
import { convex } from "@convex-dev/better-auth/plugins";
import { components } from "./_generated/api";
import { DataModel } from "./_generated/dataModel";
import { query } from "./_generated/server";
import { betterAuth } from "better-auth/minimal";
import authConfig from "./auth.config";

const siteUrl = process.env.SITE_URL!;

// Component client
export const authComponent = createClient<DataModel>(components.betterAuth);

export const createAuth = (ctx: GenericCtx<DataModel>) => {
  return betterAuth({
    baseURL: siteUrl,
    database: authComponent.adapter(ctx),
    emailAndPassword: {
      enabled: true,
      requireEmailVerification: false,
    },
    plugins: [
      convex({ authConfig }), // required
    ],
  });
};

// Optional helper
export const getCurrentUser = query({
  args: {},
  handler: async (ctx) => {
    return authComponent.getAuthUser(ctx);
  },
});
``` [[Next guide](<https://labs.convex.dev/better-auth/framework-guides/next>)]

#### React Vite example (note `crossDomain` plugin)

```ts
// convex/auth.ts
import { createClient, type GenericCtx } from "@convex-dev/better-auth";
import { convex, crossDomain } from "@convex-dev/better-auth/plugins";
import { components } from "./_generated/api";
import { DataModel } from "./_generated/dataModel";
import { query } from "./_generated/server";
import { betterAuth, type BetterAuthOptions } from "better-auth/minimal";
import authConfig from "./auth.config";

const siteUrl = process.env.SITE_URL!;

export const authComponent = createClient<DataModel>(components.betterAuth);
#

export const createAuth = (ctx: GenericCtx<DataModel>) => {
return betterAuth({
trustedOrigins: [siteUrl],
database: authComponent.adapter(ctx),
emailAndPassword: {
enabled: true,
requireEmailVerification: false,
},
plugins: [
crossDomain({ siteUrl }), // required for client-side frameworks
convex({ authConfig }), // required for Convex
],
});
};

export const getCurrentUser = query({
args: {},
handler: async (ctx) => {
return authComponent.getAuthUser(ctx);
},
});


SvelteKit, Expo, and TanStack Start follow the same pattern with minor differences (e.g., Expo uses the `expo()` plugin). [[SvelteKit guide](<https://labs.convex.dev/better-auth/framework-guides/sveltekit#installation>); [Expo guide](<https://labs.convex.dev/better-auth/framework-guides/expo>); [TanStack guide](<https://labs.convex.dev/better-auth/framework-guides/tanstack-start#installation>)]

---

## 7. Create the Better Auth client instance

This is the client‑side Better Auth SDK, configured with the Convex plugin.

### 7.1. React / Next / TanStack Start

Create `src/lib/auth-client.ts`:

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

export const authClient = createAuthClient({
  plugins: [convexClient()],
});
``` [[Next guide](<https://labs.convex.dev/better-auth/framework-guides/next>); [TanStack guide](<https://labs.convex.dev/better-auth/framework-guides/tanstack-start#installation>)]

For React Vite with cross‑domain:

```ts
import { createAuthClient } from "better-auth/react";
import {
  convexClient,
  crossDomainClient,
} from "@convex-dev/better-auth/client/plugins";
#

export const authClient = createAuthClient({
baseURL: import.meta.env.VITE_CONVEX_SITE_URL,
plugins: [convexClient(), crossDomainClient()],
});


### 7.2. SvelteKit

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

export const authClient = createAuthClient({
  plugins: [convexClient()],
});
``` [[SvelteKit guide](<https://labs.convex.dev/better-auth/framework-guides/sveltekit#installation>)]

### 7.3. Expo

Expo uses `@better-auth/expo` on the server side; client setup is in Better Auth’s Expo docs (not detailed in the provided sources beyond plugin usage). The knowledge sources don’t show a dedicated Expo client snippet beyond the server `expo()` plugin. [[Expo guide](<https://labs.convex.dev/better-auth/framework-guides/expo>)]

---

## 8. Wrap your app with the Convex + Better Auth provider

This is where you replace the standard `ConvexProvider` with the Better Auth‑aware provider.

### 8.1. Next.js

Create `app/ConvexClientProvider.tsx`:

```ts
"use client";

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

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

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


Use this provider at the root of your app (e.g., in `app/layout.tsx`).

### 8.2. React (Vite SPA)

In `src/main.tsx`:

```ts
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import "./index.css";
import { ConvexReactClient } from "convex/react";
import { ConvexBetterAuthProvider } from "@convex-dev/better-auth/react"; 
import { authClient } from "@/lib/auth-client"; 

const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL as string, {
  // Optionally pause queries until the user is authenticated
  expectAuth: true, 
});

ReactDOM.createRoot(document.getElementById("root")!).render(
  <React.StrictMode>
    <ConvexBetterAuthProvider client={convex} authClient={authClient}>
      <App />
    </ConvexBetterAuthProvider>
  </React.StrictMode>
);
``` [[React provider](<https://labs.convex.dev/better-auth/framework-guides/react#set-up-convex-client-provider>)]

SvelteKit, Expo, and TanStack Start have their own framework‑specific wiring (not fully shown in the provided snippets, but follow the same pattern: Convex client + Better Auth client + framework adapter).

---

## 9. Use Better Auth in Convex functions (optional but important)

You can call Better Auth server methods via `auth.api` inside Convex functions using `createAuth` and `authComponent.getAuth`. [[Basic usage](<https://labs.convex.dev/better-auth/basic-usage>)]

Example: change password mutation:
#
import { mutation } from "./_generated/server";
import { v } from "convex/values";
import { authComponent, createAuth } from "./auth";

export const updateUserPassword = mutation({
  args: {
    currentPassword: v.string(),
    newPassword: v.string(),
  },
  handler: async (ctx, args) => {
    const { auth, headers } = await authComponent.getAuth(createAuth, ctx);
    await auth.api.changePassword({
      body: {
        currentPassword: args.currentPassword,
        newPassword: args.newPassword,
      },
      headers,
    });
  },
});
``` [[Basic usage](<https://labs.convex.dev/better-auth/basic-usage>)]

---

## 10. (Optional) Advanced / experimental optimizations

If needed, you can later add:

1. **JWT caching** to speed up SSR token fetching. [[Experimental](<https://labs.convex.dev/better-auth/experimental>)]
2. **Static JWKS** to avoid JWKS HTTP calls on every request. [[Experimental](<https://labs.convex.dev/better-auth/experimental>); [Convex plugin](<https://labs.convex.dev/better-auth/api/convex-plugin>)]

These involve:

- Adding an internal action `getLatestJwks` in `convex/auth.ts`.
- Running it via `npx convex run auth:getLatestJwks | npx convex env set JWKS`.
- Passing `jwks: process.env.JWKS` into the `convex` plugin options.

---

## 11. (Optional) Local install mode

If you want full control over the Better Auth schema and to use additional plugins, you can convert to a “local install”:
#
  • Create convex/betterAuth/convex.config.ts with defineComponent("betterAuth").
  • Add a static auth export in convex/betterAuth/auth.ts for schema generation.
  • Run npx @better-auth/cli generate -y inside convex/betterAuth.
  • Split out createAuthOptions from createAuth.
  • Export adapter functions from convex/betterAuth/adapter.ts.
  • Update convex/convex.config.ts and convex/auth.ts to use the local component and schema. [Local install]

If you tell me your exact framework (Next.js, React Vite, SvelteKit, Expo, TanStack Start), I can collapse this into a single, linear script tailored to that stack.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@spring flume Kapa.ai is still learning and improving, please let me know how I did by reacting below.

spring flume
#

@sand lark I forgot to say that I use Next.js, please update it with Next.js.

sand larkBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

Here’s a Next.js‑specific, linear plan you can hand to an LLM to implement Convex + Better Auth.


1. Create / open the Convex project

  1. In your Next.js app root:

    npm create convex@latest
    
  2. Keep Convex dev server running:

    npx convex dev
    

    This initializes the deployment and keeps types up to date. [Getting started]


2. Install Convex + Better Auth packages

From your Next.js project root:

npm install convex@latest @convex-dev/better-auth
npm install [email protected] --save-exact

This is the required combo for Next.js. [Next guide]


3. Register the Better Auth Convex component

Edit convex/convex.config.ts:

import { defineApp } from "convex/server";
import betterAuth from "@convex-dev/better-auth/convex.config";

const app = defineApp();
app.use(betterAuth);

export default app;

[Next guide]


4. Configure Convex auth to use Better Auth

Create 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;

[Next guide]


5. Set environment variables

5.1. In Convex (via CLI)

  1. Generate Better Auth secret:

    npx convex env set BETTER_AUTH_SECRET=$(openssl rand -base64 32)
    ``` [[Next guide](<https://labs.convex.dev/better-auth/framework-guides/next>)]
    
    
  2. Set your site URL (Next.js dev default):

#
   npx convex env set SITE_URL <http://localhost:3000>
   ``` [[Next guide](<https://labs.convex.dev/better-auth/framework-guides/next>)]

### 5.2. In `.env.local` (Next.js app)

`npx convex dev` creates `.env.local`. Add:

```env
# Deployment used by `npx convex dev`
CONVEX_DEPLOYMENT=dev:adjective-animal-123 # replace with your actual deployment

NEXT_PUBLIC_CONVEX_URL=<https://adjective-animal-123.convex.cloud>
# Same as NEXT_PUBLIC_CONVEX_URL but ends in .site
NEXT_PUBLIC_CONVEX_SITE_URL=<https://adjective-animal-123.convex.site>

# Your local site URL
NEXT_PUBLIC_SITE_URL=<http://localhost:3000>

[Next guide]


6. Create the Better Auth server instance (convex/auth.ts)

Create convex/auth.ts with:

import { createClient, type GenericCtx } from "@convex-dev/better-auth";
import { convex } from "@convex-dev/better-auth/plugins";
import { components } from "./_generated/api";
import { DataModel } from "./_generated/dataModel";
import { query } from "./_generated/server";
import { betterAuth } from "better-auth/minimal";
import authConfig from "./auth.config";

const siteUrl = process.env.SITE_URL!;

// Component client: integration helpers + adapter
export const authComponent = createClient<DataModel>(components.betterAuth);

export const createAuth = (ctx: GenericCtx<DataModel>) => {
  return betterAuth({
    baseURL: siteUrl,
    database: authComponent.adapter(ctx),
    // Simple email/password to get started
    emailAndPassword: {
      enabled: true,
      requireEmailVerification: false,
    },
    plugins: [
      // Required for Convex compatibility
      convex({ authConfig }),
    ],
  });
};
#

// Example helper to get current user
export const getCurrentUser = query({
args: {},
handler: async (ctx) => {
return authComponent.getAuthUser(ctx);
},
});


[[Next guide](<https://labs.convex.dev/better-auth/framework-guides/next>)]

---

## 7. Create the Better Auth client instance (`src/lib/auth-client.ts`)

Create `src/lib/auth-client.ts`:

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

export const authClient = createAuthClient({
  plugins: [convexClient()],
});

[Next guide]


8. Set up the Convex + Better Auth provider

8.1. Create app/ConvexClientProvider.tsx

"use client";

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

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

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

[Next provider]

8.2. Wrap your root layout

In app/layout.tsx, wrap your app with ConvexClientProvider:

import type { Metadata } from "next";
import "./globals.css";
import { ConvexClientProvider } from "@/app/ConvexClientProvider";

export const metadata: Metadata = {
  title: "Your App",
  description: "Your description",
};
#

export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<ConvexClientProvider>{children}</ConvexClientProvider>
</body>
</html>
);
}


(The exact font imports / CSS are up to your app; the key is wrapping with `ConvexClientProvider`.) [[Next provider](<https://labs.convex.dev/better-auth/framework-guides/next#set-up-convex-client-provider>)]

---

## 9. Use Better Auth from Convex functions (server‑side auth logic)

When you need Better Auth’s server methods (`auth.api.*`), call them **inside Convex functions**, not directly in Next.js server code. [[Next usage](<https://labs.convex.dev/better-auth/framework-guides/next#using-better-auth-in-server-code>); [Basic usage](<https://labs.convex.dev/better-auth/basic-usage>)]

Example: change password mutation:

```ts
// convex/users.ts
import { mutation } from "./_generated/server";
import { v } from "convex/values";
import { createAuth, authComponent } from "./auth";

export const updateUserPassword = mutation({
  args: {
    currentPassword: v.string(),
    newPassword: v.string(),
  },
  handler: async (ctx, args) => {
    // Get Better Auth instance + headers (for cookies/session)
    const { auth, headers } = await authComponent.getAuth(createAuth, ctx);

    await auth.api.changePassword({
      body: {
        currentPassword: args.currentPassword,
        newPassword: args.newPassword,
      },
      headers,
    });
  },
});

[Next usage; Basic usage]

You can then call this from a Next.js server action using fetchAuthMutation (see next step).


10. Call Convex from Next.js server components / actions (optional)

#

The docs reference utilities like fetchAuthMutation / preloadAuthQuery in a helper module (e.g. lib/auth-server.ts), but the exact implementation of those helpers is not shown in the provided sources. The knowledge sources only show how they are used, not how to define them, so I can’t safely provide their implementation. [Next usage]

Usage example from the docs (for your LLM to wire up once it has the helper):

// app/actions.ts
"use server";

import { fetchAuthMutation } from "@/lib/auth-server";
import { api } from "../convex/_generated/api";

export async function updatePassword({
  currentPassword,
  newPassword,
}: {
  currentPassword: string;
  newPassword: string;
}) {
  await fetchAuthMutation(api.users.updatePassword, {
    currentPassword,
    newPassword,
  });
}

[Next usage]

Similarly, SSR preloading uses preloadAuthQuery / usePreloadedAuthQuery, but only their usage is shown, not their definitions. [Next usage]


11. Use Better Auth on the client (sign in / sign out, etc.)

The Better Auth client (authClient) is a standard Better Auth client; you use it as in Better Auth’s own docs (sign in, sign out, social providers, etc.). The Convex docs state that Better Auth’s basic usage guide applies directly, with a few exceptions (e.g., sign‑in/out must be done from the client, not Convex functions). [Basic usage]

Key Convex‑specific note:

#
  • Sign up / sign in / sign out must be done from the client via authClient.signIn.* etc., not from Convex functions, because Convex functions don’t set HTTP cookies. [Basic usage]

12. (Optional later) Experimental performance optimizations

Once everything works, you can optionally add:

These require:

  1. Adding an internal action getLatestJwks in convex/auth.ts:

    // inside convex/auth.ts
    import { internalAction } from "./_generated/server";
    
    export const getLatestJwks = internalAction({
      args: {},
      handler: async (ctx) => {
        const auth = createAuth(ctx);
        return await auth.api.getLatestJwks();
      },
    });
    

    (The docs show this pattern but not the import line; I’ve inferred internalAction from typical Convex usage, so treat this line with caution.) [Experimental]

  2. Running:

    npx convex run auth:getLatestJwks | npx convex env set JWKS
    
  3. Passing jwks: process.env.JWKS into both:

    • getAuthConfigProvider({ jwks: process.env.JWKS }) in auth.config.ts.
    • convex({ authConfig, jwks: process.env.JWKS }) in createAuth. [Experimental; Convex plugin]

#

This is the full Next.js‑specific plan based on the available docs. You can now give this to your LLM and have it implement each step in your repo.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: