Field methods

Every s.* field is chainable. This page is the complete list of methods. For how the clauses render, see from schema to DDL.

Two kinds of method: app-side and DDL

The methods split by prefix, and the prefix tells you which side a method acts on:

  • Non-$ methods are native Zod. s.* is a drop-in for z.* on the app side — your fields are Zod schemas, so these methods delegate to Zod, shaping the app-side value and its validation.
  • $-prefixed methods are Schemic’s. They attach a clause to the Postgres column — the database side.

The two do not bridge; each side is explicit.

Native Zod methods

These delegate to the underlying Zod schema and act on the app side. They are available on any s.* field:

optional, nullable, nullish, array, refine, superRefine, transform, pipe, brand, default, catch, describe, meta.

Modifiers

These shape the column’s type. They carry through to both the TypeScript type and the DDL.

MethodDDL effectDescription
.optional()nullable columnThe column may be NULL.
.nullable()nullable columnSame column — option and null both collapse to nullable.
.array()T[]A Postgres array of the field’s type.

DDL clauses

Each $-method adds a clause to the column (or emits a companion statement).

MethodDDLDescription
.$default(value | sqlExpr(expr))DEFAULT ...A JS literal is rendered as a SQL literal; sqlExpr(...) is spliced as a raw SQL expression.
.$check(expr)CHECK (expr)A column-level check. Accepts a string or sqlExpr(...).
.$generated(expr)GENERATED ALWAYS AS (expr) STOREDA computed column; reference other columns by quoted name.
.$identity(mode?)GENERATED {ALWAYS | BY DEFAULT} AS IDENTITYAuto-increment. Default mode "by-default".
.$unique()CREATE UNIQUE INDEXA unique index on the column, named <table>_<col>_key.
.$primaryKey()PRIMARY KEYMake this column the primary key, replacing the implicit id.
.$references(table, opts?)FOREIGN KEY ... REFERENCES table(id)A foreign key; opts is { onDelete?, onUpdate? }.
.$comment(text)COMMENT ON COLUMNAttach a comment to the column.

Escape hatch

When a value has no built-in mapping, teach the driver how to store it with a Postgres type plus a Zod codec.

MethodDescription
.$postgres(wire, codec?)Chainable: store this field’s app value as the given wire type (an s.* field). codec is { encode, decode }; omit it for an identity mapping.

There is also a factory form for building such a field from scratch — s.$postgres(pgType, codec). See definers.

TypeScript
import { defineTable, s, PgField } from "@schemic/postgres";
import * as z from "zod";

defineTable("tx", {
  amount: new PgField(z.instanceof(Money), {}).$postgres(s.varchar(32), {
    encode: (m) => String(m.cents),
    decode: (v) => new Money(Number(v)),
  }),
});

The column emits as the wire type (varchar(32)); the codec maps between the app value and the wire value.

Where to go next