This is the exhaustive matrix from each s builder to the Postgres column it emits, with a support status. The s vocabulary is Postgres-native: alongside the Zod drop-ins (s.string, s.number) there are precise pg types (text, varchar, numeric, timestamptz, …). For how types compose into DDL see from schema to DDL.
Text
Supported Partial / lossy Schema-layer gap
s.* Postgres TYPE STATUS NOTES
s.text() / s.string() text OK
s.varchar(n) varchar(n) OK Length preserved; also validated app-side.
s.char(n) char(n) OK
s.enum([...]) text Partial App-side Zod enum; projects to text. For a native CREATE TYPE enum, use defineEnum.
s.literal(v) text / double precision / boolean OK Column type follows the literal's JS type.
s.citext() citext Partial Case-insensitive text; requires the citext extension (declare via defineExtension) — the embedded PGlite engine may not bundle it.
s.email() / s.url() / s.cuid2() / … text OK String-format factories (email, url, emoji, nanoid, cuid, cuid2, ulid, guid, xid, ksuid, base64, base64url, e164, jwt) — app-side Zod validation; the column stays text.
Numeric
Supported Partial / lossy Schema-layer gap
s.* Postgres TYPE STATUS NOTES
s.smallint() smallint OK
s.integer() / s.int() integer OK
s.bigint() bigint OK App value is a bigint (precise).
s.serial() integer (identity) OK Modeled as GENERATED BY DEFAULT AS IDENTITY.
s.bigserial() bigint (identity) OK
s.numeric(p, s) / s.decimal(p, s) numeric(p, s) OK Precision and scale preserved.
s.numeric() numeric OK
s.real() real OK
s.doublePrecision() / s.float() / s.number() double precision OK
s.money() money OK String app-side.
Boolean & temporal
Supported Partial / lossy Schema-layer gap
s.* Postgres TYPE STATUS NOTES
s.boolean() / s.bool() boolean OK
s.timestamptz() timestamp with time zone OK Date app-side.
s.timestamp() timestamp OK Without time zone; Date app-side.
s.date() date OK
s.time() time OK String app-side.
s.timetz() timetz OK String app-side.
s.interval() interval OK String app-side.
Identifier, network & binary
Supported Partial / lossy Schema-layer gap
s.* Postgres TYPE STATUS NOTES
s.uuid() uuid OK
s.bytea() bytea OK Uint8Array app-side.
s.inet() inet OK
s.cidr() cidr OK
s.macaddr() macaddr OK
JSON & structural
Supported Partial / lossy Schema-layer gap
s.* Postgres TYPE STATUS NOTES
s.jsonb(shape?) jsonb OK Opaque on disk; sub-structure validated app-side by Zod.
s.json(shape?) json Partial Round-trips; distinct from jsonb.
s.object({ ... }) jsonb OK Collapses to one opaque jsonb column.
s.array(el) / T[] / .array() el[] Partial Round-trips for canonical element types; arrays of pg-native element types are a known gap.
Foreign keys
Supported Partial / lossy Schema-layer gap
s.* Postgres TYPE STATUS NOTES
s.references(table, opts?) text + FOREIGN KEY OK A text column referencing table(id); ON DELETE/UPDATE optional.
Modifiers
Supported Partial / lossy Schema-layer gap
s.* Postgres TYPE STATUS NOTES
.optional() nullable column OK Same column as .nullable().
.nullable() nullable column OK option and null both collapse to a nullable column.
.array() T[] OK
Escape hatch
For a value with no built-in mapping, store it explicitly with a Postgres type plus a Zod codec — see field methods.
Supported Partial / lossy Schema-layer gap
s.* Postgres TYPE STATUS NOTES
s.$postgres(pgType, codec) pgType OK Factory: any pg type + an encode/decode codec.
.$postgres(wire, codec?) wire OK Chainable: store an app value as the given wire type.
Not yet covered
These Postgres features are not expressible in the schema today, or can’t be applied on the embedded engine. Do not rely on them yet:
- Procedures (
CALL-only), expression indexes (an index on lower(col) rather than a plain column), and EXCLUDE constraints.
- Multiple schemas — everything lives in
public — plus roles and grants.
- Extension-backed types —
defineExtension can declare an extension, but the embedded PGlite engine bundles only a few, so citext / PostGIS / pgvector can’t be applied locally (they work against a real Postgres server).
Native enums and domains, views and materialized views, functions, triggers, RLS policies, standalone sequences, composite / non-id foreign keys, and gin/gist/brin/hash + partial indexes are all supported — see definers.
Where to go next