> ## Documentation Index
> Fetch the complete documentation index at: https://confect.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Schema

> Define your database schema using Effect schemas.

Your database schema lives in `confect/tables/`: one file per table, each default-exporting a `Table.make(...)` value, where the **filename is the table name** (`confect/tables/notes.ts` defines a table called `notes`). The Confect CLI's codegen combines these files into a single schema behind the scenes — and generates the per-table helpers your code imports — so you never assemble or edit a schema definition by hand. (For the full layout of what codegen emits, see [Project Structure](/concepts/project-structure).)

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.

```ts confect/tables/notes.ts theme={null}
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,
  });
```

```ts confect/tables/users.ts theme={null}
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 `Schema`s through the generated wrapper at `confect/_generated/tables/<name>`, e.g. `import notes from "../_generated/tables/notes"` and then `notes.Doc`.

## Deriving table `Schema`s

### 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`).

## Document types

`Fields` and `Doc` are runtime `Schema`s — *values*. Reach for them whenever you need a value: a spec's `returns`, an HTTP endpoint's `addSuccess`, or decoding. Their TypeScript types are available as `typeof notes.Doc.Type` and `typeof notes.Fields.Type`.

When you only need to annotate something with a table's document *type*, codegen also emits named document types in `confect/_generated/docs.ts` — one per table (`NotesDoc`, `UsersDoc`, …) plus a `Docs` registry:

```ts confect/_generated/docs.ts theme={null}
import type { Document } from "@confect/server";
import type schemaDefinition from "./schema";

export type NotesDoc = Document.Document<typeof schemaDefinition, "notes">;
export type UsersDoc = Document.Document<typeof schemaDefinition, "users">;

export interface Docs {
  notes: NotesDoc;
  users: UsersDoc;
}
```

`NotesDoc` is the decoded document — the table's fields plus its system fields — exactly the same type as `typeof notes.Doc.Type`, but as a *named* type. Because it's named, it keeps editor hovers and emitted `.d.ts` files readable (`NotesDoc` rather than the fully-expanded row), and it's what lets the `DatabaseReader`/`DatabaseWriter` services hand documents back under their table's name.

```ts theme={null}
import type { NotesDoc } from "./confect/_generated/docs";

const renderNote = (note: NotesDoc) => note.text;
```

You can also write the type inline with the same helper the generated types alias, `Document.Document<typeof schemaDefinition, "notes">`, and drop the system fields with `Document.WithoutSystemFields<NotesDoc>`.

As a rule of thumb: reach for `notes.Doc` when you need a **value** (a `Schema`), and `NotesDoc` when you need a **type**.

<Note>
  Each type's name is the table name folded to PascalCase, so `user_profiles`
  and `userProfiles` would both produce `UserProfilesDoc`. Codegen reports an
  error if two tables would collide on the same name.
</Note>
