Constraints & defaults

The $-prefixed field methods attach SQL clauses to a column. This guide covers the everyday ones — defaults, checks, generated columns, identity, and comments. For the full method list see field methods.

Defaults

$default(value) emits a DEFAULT clause. A bare JavaScript value is rendered as a SQL literal; wrap a SQL expression in sqlExpr(...) so the driver knows it is an expression, not a string:

TypeScript
import { defineTable, s, sqlExpr } from "@schemic/postgres";

export const t = defineTable("t", {
  status: s.text().$default("active"),
  created: s.timestamptz().$default(sqlExpr("now()")),
});
Generated DDL PostgreSQL
CREATE TABLE "t" (
  "id" text PRIMARY KEY,
  "created" timestamp with time zone NOT NULL DEFAULT now(),
  "status" text NOT NULL DEFAULT 'active'
);

The rule: $default("active") is the literal string 'active'; $default(sqlExpr("now()")) is the SQL function call now().

CHECK constraints

$check(expr) adds a column-level CHECK. Pass a bare SQL boolean expression (or wrap it in sqlExpr(...)):

TypeScript
export const t = defineTable("t", {
  score: s.integer().$check("score >= 0"),
});
Generated DDL PostgreSQL
CREATE TABLE "t" (
  "id" text PRIMARY KEY,
  "score" integer NOT NULL CHECK (score >= 0)
);

For a constraint that spans columns, use the table-level .check(expr) instead — see define a table.

Generated columns

$generated(expr) emits a GENERATED ALWAYS AS (expr) STORED column computed from other columns. Reference other columns by their quoted name; a camelCase column needs the quotes:

TypeScript
export const line = defineTable("line", {
  quantity: s.integer(),
  unitPrice: s.numeric(10, 2),
  total: s.numeric(12, 2).$generated('quantity * "unitPrice"'),
});
Generated DDL PostgreSQL
CREATE TABLE "line" (
  "id" text PRIMARY KEY,
  "quantity" integer NOT NULL,
  "total" numeric(12, 2) NOT NULL GENERATED ALWAYS AS (quantity * "unitPrice") STORED,
  "unitPrice" numeric(10, 2) NOT NULL
);

Identity columns

$identity() makes a column an auto-incrementing identity. The default mode is by-default (GENERATED BY DEFAULT AS IDENTITY); pass "always" for GENERATED ALWAYS AS IDENTITY:

TypeScript
export const t = defineTable("t", {
  seq: s.integer().$identity("by-default"),
});
Generated DDL PostgreSQL
CREATE TABLE "t" (
  "id" text PRIMARY KEY,
  "seq" integer NOT NULL GENERATED BY DEFAULT AS IDENTITY
);

s.serial() and s.bigserial() are shorthands for an integer / bigint identity column.

Comments

$comment(text) emits a COMMENT ON COLUMN statement:

TypeScript
export const t = defineTable("t", {
  note: s.text().$comment("free text"),
});
Generated DDL PostgreSQL
CREATE TABLE "t" (
  "id" text PRIMARY KEY,
  "note" text NOT NULL
);
COMMENT ON COLUMN "t"."note" IS 'free text';

The comment emits but is not read back during introspection, so — like the clauses above — it is excluded from drift detection.

Where to go next