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

ValidatorDescriptionExample
v.string()UTF-8 stringname: v.string()
v.number()64-bit floatcount: v.number()
v.boolean()True or falseisActive: v.boolean()
v.id("table")Reference to another tableauthorId: v.id("users")
v.array(inner)Ordered listtags: v.array(v.string())
v.object({...})Nested objectmeta: v.object({ k: v.string() })
v.optional(inner)Field may be absentbio: v.optional(v.string())
v.union(a, b)One of several typesv.union(v.string(), v.null())
v.literal(val)Exact valuev.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.