Defaults

Omitted, the column is required and not null.

Where this fits

A column with no default is required and not null. This page is how a missing value is filled, from nullable through a generated expression. Shape is next, when one column holds more than one value.

Insert requires the field unless the column is nullable, has a default, or is left out of input. Pass a value and that value is stored. Update does not fill the default again. A later .default(), .defaultSql(), or .generated() replaces the one before it.

Nullable

.nullable() allows SQL NULL. Omit the field, or pass null. The catalog stores no default.

nullable.ts
import { table, t } from "okmodel/pg";

export const notes = table("notes", {
  id: t.identity(),
  publishedAt: t.timestamptz().nullable(), 
});

Literal

.default(literal) stores the encoded literal in the catalog. Omit the field on insert and Postgres stores that literal.

literal.ts
import { connect } from "okmodel/pg/postgresjs";
import { schema, table, t } from "okmodel/pg";

const notes = table("notes", {
  id: t.identity(),
  title: t.text().default(""), 
});
const app = schema({ tables: [notes] });

export async function add(url: string) {
  await using db = connect(url, { schema: app });
  return db.notes.insert({}); 
}

OKM1060

A default outside a picklist is refused. The message names the value and the list.

Generator

.default(generator) runs when insert omits the field. The catalog stores no default, so a writer that bypasses okmodel must supply the value. Pass a value and that value is stored instead.

generator.ts
import { connect } from "okmodel/pg/postgresjs";
import { schema, table, t } from "okmodel/pg";
import { uuidv7 } from "okmodel/ids";

const notes = table("notes", {
  id: t.uuid().default(uuidv7), 
  title: t.text(),
});
const app = schema({ tables: [notes] });

export async function add(url: string, title: string) {
  await using db = connect(url, { schema: app });
  return db.notes.insert({ title });
}

Generators

A generator is uuidv4, uuidv7, or OKID (okid(...)), all from okmodel/ids, or any function. connect({ generators }) replaces uuidv4, uuidv7, and okid. A function you wrote is left alone. These run in the application. They are not the database functions uuidv7() and gen_random_uuid().

Already filled

t.id() and t.identity() already carry a default. Both are omitted from insert and update, so input cannot set them. The schema id default changes what a bare t.id() means. A column option wins.

CallWhat fills itInsert
t.id()Database gen_random_uuid(), unless the schema default says otherwise.Omitted.
t.id({ default: "uuidv7" })Database uuidv7(). Needs Postgres 18.Omitted.
t.id({ default: "uuidv4" })Database gen_random_uuid(), the same as a bare call.Omitted.
t.id({ default: uuidv7 })The client generator. Nothing is stored in the catalog.Omitted.
t.id({ default: "none" })Nothing.Required. Still omitted from update.
t.identity()generated always as identity.Omitted.

t.id({ default: "none" }) is the column option. It is not a schema default.

database

A bare t.id() is omitted from insert. Postgres fills gen_random_uuid(). A large write-heavy table can store uuidv7() instead: the ids are time-ordered and the index stays local. That function needs Postgres 18 and minPostgres: 18.

id-insert.ts
import { connect } from "okmodel/pg/postgresjs";
import { schema, table, t } from "okmodel/pg";

const notes = table("notes", {
  id: t.id(), 
  title: t.text(),
});
const app = schema({ tables: [notes] });

export async function add(url: string, title: string) {
  await using db = connect(url, { schema: app });
  return db.notes.insert({ title });
}

supplied

"none" is required on insert. Update still cannot set it.

id-none.ts
import { connect } from "okmodel/pg/postgresjs";
import { schema, table, t } from "okmodel/pg";

const notes = table("notes", {
  id: t.id({ default: "none" }), 
  title: t.text(),
});
const app = schema({ tables: [notes] });

export async function add(url: string, id: string, title: string) {
  await using db = connect(url, { schema: app });
  return db.notes.insert({ id, title }); 
}

Null

.default(null) is accepted only after .nullable(). The check runs when .default() is called, so the order is .nullable().default(null).

null-default.ts
import { table, t } from "okmodel/pg";

export const notes = table("notes", {
  id: t.identity(),
  closedAt: t.timestamptz().nullable().default(null), 
});

OKM1060

A null default on a column that is not nullable is refused. Call .nullable() first.

SQL

.defaultSql(expression) stores the expression as written. Omit the field and Postgres evaluates it. An empty expression is refused.

default-sql.ts
import { table, t } from "okmodel/pg";

export const notes = table("notes", {
  id: t.identity(),
  createdAt: t.timestamptz().defaultSql("now()"), 
});

OKM1060

defaultSql must be a non-empty SQL expression.

Generated

.generated(expression) is generated always as (expression) stored. Insert and update omit the column. A read returns the value. An empty expression is refused. { stored: true } is the default, and the SQL is stored either way.

generated.ts
import { table, t } from "okmodel/pg";

export const notes = table("notes", {
  id: t.identity(),
  title: t.text(),
  search: t.tsvector().generated("to_tsvector('simple', title)"), 
});

OKM1060

generated() must be a non-empty SQL expression.