Schema Design
Convex schemas define the shape of every document in your database. They provide runtime validation and generate TypeScript types that flow through your entire application.
Defining a schema
Use defineSchema and defineTable from convex/server to declare tables and their fields.
// convex/schema.ts
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";
export default defineSchema({
projects: defineTable({
name: v.string(),
description: v.optional(v.string()),
isActive: v.boolean(),
createdAt: v.number(),
}).index("by_active", ["isActive"]),
tasks: defineTable({
projectId: v.id("projects"),
title: v.string(),
priority: v.union(v.literal("low"), v.literal("medium"), v.literal("high")),
tags: v.array(v.string()),
metadata: v.object({
assignee: v.optional(v.string()),
dueDate: v.optional(v.number()),
}),
})
.index("by_project", ["projectId"])
.index("by_priority", ["priority"]),
});
Field validators
| Validator | Description | Example |
|---|---|---|
v.string() | UTF-8 string | name: v.string() |
v.number() | 64-bit float | count: v.number() |
v.boolean() | True or false | isActive: v.boolean() |
v.id("table") | Reference to another table | authorId: v.id("users") |
v.array(inner) | Ordered list | tags: v.array(v.string()) |
v.object({...}) | Nested object | meta: v.object({ k: v.string() }) |
v.optional(inner) | Field may be absent | bio: v.optional(v.string()) |
v.union(a, b) | One of several types | v.union(v.string(), v.null()) |
v.literal(val) | Exact value | v.literal("admin") |
Indexes
Indexes speed up queries that filter or sort on specific fields. Declare them by chaining
.index() on a table definition.
defineTable({ status: v.string(), createdAt: v.number() })
.index("by_status_created", ["status", "createdAt"])
- The first fields in the index are used for equality filters.
- The last field can be used for range filters or ordering.
- Every table automatically has an index on
_creationTime.
Tips
- Keep schemas strict — avoid
v.any()unless you genuinely need it. - Use
v.id("tableName")instead of raw strings for foreign keys. - Add indexes for every query pattern you plan to use in production.