Types
Every field carries one logical type from a closed set of 19. The logical type drives runtime
coercion, default generation and validation; the backend dialect turns it into a concrete column
type at CREATE TABLE time. A schema-declared custom type overrides that mapping.
The logical type set
Section titled “The logical type set”The canonical token is what type: resolves to internally. The four backend columns are what the
dialect emits for a bare declaration of that token, with no custom type in play. Introspecting a
live column reports a wider, dialect-oriented vocabulary (integer, float, json, smallint,
ip_address) rather than these canonical tokens, so the mapping does not round-trip by name.
| Type | Holds | PostgreSQL | ClickHouse | DuckDB | SQLite |
|---|---|---|---|---|---|
string | Bounded text | VARCHAR(255) | String | VARCHAR(255) | TEXT |
text | Unbounded text | TEXT | String | TEXT | TEXT |
int | 32-bit signed integer | INTEGER | Int32 | INTEGER | INTEGER |
bigint | 64-bit signed integer | BIGINT | Int64 | BIGINT | INTEGER |
double | Double-precision float | DOUBLE PRECISION | Float64 | DOUBLE | REAL |
decimal | Exact fixed-point number | DECIMAL(20,6) | Decimal64(6) | DECIMAL(20,6) | TEXT |
money | Monetary amount | DECIMAL(20,6) | Decimal64(6) | DECIMAL(20,6) | TEXT |
boolean | True / false | BOOLEAN | Bool | BOOLEAN | INTEGER |
timestamp | Instant | TIMESTAMPTZ | DateTime64(3) | TIMESTAMP | DATETIME |
date | Calendar date | DATE | Date | DATE | DATE |
time | Time of day | TIME | String | TIME | TEXT |
uuid | UUID | UUID | UUID | UUID | TEXT |
ulid | 26-char Crockford ULID | CHAR(26) | FixedString(26) | VARCHAR(26) | TEXT |
jsonb | JSON document | JSONB | String | JSON | TEXT |
enum | One value from a named enum | TEXT | String | VARCHAR(255) | TEXT |
state | Enum bound to a stateflow | TEXT | String | VARCHAR(255) | TEXT |
file | Reference into the content store | TEXT | String | TEXT | TEXT |
array | List of values | JSONB | String | JSON | TEXT |
bytes | Binary blob | BYTEA | String | BLOB | BLOB |
Notes on individual rows:
decimalandmoneyare always(20,6). There is no field-levelprecision:orscale:. The only way to change the precision of a numeric column is a custom type — see below.moneynarrows todecimalin the loader, so its ClickHouse column isDecimal64(6), not the widerDecimal128(6)the ClickHouse dialect reserves for the money branch.textnarrows tostringat the logical layer but keeps its spelling, which is what makes PostgreSQL and DuckDB emit an unboundedTEXTinstead ofVARCHAR(255).- No backend gets a database enum type. The PostgreSQL dialect can generate
CREATE TYPE ... AS ENUM, but nothing in the shipping service calls it — not the migration planner, not the direct create path — so the column is a plain string on every backend and the database constrains nothing. To reject bad values, declare an explicitvalidations: [{type: enum, values: [...]}]on the field, or create the type yourself in a datastore init script and bind it through akind: databasecustom type. - SQLite type names are affinity hints.
DATEandDATETIMEare stored as ISO-8601 text;decimalis stored as text to avoid float rounding.
Accepted spellings
Section titled “Accepted spellings”type: is lower-cased and trimmed before resolution. These spellings resolve into the set above.
| You write | Resolves to | Column effect |
|---|---|---|
varchar, char | string | Same as string |
integer, int32, int16, int8 | int | Same as int — int8 means 8-bit here, not PostgreSQL’s 64-bit int8 |
long, int64 | bigint | Same as bigint |
float, float32, float64, real | double | Same as double |
numeric | decimal | Same as decimal |
bool | boolean | Same as boolean |
timestampz, datetime | timestamp | Same as timestamp |
object, json | jsonb | Same as jsonb |
bytea, binary | bytes | Same as bytes |
klid | ulid | Spelling preserved: CHAR(27) on PostgreSQL (one-letter prefix + ULID); the other backends fall back to the ulid column |
uint8, uint16, uint32 | int | Spelling preserved: UInt8 / UInt16 / UInt32 on ClickHouse; INTEGER elsewhere |
uint64 | bigint | Spelling preserved: UInt64 on ClickHouse; BIGINT on PostgreSQL and DuckDB; INTEGER on SQLite |
<type>[] | string, spelling preserved | PostgreSQL and DuckDB <mapped>[], ClickHouse Array(<mapped>), SQLite TEXT. The suffix is a dialect-side rule only: the loader does not recognise it, so the field’s logical type is string, not array |
array(<type>) | array | Same as bare array. The element type is parsed and then discarded — nothing downstream reads it, and no per-element column type or validation follows from it |
vector(<n>) | custom | PostgreSQL vector(<n>) (needs the pgvector extension), DuckDB FLOAT[<n>], ClickHouse String, SQLite TEXT |
An empty type: resolves to string.
Custom types
Section titled “Custom types”customtypes: is a top-level block. Each entry names a reusable type that fields reference by
name from type:.
| Key | Meaning | Default |
|---|---|---|
name | Type name; what a field writes in type: | — |
description | Human-readable note carried to schema exports | — |
kind | composite, application or database; anything else silently becomes composite | composite |
basetype | SQL type the value renders as; also the last fallback for database kind | — |
dbtype | Concrete SQL type, used when no per-backend entry matches | — |
database | A single SQL type string, or a per-backend map | — |
nativetype | Superseded by basetype; carried, not read | — |
encode / decode | Encode and decode expressions | — |
fields | Field list for a composite type | — |
precision, scale | Numeric precision and scale | 0 |
length | String length | 0 |
validations, transforms, compliances | Type-level rule lists | — |
How each kind renders
Section titled “How each kind renders”kind | PostgreSQL | ClickHouse | SQLite | Other backends |
|---|---|---|---|---|
database | The database: entry for the active backend, else dbtype:, else basetype: | same rule | same rule | same rule |
composite | JSONB | String | TEXT | JSON |
application | basetype: verbatim | basetype: verbatim | basetype: verbatim | basetype: verbatim |
Keys accepted in the per-backend database: map:
| Backend | Accepted keys |
|---|---|
| PostgreSQL | postgresql, postgres, pg |
| ClickHouse | clickhouse, ch |
| DuckDB | duckdb |
| SQLite | sqlite, sqlite3 |
Precision, scale and length
Section titled “Precision, scale and length”After the kind resolves a SQL type, a size suffix is appended — but only when the resolved type carries no parenthesised group already, and only for these shapes:
| Condition | Suffix |
|---|---|
precision > 0 and scale >= 0 and the resolved type is DECIMAL or NUMERIC | (precision,scale) |
length > 0 and the resolved type is VARCHAR, CHAR or STRING | (length) |
This is the only route by which an author-chosen width reaches the DDL. Every other width in the
tables above — VARCHAR(255), DECIMAL(20,6), CHAR(26) — is hard-coded in the dialect.
Examples
Section titled “Examples”A numeric type with its own precision, a bounded string, and two backend-specific types:
customtypes: - name: amount kind: application basetype: decimal precision: 20 scale: 6 description: "Monetary amount with 6 decimal places"
- name: currency_code kind: application basetype: varchar length: 3
- name: pg_tsvector kind: database basetype: string database: postgresql: tsvector
- name: ch_low_cardinality_string kind: database basetype: string database: clickhouse: "LowCardinality(String)"Referencing them from a field:
entities: - name: invoice fields: - name: total type: amount - name: currency type: currency_codetotal becomes decimal(20,6) and currency becomes varchar(3). The emitted spelling is the
basetype: you wrote plus the size suffix — the registry does not canonicalise it.
Resolution order
Section titled “Resolution order”Every column type — in CREATE TABLE and in the compiled DML — comes out of one function, given
the spelling you wrote in type: and the field itself. It resolves in this order:
- Extension overlay for the datastore’s declared extensions, matched on the spelling you wrote, then on the canonical logical type.
customtypes:, matched the same way.- Array suffix —
<type>[]strips the suffix, maps the element, and re-applies the backend’s array form. vector(<n>)parametric form.- The backend’s static switch over the canonical tokens.
- Fallback: retry with the field’s narrowed logical type, then
TEXT(Stringon ClickHouse) if that also misses.
Extensions win over customtypes: deliberately: an extension is an explicit per-datastore
declaration, so its type names win a name collision.
Extension-provided types
Section titled “Extension-provided types”Available once the datastore declares the extension.
| Backend | Extension | Types added |
|---|---|---|
| PostgreSQL | pgvector | vector, halfvec, sparsevec |
| PostgreSQL | postgis | geometry, geography, box2d, box3d |
| PostgreSQL | citext | citext |
| PostgreSQL | pg_trgm | none (operators and functions only) |
| PostgreSQL | uuid-ossp | none (functions only) |
| DuckDB | spatial | geometry, geography |
| DuckDB | icu | none (collations and time zones only) |
An unrecognised type is not an error
Section titled “An unrecognised type is not an error”A type: value that matches none of the spellings above is not rejected at load. It is kept
on the field as a custom-type reference and the field’s logical type is narrowed to string.
This is deliberate. Schemas are accumulated from many files, and a field may reference a custom type declared in a file loaded later, so resolution is deferred to the compile pass rather than enforced at parse time.
There is a second consequence on the production path: a field whose type: names a custom type
keeps string as its logical type regardless of the type’s basetype:. The column is correct —
the registry resolves it — but defaults, coercion and validation treat the value as a string. A
field typed amount gets a decimal(20,6) column and string-shaped runtime handling.
Related
Section titled “Related”- Fields — the rest of the field surface: modifiers, defaults, validations, compliance.
- Enums — declaring the named enums that
enum:references. - Embeddables — reusable field groups stored inline as a JSON column.
- Datastores — backend selection and extension declaration.