#Step by step implementation setup "Better Auth" with Convex.
22 messages · Page 1 of 1 (latest)
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
-
If starting from scratch, run:
npm create convex@latest -
Choose your frontend framework (React, Next.js, etc.). [Getting started]
-
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
-
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>)]
-
Set
SITE_URLin 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>)]
-
-
In
.env.local(created bynpx 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:
- Import
createClientfrom@convex-dev/better-auth. - Import
convexplugin from@convex-dev/better-auth/plugins. - Import
betterAuthfrombetter-auth/minimal. - Create
authComponentusingcomponents.betterAuth. - Define
createAuth(ctx)that callsbetterAuthwith:baseURLortrustedOrigins/siteUrldatabase: authComponent.adapter(ctx)emailAndPasswordconfigplugins: [convex({ authConfig }), ...]
- Optionally export a
getCurrentUserquery usingauthComponent.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.tswithdefineComponent("betterAuth"). - Add a static
authexport inconvex/betterAuth/auth.tsfor schema generation. - Run
npx @better-auth/cli generate -yinsideconvex/betterAuth. - Split out
createAuthOptionsfromcreateAuth. - Export adapter functions from
convex/betterAuth/adapter.ts. - Update
convex/convex.config.tsandconvex/auth.tsto 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.
@sand lark I forgot to say that I use Next.js, please update it with Next.js.
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
-
In your Next.js app root:
npm create convex@latest -
Keep Convex dev server running:
npx convex devThis 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;
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;
5. Set environment variables
5.1. In Convex (via CLI)
-
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>)] -
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>
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()],
});
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>
);
}
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,
});
},
});
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,
});
}
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:
- JWT caching for faster SSR token usage. [Experimental]
- Static JWKS to avoid JWKS HTTP calls on every request. [Experimental; Convex plugin]
These require:
-
Adding an internal action
getLatestJwksinconvex/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
internalActionfrom typical Convex usage, so treat this line with caution.) [Experimental] -
Running:
npx convex run auth:getLatestJwks | npx convex env set JWKS -
Passing
jwks: process.env.JWKSinto both:getAuthConfigProvider({ jwks: process.env.JWKS })inauth.config.ts.convex({ authConfig, jwks: process.env.JWKS })increateAuth. [Experimental; Convex plugin]