Database Schema
Understanding the Convex database schema and data models
Database Schema
The database schema is auto-generated by the Better Auth CLI. You don't need to manually define or modify auth tables.
We use better-convex for Better Auth integration. The auth schema lives in your main Convex database, not as a separate Convex Component. This means you can directly reference users, sessions, and organizations in your Convex functions without going through a component layer.
Direct Access
Since auth tables are in your main database, you can query them directly:
import { query } from "./_generated/server";
import { v } from "convex/values";
export const getUser = query({
args: { userId: v.string() },
handler: async (ctx, args) => {
return await ctx.db
.query("users")
.filter((q) => q.eq(q.field("id"), args.userId))
.first();
},
});Extending the Schema
When adding your own features, extend the schema alongside the auth tables:
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";
// Import Better Auth schema
import { betterAuthSchema } from "./betterAuth/schema";
export default defineSchema({
// Include Better Auth tables
...betterAuthSchema,
// Add your own tables
projects: defineTable({
name: v.string(),
description: v.optional(v.string()),
ownerId: v.string(), // Reference to user.id
organizationId: v.optional(v.string()),
createdAt: v.number(),
})
.index("by_owner", ["ownerId"])
.index("by_organization", ["organizationId"]),
});Relationships
Reference auth data directly in your queries:
// Get project with owner details
export const getProjectWithOwner = query({
args: { projectId: v.id("projects") },
handler: async (ctx, args) => {
const project = await ctx.db.get(args.projectId);
if (!project) return null;
const owner = await ctx.db
.query("users")
.filter((q) => q.eq(q.field("id"), project.ownerId))
.first();
return { ...project, owner };
},
});Resources
- Convex Schema Documentation
- Convex Components - Understanding the difference
- Better Auth Database