Better Auth Convex Local Integration

0.5.1 · active · verified Wed Apr 22

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

Warnings

Install

Imports

Quickstart

This quickstart demonstrates the core setup for `better-auth-convex`, including defining the `auth.config.ts` and `auth.ts` files. It shows how to create an `authClient` with custom `user` and `session` triggers for lifecycle management, such as setting a default username, creating a personal organization for new users, and cleaning up data upon user deletion. It also illustrates how to combine these with `betterAuth` and Convex-specific plugins.

// 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 })
  ]
});

view raw JSON →