Skip to main content

Defining a Schema

A schema is a description of

  1. the tables in your Convex project
  2. the type of documents within your tables

While it is possible to use Convex without defining a schema, adding a schema.ts file will give you additional type safety throughout your app.

We recommend beginning your project without a schema for rapid prototyping and then adding a schema once you've solidified your plan. To learn more see our Schema Philosophy.

Schema Enforcement

Currently schemas are a "types only" feature. Adding a schema will give you precise, code generated types, but won't change the runtime behavior of your application.

In the future Convex will support enforcing your schema and rejecting documents that don't match it.

Writing Schemas

Schemas are defined in a schema.ts file in your convex/ directory and look like:

import { defineSchema, defineTable, s } from "convex/schema";

export default defineSchema({
channels: defineTable({
name: s.string(),
messages: defineTable({
body: s.string(),
users: defineTable({
name: s.string(),
tokenIdentifier: s.string(),
}).index("by_token", ["tokenIdentifier"]),

This schema (which is based on our Users and Auth tutorial), has 3 tables: channels, messages, and users. Each table is defined using the defineTable function. Within each table, the document type is defined using our schema builder, s. In addition to the fields listed, Convex will also automatically add _id and _creationTime fields. To learn more, see System Fields.

Generating a Schema

While writing your schema, it can be helpful to consult the Convex Dashboard. The "Generate Schema" button in the "Data" view suggests a schema declaration based on the data in your tables.

Supported Types

The schema builder supports all of the Convex types. It also supports union and literal types.


You can describe fields that could be one of multiple types using s.union:

stringOrNumber: s.union(s.string(), s.number()),

If your table stores multiple different types of documents, you can also use s.union at the top level:

string: s.string(),
number: s.number(),


Fields that are a constant can be expressed with s.literal:

oneTwoOrThree: s.union(

Using Schemas

Once you've defined a schema, npx convex dev will produce new versions of dataModel.d.ts and server.d.ts.

The Document type from dataModel.d.ts provides document types for all of your tables. You can use these both when writing Convex functions and in your React components:

import { Document } from "../convex/_generated/dataModel";

function MessageView(props: { message: Document<"messages"> }) {

The query and mutation functions in server.js have the same API as before but now provide a db with more precise types. Functions like db.insert(table, document) now understand your schema. Additionally database queries will now return the correct document type (not any).