Keys

One table has one primary key.

Where this fits

Start here. Columns are the other fields.

One table has one primary key. A unique is a separate constraint.

One column

.primaryKey() marks that column. Update omits it. Insert still accepts the value, unless the column is generated or guarded.

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

export const notes = table(
  "notes",
  {
    id: t.uuid().primaryKey(), 
    title: t.text(),
  },
);

Not a composite

A second .primaryKey() is not a composite key. Use several columns.

Several columns

primaryKey lists the columns, in column order.

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

export const memberships = table(
  "memberships",
  {
    organizationId: t.uuid(),
    userId: t.uuid(),
  },
  { primaryKey: ["organizationId", "userId"] }, 
);

Unique

A unique is its own constraint. The primary key stays separate.

FormResult
.unique()One column.
unique: { name: [fields] }Those fields together, in that order. name is the key you choose.
.unique({ global: "reason" })On a tenant table, the tenant key is left out.

column

.unique() is the constraint for one field.

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

export const users = table("users", {
  id: t.identity(),
  email: t.text().unique(), 
});

several

The table option names the constraint and lists the fields, in column order.

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

export const logins = table(
  "logins",
  {
    id: t.identity(),
    email: t.text(),
    provider: t.text(),
  },
  { unique: { login: ["email", "provider"] } }, 
);

global

On a tenant table a unique includes the tenant key. { global: "reason" } leaves that key out. { global: true, reason } is the same opt-out.

global.ts
import { schema, table, t } from "okmodel/pg";
import { columnTenancy } from "okmodel/tenancy";

const users = table("users", {
  id: t.identity(),
  email: t.text().unique({ global: "one login email" }), 
});

export const app = schema({
  tables: [users],
  tenancy: columnTenancy({ key: "tenantId", type: "uuid" }),
});

OKM1060

An empty reason is unique() global needs a reason. { global: true } with no reason is Unique users.email sets global and needs a reason.

id()

Needs PostgreSQL 18 or higher.

t.id() is a uuid primary key. The schema id default is what a bare call means. A column option wins. OKID is t.id({ default: okid() }), stored as text.

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. A bigint.Omitted.

database

A bare t.id() stores gen_random_uuid(). Random ids are fine for most tables. A large write-heavy table can use t.id({ default: "uuidv7" }): the ids are time-ordered and the index stays local. That function needs Postgres 18 and minPostgres: 18.

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

export const notes = table("notes", {
  id: t.id(), 
  title: t.text(),
});

uuidv4

"uuidv4" stores gen_random_uuid().

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

export const notes = table("notes", {
  id: t.id({ default: "uuidv4" }), 
  title: t.text(),
});

generator

A function from okmodel/ids runs on insert. The catalog stores no default.

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

export const notes = table("notes", {
  id: t.id({ default: uuidv7 }), 
  title: t.text(),
});

none

"none" takes the id on insert. Update still omits it.

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

export const notes = table("notes", {
  id: t.id({ default: "none" }), 
  title: t.text(),
});

identity

t.identity() is a generated bigint, not a uuid.

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

export const notes = table("notes", {
  id: t.identity(), 
  title: t.text(),
});

OKM1060

A default other than uuidv7, uuidv4, none, or a client generator is refused.