Column types

The raw PostgreSQL types a schema.json column may declare, why default and check are SQL expressions rather than values, and the generated and generator blocks.

A column's type is not an abstraction over the database. It is the column type, written straight into the DDL. That is worth knowing before you write your first entity, because it explains every "why is there no string" question at once.

type is raw PostgreSQL

Only these are supported:

text
text            integer     bigint      smallint    uuid        boolean
jsonb           json        timestamptz timestamp   date        time
numeric         numeric(p,s)            serial      bigserial   bytea
inet            cidr        macaddr     point       real        double precision
text[]          integer[]   jsonb[]

So there is no string, no decimal, no float, and timestamp is the timezone-less variant — you almost always want timestamptz.

The mapping from the types you may be used to:

You might writeUse instead
stringtext
decimal with precision/scalenumeric(12,2)
floatdouble precision (but use numeric for money)
timestamp for "date and time with timezone"timestamptz
string with an enumtext with a check

default and check are SQL, not values

Both are raw SQL expression strings. That means a literal string default needs SQL quoting inside the JSON string:

schema.json
"status":     { "type": "text", "default": "'in_stock'" },
"attributes": { "type": "jsonb", "default": "'{}'::jsonb" },
"created_at": { "type": "timestamptz", "default": "now()" },
"is_active":  { "type": "boolean", "default": "true" }

"default": "in_stock" without the inner quotes is a SQL identifier, not a string, and will fail.

Constrained values are a check, which is also how you replace the enum you may be looking for:

schema.json
"status": {
  "type": "text",
  "notNull": true,
  "default": "'in_stock'",
  "check": "status in ('in_stock','sold','in_service')"
}

Constraints spanning more than one column go in the entity's checks array instead.

Choosing a type

Money is numeric(p,s). double precision is binary floating point: it will quietly turn 1.23 + 4.56 into something you do not want on an invoice. numeric(12,2) for a price, numeric(14,3) for a quantity that can be fractional.

Time is timestamptz. A timezone-less timestamp is a bug waiting for a tenant in another region, and a bug that only shows up twice a year.

Identifiers are uuid with gen_random_uuid(). Safe to generate anywhere, no collisions, no sequence to coordinate, and several schema features require a uuid id column outright.

Semi-structured data is jsonb, not json. jsonb is the indexable one. If you find yourself filtering on a key inside the document, either index the expression or promote the key to its own column.

Naming matters more than you would like. Column names travel into generated clients for every SDK target the platform emits, so a column called class, function, new, default or return breaks code generation for at least one of them. Rename before you deploy — a column rename after the fact takes three releases.

generated

A stored computed column. PostgreSQL evaluates the expression on every write.

schema.json
"completeness_pct": {
  "type": "numeric",
  "generated": { "expression": "((completeness->>'ratio')::numeric * 100)" }
}

The expression may reference any non-generated sibling column of the same entity. It may not reference another generated column, itself, or tenant_id. A generated column must not also declare pk, notNull, default or references — the expression is the value.

The expression is fixed at creation. Changing it in a later version is rejected, because PostgreSQL cannot alter a generation expression. If it has to change, retire the column and add a differently named one.

generator

A bounded, declarative hint for generating sample data during development. No arbitrary SQL.

schema.json
"serial": { "type": "text", "generator": { "kind": "sequence", "prefix": "SN-" } },
"status": { "type": "text", "generator": { "kind": "pick", "values": ["in_stock", "sold"] } },
"weight": { "type": "numeric", "generator": { "kind": "decimal", "min": 0.1, "max": 40, "scale": 2 } }

kind is one of sequence, pick, range, decimal, uuid, bool, timestamp, date, lorem, with prefix, values, min, max and scale as applicable.

generator affects development data only. It has no effect on a production table.

Next steps

Was this page helpful?