Better Auth Convex Local Integration
better-auth-convex is a JavaScript/TypeScript library designed to integrate the better-auth authentication solution directly into a Convex application's schema, offering an alternative to the official component-based approach. The current stable version is 0.5.1, with development showing a consistent release cadence of patch and minor updates. Its primary differentiation lies in placing authentication tables within the application's own schema, allowing for direct database access without the latency associated with ctx.runQuery or ctx.runMutation overhead. This approach also ensures a unified context, enabling auth triggers to directly access and modify application tables transactionally, and provides full TypeScript inference across the entire schema. This library requires better-auth and @convex-dev/better-auth as peer dependencies and is primarily used in a Node.js/Convex environment.
Common errors
-
Module not found: Error: Can't resolve 'better-auth-convex' in '...'
cause The `dist` folder, which contains the compiled JavaScript output, was missing from the published npm package in some earlier versions, preventing bundlers from resolving the package correctly.fixUpgrade `better-auth-convex` to version `0.4.6` or later to ensure the `dist` folder and compiled assets are properly included in the npm package. -
Convex bundler error: 'path' module not found
cause Prior to `v0.4.6`, the `createSchema` utility was directly exported from the main package entry point, which caused issues with the Convex bundler trying to resolve Node.js-specific built-in modules like `path` that are not available in the Convex environment.fixFor `createSchema`, explicitly import it from the dedicated subpath: `import { createSchema } from 'better-auth-convex/schema'`. Ensure you are using `better-auth-convex@0.4.6` or a newer version where this fix was implemented.
Warnings
- breaking This package fundamentally alters where Better Auth tables are stored, moving them from a component schema into your main application schema. This requires a manual migration script if you are moving an existing `@convex-dev/better-auth` component-based deployment to `better-auth-convex`.
- breaking Version 0.5.0 introduces compatibility with `@convex-dev/better-auth@0.10.4` and `better-auth@1.4.7`. Migrating to this version requires following the upstream migration guide for `@convex-dev/better-auth@0.10` to ensure API consistency.
- breaking In version 0.5.1, the `getLatestJwks` function was changed from an internal mutation to an internal action. This affects how it is called and defined in your Convex internal API.
- gotcha By default, internal API functions generated by `createApi` include typed validators, which can significantly increase your bundle size, especially with complex schemas. For internal functions where type validation is less critical or handled by other mechanisms, this can lead to larger deployment bundles and slower build times.
Install
-
npm install better-auth-convex -
yarn add better-auth-convex -
pnpm add better-auth-convex
Imports
- createClient
const { createClient } = require('better-auth-convex')import { createClient } from 'better-auth-convex' - createApi
const { createApi } = require('better-auth-convex')import { createApi } from 'better-auth-convex' - AuthFunctions
import { AuthFunctions } from 'better-auth-convex'import type { AuthFunctions } from 'better-auth-convex' - createSchema
import { createSchema } from 'better-auth-convex'import { createSchema } from 'better-auth-convex/schema'
Quickstart
// convex/auth.config.ts
import { getAuthConfigProvider } from "@convex-dev/better-auth/auth-config";
import type { AuthConfig } from "convex/server";
export default {
providers: [getAuthConfigProvider({ jwks: process.env.JWKS ?? '' })],
} satisfies AuthConfig;
// convex/auth.ts
import { betterAuth } from "better-auth";
import { convex } from "@convex-dev/better-auth/plugins";
import { admin, organization } from "better-auth/plugins"; // Optional plugins
import {
type AuthFunctions,
createClient,
createApi
} from "better-auth-convex";
import { internal } from "./_generated/api";
import type { MutationCtx, QueryCtx, GenericCtx } from "./_generated/server";
import type { DataModel } from "./_generated/dataModel";
import schema from "./schema"; // YOUR app schema with auth tables
import authConfig from "./auth.config";
// 1. Internal API functions for auth operations
const authFunctions: AuthFunctions = internal.auth;
// 2. Auth client with triggers that run in your app context
export const authClient = createClient<DataModel, typeof schema>({
authFunctions,
schema,
triggers: {
user: {
beforeCreate: async (_ctx, data) => {
const username =
data.username?.trim() ||
data.email?.split("@")[0] ||
`user-${Date.now()}`;
return {
...data,
username
};
},
onCreate: async (ctx, user) => {
const orgId = await ctx.db.insert("organization", {
name: `${user.name}'s Workspace`,
slug: `personal-${user._id}`
});
await ctx.db.patch(user._id, {
personalOrganizationId: orgId
});
},
beforeDelete: async (ctx, user) => {
if (user.personalOrganizationId) {
await ctx.db.delete(user.personalOrganizationId);
}
return user;
}
},
session: {
onCreate: async (ctx, session) => {
// Handle session creation logic, e.g., logging or analytics
}
}
}
});
export const auth = betterAuth<
typeof schema,
MutationCtx<DataModel>,
QueryCtx<DataModel>,
GenericCtx<DataModel>
>({
authConfig,
authClient,
plugins: [
convex({
internal,
authClient,
schema
}),
admin({ authClient }),
organization({ authClient })
]
});