Fields
Field builders define the scalar shape of each model.
Scalar builders
id()
id: id();- runtime output type:
string - unique by default
- generated as an id by default
For numeric primary keys, pass an explicit type:
id: id({ type: "integer" });- runtime output type:
number - useful when an app or integration needs integer-shaped identifiers
- works well for manually assigned numeric IDs
If you want the runtime or database to generate integer IDs for you, opt into increment semantics:
id: id({ type: "integer", generated: "increment" });- runtime output type:
number - SQL, Drizzle, Kysely, Prisma, and the memory driver can generate the value
- MongoDB and Mongoose still require manual numeric IDs today
string()
email: string();- runtime output type:
string
boolean()
emailVerified: boolean();- runtime output type:
boolean
datetime()
createdAt: datetime();- runtime output type:
Date
integer()
loginCount: integer();- runtime output type:
number
enumeration()
status: enumeration(["draft", "published"]);- runtime output type: the string union from the values you pass in
- generated as a real enum where the target supports it
- TypeScript reserves the
enumkeyword, so the builder is namedenumeration()
bigint()
quota: bigint();- runtime output type:
bigint - useful for 64-bit counters, quotas, and ids that should not round through
number
decimal()
balance: decimal();- runtime output type:
string - keeps precision stable across runtimes instead of routing decimal math through JavaScript
number - decoded values are normalized strings, so
"12.50"may round-trip as"12.5"
json()
metadata: json<{
plan: string;
scopes: string[];
}>();- runtime output type: the JSON shape you pass to
json<T>() - useful for provider metadata, settings blobs, and structured plugin state
Field modifiers
.unique()
email: string().unique();Marks the field as unique in the schema and generated outputs.
Use this for single-field uniqueness. For compound uniqueness such as
provider + accountId, use model-level constraints.unique on model(...)
instead.
.nullable()
bio: string().nullable();Changes the runtime output type from string to string | null.
.default(...)
role: string().default("member");Stores a literal default value.
This works well for string, boolean, integer, bigint, decimal, and enum fields today. JSON defaults can still be applied by runtime drivers, but generated schema support is more limited and should be treated as backend-specific for now.
.defaultNow()
createdAt: datetime().defaultNow();Marks the field as generated from the current time.
.references("model.field")
userId: string().references("user.id");Adds foreign-key-style metadata that generators can translate.
.map("column_name")
email: string().map("email_address");Keeps the logical API field name as email while generating the physical column
name email_address.
.describe("...")
email: string().describe("Canonical login email");Adds descriptive metadata to the manifest/docs layer.
Example model
user: model({
table: "users",
fields: {
id: id(),
email: string().unique().map("email_address"),
name: string(),
emailVerified: boolean().default(false),
loginCount: integer().default(0).map("login_count"),
tier: enumeration(["free", "pro", "enterprise"]).default("free"),
quota: bigint().default(0n).map("quota_bigint"),
balance: decimal().default("0.00"),
createdAt: datetime().defaultNow(),
bio: string().nullable(),
},
});Numeric IDs use the same DSL:
auditEvent: model({
table: "audit_events",
fields: {
id: id({ type: "integer" }),
email: string().unique(),
},
});Generated numeric IDs use the same builder:
auditEvent: model({
table: "audit_events",
fields: {
id: id({ type: "integer", generated: "increment" }),
email: string().unique(),
},
});What generators care about
Field data is especially important because it drives:
- Prisma field types
- Drizzle column definitions
- SQL column definitions
- nullability
- unique constraints
- references
- generated/default behavior
The current generators and live runtimes support:
integer()across Prisma, Drizzle, safe SQL, memory, Prisma runtime, SQL, Kysely, Drizzle, MongoDB, and Mongoosejson()across Prisma, Drizzle, safe SQL, memory, Prisma runtime, SQL, MongoDB, and Mongooseenumeration()across Prisma, Drizzle, safe SQL, memory, Prisma runtime, SQL, Kysely, MongoDB, and Mongoosebigint()across Prisma, Drizzle, safe SQL, memory, Prisma runtime, SQL, Kysely, MongoDB, and Mongoosedecimal()across Prisma, Drizzle, safe SQL, memory, Prisma runtime, SQL, Kysely, MongoDB, and Mongoose
For SQLite-backed bigint tests, the local matrix enables the underlying
node:sqlite big-int read mode so values do not get truncated back into
JavaScript number.
Mapping example
email: string().unique().map("email_address");Logical side:
user.email
Physical side:
users.email_address
This is a big part of why the schema DSL is useful for reusable packages: the library can keep a stable logical field name even when the physical database surface needs a different name.
How is this guide?