Define a table
defineTable(name, fields) creates a table definition: a Zod object whose fields carry SurrealQL metadata. This guide covers the everyday choices you make when defining one. For the conceptual background see from schema to DDL.
Define the fields
Pass the table name and an object of fields built with s, the drop-in for Zod’s z:
import { s, defineTable } from "@schemic/core";
export const Post = defineTable("post", {
id: s.string(),
title: s.string(),
body: s.string(),
views: s.int(),
draft: s.boolean(),
});Each field is a real Zod schema, so .optional(), .array(), and refinements work as usual and carry through to the DDL — .optional() becomes option<...>, .array() becomes array<...>.
Choose the record id type
The id field controls the type of the record id, not a normal column:
id: s.string()makes the id arecord<post, string>.- Omit
identirely and SurrealDB assigns its own id; the type isrecord<post>.
SurrealDB manages the id itself, so Schemic never emits a DEFINE FIELD id. Set id only to constrain the id type.
Schemafull or schemaless
Tables are schemafull by default: SurrealDB rejects any field you did not define. Change that with a chained method:
defineTable("event", { id: s.string(), name: s.string() })
.schemaless(); // accept arbitrary extra fields| Method | DDL | Meaning |
|---|---|---|
.schemafull() | SCHEMAFULL | Only defined fields are allowed. The default. |
.schemaless() | SCHEMALESS | Any field is allowed; defined fields are still validated. |
.typeAny() | TYPE ANY | The table accepts records of any shape. |
For a single object field that should accept arbitrary keys while staying on a schemafull table, use .loose() on that field instead (it emits FLEXIBLE).
Add a comment
.comment(text) attaches a SurrealQL COMMENT to the table, which shows up in INFO FOR DB:
defineTable("post", { id: s.string(), title: s.string() })
.comment("Blog posts authored by users.");Derive a table from another
The Zod set operations return a new full table definition — with its own DDL and codecs — so you can build variants without repeating fields:
const Post = defineTable("post", {
id: s.string(),
title: s.string(),
body: s.string(),
authorId: User.record(),
});
const PostSummary = Post.pick("id", "title"); // only id + title
const PostDraft = Post.omit("authorId"); // everything but authorId
const PostPatch = Post.partial(); // every field optional
const PostPlus = Post.extend({ pinned: s.boolean() });| Method | Result |
|---|---|
.pick(...keys) | A table with only the named fields. |
.omit(...keys) | A table without the named fields. |
.partial() | A table with every field optional. |
.extend({ ... }) | A table with extra fields added. |
Verify it
Generate a migration to see the DDL your definition produces:
npx schemic gen add_postThen inspect the written .surql file, or use schemic diff to preview changes against a live database without writing anything.
Where to go next
- Constraints, defaults & permissions —
ASSERT,DEFAULT,PERMISSIONS. - Model relationships — record links and
RELATEedges. - Definers reference — every option on
defineTable.