Skip to content

Convex + Better Auth

Comprehensive, secure authentication for Convex apps using Better Auth.

This library is a Convex Component that provides an integration layer for using Better Auth with Convex.

After following the installation and setup steps below, you can use Better Auth in the normal way. Some exceptions will apply for certain configuration options, apis, and plugins.

Check out the Better Auth docs for usage information, plugins, and more.



To quickly setup and test out this plugin, try out one of these templates:


You’ll first need a project on Convex where npx convex dev has been run on your local machine. If you don’t have one, run npm create convex@latest to get started, and check out the docs to learn more.

To get started, install the component, and a pinned version of Better Auth:

Terminal window
npm install @convex-dev/better-auth
npm install better-auth@1.2.7 --save-exact

then in your convex.config.ts file, add the plugin:

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;

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


convex/auth.config.ts
export default {
providers: [
{
domain: process.env.CONVEX_SITE_URL,
applicationID: "convex",
},
],
};

Generate a secret for encryption and generating hashes. Use the command below if you have openssl installed, or generate a random value here, or whatever other method you prefer.


Terminal window
npx convex env set BETTER_AUTH_SECRET=$(openssl rand -base64 32)


Add the Convex site URL environment variable to the .env.local file created by npx convex dev. It will be picked up by your framework dev server.


Terminal window
# Deployment used by npx convex dev
CONVEX_DEPLOYMENT=dev:adjective-animal-123 # team: team-name, project: project-name
VITE_CONVEX_URL=https://adjective-animal-123.convex.cloud
# Same as VITE_CONVEX_URL but ends in .site
VITE_CONVEX_SITE_URL=https://adjective-animal-123.convex.site

First, add a users schema, name it whatever you’d like:

convex/schema.ts
import { defineSchema, defineTable } from "convex/server";
export default defineSchema({
users: defineTable({
// Fields are optional
}),
});

Now, create your Better Auth instance:



convex/auth.ts
10 collapsed lines
import {
BetterAuth,
convexAdapter,
type AuthFunctions,
} from "@convex-dev/better-auth";
import { convex } from "@convex-dev/better-auth/plugins";
import { betterAuth } from "better-auth";
import { api, components, internal } from "./_generated/api";
import { query, type GenericCtx } from "./_generated/server";
import type { Id, DataModel } from "./_generated/dataModel";
// Typesafe way to pass Convex functions defined in this file
const authFunctions: AuthFunctions = internal.auth;
// Initialize the component
export const betterAuthComponent = new BetterAuth(
components.betterAuth,
{
authFunctions,
}
);
export const createAuth = (ctx: GenericCtx) =>
// Configure your Better Auth instance here
betterAuth({
database: convexAdapter(ctx, betterAuthComponent),
5 collapsed lines
// Simple non-verified email/password to get started
emailAndPassword: {
enabled: true,
requireEmailVerification: false,
},
plugins: [
// The Convex plugin is required
convex(),
// The cross domain plugin is required for client side frameworks
crossDomain({
siteUrl: "http://localhost:5173",
}),
],
});

Register Better Auth route handlers on your Convex deployment.

convex/http.ts
import { httpRouter } from "convex/server";
import { betterAuthComponent, createAuth } from "./auth";
const http = httpRouter();
betterAuthComponent.registerRoutes(http, createAuth);
export default http

for full-stack framworks, you also need to proxy auth requests from your backend to Convex:

No changes needed for React!

src/lib/auth-client.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(),
],
});

Wrap your app in the ConvexBetterAuthProvider:

src/main.tsx
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);
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<ConvexBetterAuthProvider client={convex} authClient={authClient}>
<App />
</ConvexBetterAuthProvider>
</React.StrictMode>
);

and lastly for Tanstack Start, you also need to provide context from Convex to your routes:

…although for React, you can just skip this part :)