Skip to main content
Your database schema lives in confect/tables/: one file per table, each default-exporting a Table.make(...) value. The filename is the table nameconfect/tables/notes.ts defines a table called notes — so filenames must be valid JS identifiers and may not start with _ (Convex reserves underscore-prefixed names for system tables). Codegen scans this directory and produces confect/_generated/schema.ts (the runtime DatabaseSchema, consumed by your impls), confect/_generated/convexSchema.ts (the deploy-time Convex SchemaDefinition re-exported from convex/schema.ts), and a pair of consumer-facing modules per table: confect/_generated/id.ts (a type-safe Id constructor) and confect/_generated/tables/<name>.ts (a thin wrapper that binds the table name). You never edit any of these generated files directly. Each table is defined with Table.make, which takes a callback returning the table’s fields. The callback runs lazily — only the first time the table’s Fields, Doc, or tableDefinition is accessed — so a function that never touches a table pays no schema-construction cost for it at cold start. Indexes are defined on tables the same way as in the base Convex APIs. Cross-table Id references are constructed with the codegen-emitted Id helper from confect/_generated/id.ts, whose argument is type-constrained to the set of declared table names.
confect/tables/notes.ts
import { Table } from "@confect/server";
import * as Schema from "effect/Schema";
import { Id } from "../_generated/id";

export default Table.make(() =>
  Schema.Struct({
    userId: Schema.optional(Id("users")),
    text: Schema.String.pipe(Schema.maxLength(100)),
    tag: Schema.optional(Schema.String),
    author: Schema.optional(
      Schema.Struct({
        role: Schema.Literal("admin", "user"),
        name: Schema.String,
      }),
    ),
    embedding: Schema.optional(Schema.Array(Schema.Number)),
  }),
)
  .index("by_text", ["text"])
  .index("by_role", ["author.role"])
  .searchIndex("text", {
    searchField: "text",
    filterFields: ["tag"],
  })
  .vectorIndex("embedding", {
    vectorField: "embedding",
    filterFields: ["author.name", "tag"],
    dimensions: 1536,
  });
confect/tables/users.ts
import { Table } from "@confect/server";
import * as Schema from "effect/Schema";

export default Table.make(() =>
  Schema.Struct({
    username: Schema.String,
  }),
);
Each confect/tables/*.ts file must default-export a Table — codegen reads module.default and applies the filename as the table name. Consumers (specs, impls, HTTP handlers, etc.) reach a table’s derived Schemas through the generated wrapper at confect/_generated/tables/<name>, e.g. import notes from "../_generated/tables/notes" and then notes.Doc.

Deriving table Schemas

Fields

A Table’s Fields property contains an Effect Schema for the user-defined fields of a table.

Doc

A Table’s Doc property contains an Effect Schema for the table’s document, meaning the table’s user-defined Fields plus its system fields (_id and _creationTime).