Skip to main content
Version: Next

Field types

Every model field has a YAML type that becomes a Dart type, a JSON encoding, and, on a table model, a database column. This catalog lists the mapping, allowed defaults, query operators, and storage caveats for each type. For the model file format, see Working with models.

Overview

YAMLDartPostgresSQLiteJSONDefaults
boolboolbooleanINTEGER (0 / 1)true / falsetrue, false
intintbigintINTEGERnumberany int; serial only with defaultPersist
doubledoubledouble precisionREALnumberany double
StringStringtextTEXTstringquoted '...' or "..."
DateTimeDateTimetimestamp without time zoneINTEGER (UTC epoch ms)UTC ISO-8601 stringnow, or yyyy-MM-dd'T'HH:mm:ss.SSS'Z'
DurationDurationbigint (milliseconds)INTEGER (ms)int millisecondsXd Xh Xmin Xs Xms
ByteDataByteDatabyteaBLOBdecode('<base64>', 'base64')none
UuidValueUuidValueuuidBLOB (16 bytes, no dashes)UUID stringrandom, random_v7, or a quoted UUID
UriUritextTEXTUri.toString()quoted URI string
BigIntBigInttextTEXTdecimal stringany BigInt.parse-able value

Vector types require a dimension of at least 1, for example Vector(1536).

YAMLDartPostgresSQLiteJSONDefaults
Vector(n)Vectorvector(n)TEXTList<double>none
HalfVector(n)HalfVectorhalfvec(n)TEXTList<double>none
SparseVector(n)SparseVectorsparsevec(n)TEXTstring {i:v,...}/nnone
Bit(n)Bitbit(n)TEXTbit stringnone

Geography types use SRID 4326 (WGS 84), which is not configurable per column.

YAMLDartPostgresSQLiteJSONDefaults
GeographyPointGeographyPointgeography(Point,4326)TEXT (EWKT)EWKT, for example SRID=4326;POINT(lon lat)none
GeographyLineStringGeographyLineStringgeography(LineString,4326)TEXTEWKTnone
GeographyPolygonGeographyPolygongeography(Polygon,4326)TEXTEWKTnone
GeographyGeometryCollectionGeographyGeometryCollectiongeography(GeometryCollection,4326)TEXTEWKTnone

Enums store as text or bigint, not json. A field default is any of the enum's values.

serializedJSONPostgresSQLite
byName (default)string literaltextTEXT
byIndexint indexbigintINTEGER

Collections, records, nested models, custom classes, and dynamic persist as json or jsonb. See JSON vs JSONB.

YAMLJSONPostgres defaultSQLite (json)SQLite (jsonb)
List<T>JSON arrayjsonTEXTBLOB via jsonb()
Set<T>JSON array (order not guaranteed)jsonTEXTBLOB via jsonb()
Map<String, V>JSON objectjsonTEXTBLOB via jsonb()
Map<K, V> (K is not String)JSON array of {k, v} objectsjsonTEXTBLOB via jsonb()
(int, String) / ({int n}){ "p": [...], "n": {...} }jsonTEXTBLOB via jsonb()
nested model / custom classobject JSONjsonTEXTBLOB via jsonb()
dynamicvalue plus type metadatajsonTEXTBLOB via jsonb()

Nullability

A trailing ? on the type makes the field nullable. Non-nullable fields are required constructor parameters. The required keyword is valid only on nullable fields: it makes the constructor parameter required while keeping the type nullable.

class: Person
fields:
name: String
nickname: String?, required
age: int?

The dynamic type is already nullable. dynamic? and List<dynamic?> are rejected. See Required fields and Dynamic fields.

Serialization

Three encodings are involved when a value moves between the app, the server, and the database:

  1. Protocol JSON (client and server): toJson() / fromJson().
    • int, bool, double, and String pass through as native JSON.
    • Every other type converts, as listed in the overview tables.
  2. Postgres literals used when writing SQL: UUIDs are quoted, ByteData is \x hex, vectors are '[1,2,3]', geography is EWKT, and collections are JSON text.
  3. SQLite literals used by the client-side database: bool is 0 / 1, DateTime is epoch milliseconds, UuidValue is a 16-byte blob, ByteData is X'hex', and jsonb values use jsonb(...).

Protocol JSON for DateTime always uses toUtc().toIso8601String(). Postgres stores the value as timestamp without time zone, so stored values are UTC.

Primitive types

class: Sample
fields:
active: bool
count: int
price: double
name: String
createdAt: DateTime
timeout: Duration
payload: ByteData
uuid: UuidValue
homepage: Uri
huge: BigInt

bool

fields:
published: bool, default=false

SQLite stores false as 0 and true as 1, and reads those integers back as bools. Ordering operators (>, <, >=, <=) are not available. Equality operators are: equals, notEquals, inSet, notInSet.

int

fields:
views: int, default=0
invoiceNumber: int?, defaultPersist=serial

An int column is always 64-bit Postgres bigint, not integer. Serial IDs use bigserial. The serial default is valid only with defaultPersist, never with default or defaultModel. On SQLite, serial is supported only on the id column.

The Duration type also uses Postgres bigint. The column type is the same; the meaning is not. A Duration value is milliseconds.

double

fields:
rating: double, default=0.0

On SQLite, NaN is stored as NULL, and Infinity is stored as 1e999.

String

fields:
title: String, default='Untitled'

Strings are unbounded text (Postgres) or TEXT (SQLite). There is no varchar length. String columns support like, notLike, ilike, and notIlike in addition to ordering and equality. On SQLite, like and ilike are both case-insensitive for ASCII characters. See Filter.

DateTime

fields:
createdAt: DateTime, default=now
publishedAt: DateTime?, default=2024-05-01T22:00:00.000Z

Values are always converted to UTC. The Postgres default for now is CURRENT_TIMESTAMP. SQLite stores UTC epoch milliseconds as INTEGER.

On an immutable class, now is not valid with default or defaultModel. A table model can set defaultPersist=now instead.

Duration

fields:
timeout: Duration, default=1d 2h 10min 30s 100ms

Stored as milliseconds in a bigint (Postgres) or INTEGER (SQLite) column, the same SQL type as int. JSON is that millisecond count as an integer.

ByteData

fields:
blob: ByteData

No YAML default. Postgres inserts use \x hex for bytea. SQLite uses a BLOB with X'hex' literals. On the wire, the protocol JSON is a Postgres-style string: decode('<base64>', 'base64').

UuidValue

fields:
publicId: UuidValue, default=random
orderedId: UuidValue, default=random_v7
fixedId: UuidValue, default='550e8400-e29b-41d4-a716-446655440000'

The random default generates a UUID v4 (Uuid().v4obj() in Dart, gen_random_uuid() in Postgres). The random_v7 default generates a UUID v7 (Uuid().v7obj() in Dart, gen_random_uuid_v7() in Postgres). SQLite stores the 16-byte value as a BLOB with no dashes.

On an immutable class, random and random_v7 are not valid with default or defaultModel. A table model can set defaultPersist instead.

Uri

fields:
homepage: Uri, default='https://serverpod.dev'

A URI default must be a quoted string. Stored as text / TEXT. Query operators are equality only (equals, notEquals), not ordered comparison.

BigInt

fields:
huge: BigInt, default='1234567890'

Stored as text / TEXT, not a numeric column. The JSON value is a decimal string. Any value BigInt.parse accepts is a valid default. Query operators are equality only (equals, notEquals).

Enums

enum: Animal
serialized: byName
default: unknown
values:
- unknown
- dog
- cat

The serialized keyword has two values. byName is the default and stores the string literal as Postgres text / SQLite TEXT. byIndex stores the index as Postgres bigint / SQLite INTEGER.

Changing the order of a byIndex enum changes the stored integers and can corrupt existing data. The default byName mode is stable when values are added or reordered.

A field default is any of the enum's values. The enum's own default is the fallback when an unknown value is deserialized, not a column default. See Handling unknown enum values.

Enhanced enum properties support int, double, bool, String, and their nullable forms. Those properties exist only on the generated Dart enum; they are not extra columns.

Collections and records

Type arguments are required: List<String>, Map<String, int>, Set<UuidValue>. Untyped List or Map is rejected.

class: Packed
fields:
tags: List<String>
uniqueTags: Set<String>
counts: Map<String, int>
labeled: Map<int, String>
pair: (int, String)
named: ({int count})
  • List<T> is a JSON array.
  • Set<T> is also a JSON array. Order is not guaranteed after a round trip.
  • Map<String, V> is a JSON object.
  • Map<K, V> where K is not String is a JSON array of {k, v} objects, not a JSON object, because JSON object keys must be strings.
  • Records serialize positional fields under "p" and named fields under "n". A record with only positional fields omits "n"; a record with only named fields omits "p". A single-element record needs a trailing comma: (int,).
{
"pair": { "p": [1, "hello"] },
"named": { "n": { "count": 42 } },
"labeled": [{ "k": 1, "v": "a" }, { "k": 2, "v": "b" }]
}

No YAML defaults for List, Map, Set, or records.

Nested models

A nested model (or custom class) in a JSON column is a copy stored on that row. Updating it does not update other rows that hold a similar object. A relation field stores a foreign key instead, so multiple rows can point at the same object.

class: Company
table: company
fields:
address: Address
billingAddress: Address?, relation

The address field is stored as json on company. The billingAddress field is a foreign key to an address row. See Tables.

JSON vs JSONB

Serializable types (List, Map, Set, records, nested models, custom classes, and dynamic) default to Postgres json (SQLite TEXT). Setting serializationDataType=jsonb on the field, the class, or serialize_as_jsonb_by_default: true in config/generator.yaml stores them as:

  • Postgres: jsonb, which can use a GIN index
  • SQLite: BLOB via SQLite's jsonb() function

The serializationDataType keyword is not valid on primitives (String, int, bool, and the other core types). A GIN index requires every indexed field to be jsonb. See Storing serializable fields as JSONB.

Vector types

class: Document
table: document
fields:
embedding: Vector(1536)

Dimension is required: Vector(1536), HalfVector(1536), SparseVector(10000), Bit(256). The dimension must be at least 1.

SQLite has no native vector type. Values are stored as text, vector query operators are not supported, and HNSW / IVFFLAT indexes are omitted.

On Postgres, the default index type is hnsw. Vector, HalfVector, and Bit also allow ivfflat. SparseVector allows only hnsw. Distance functions are l2, innerProduct, cosine, and l1. Bit also has Hamming and Jaccard.

Vector fields require the pgvector extension. See Vector and geography fields for usage, Dart APIs, and setup.

Geography types

class: Store
table: store
fields:
location: GeographyPoint

Coordinates in EWKT are longitude then latitude: SRID=4326;POINT(lon lat). No YAML defaults.

Indexes are gist (default) or spgist only. Query operators are intersects, distanceWithin, distance, contains, and within (Postgres ST_* / ST_GeogFromText).

Geography fields require PostGIS. On SQLite they are opaque EWKT strings: CRUD round-trips, spatial operators throw. See Vector and geography fields and Upgrading to PostGIS support.

Dynamic and custom classes

A dynamic field holds any serializable value when the type is not known at compile time. Serverpod includes type metadata in the JSON so the value round-trips. That wrapper is an implementation detail. See Dynamic fields.

A hand-written Dart class can be a field type after it is registered in config/generator.yaml. It persists as json / jsonb like a nested model. See Custom serialization.

ID types

If id is omitted on a table model, it is int? with defaultPersist=serial.

TypeYAMLPersist defaultModel defaultPostgresSQLite
intid: int?, defaultPersist=serialserialnot allowedbigserial PRIMARY KEYINTEGER PRIMARY KEY (ROWID)
UuidValueid: UuidValue?, defaultPersist=randomrandom, random_v7random, random_v7uuidBLOB

An int id must be nullable. serial is defaultPersist only, never defaultModel. SQLite allows autoincrement only on the id column.

Choosing between int and UuidValue, and generating the id before insert with defaultModel=random, is covered in Choosing an ID strategy.

Defaults

Allowed values by type are in the overview and in each type section. The default, defaultModel, and defaultPersist keywords are explained in Default values.

These types cannot have a YAML default:

  • ByteData
  • Vector types (Vector, HalfVector, SparseVector, Bit)
  • Geography types
  • List, Map, Set
  • Records
  • Nested models and custom classes
  • dynamic

On an immutable class, now, random, and random_v7 are not valid with default or defaultModel. Those values belong on defaultPersist instead.

Indexes

Fields marked !persist cannot be indexed.

FieldsDefault indexAllowed
Scalarsbtreebtree, hash, gin, gist, spgist, brin
jsonb serializablegingin requires every indexed field to be jsonb
Vector / HalfVector / Bithnswhnsw, ivfflat
SparseVectorhnswhnsw only
Geographygistgist, spgist

Index types other than btree are Postgres-only. On SQLite they are skipped when a migration is created. See Indexing.

Query operators

Table columns expose the operators below in filters.

TypesOperators
int, double, String, DateTime, Duration, UuidValue, enumsequals, notEquals, >, <, >=, <=, inSet, notInSet
int, double, DateTime, Durationbetween, notBetween
Stringlike, notLike, ilike, notIlike
boolequals, notEquals, inSet, notInSet
Uri, BigIntequals, notEquals
Vector, HalfVector, SparseVectordistanceL2, distanceInnerProduct, distanceCosine, distanceL1
BitdistanceHamming, distanceJaccard
Geographyintersects, distanceWithin, distance, contains, within

Vector and geography operators run on Postgres. They are not supported on SQLite.

Postgres vs SQLite

The client-side database is SQLite. These mappings differ from Postgres:

ConcernPostgresSQLite
boolbooleanINTEGER 0 / 1
intbigintINTEGER
DateTimetimestamp without time zoneUTC epoch milliseconds
UuidValueuuid16-byte BLOB
ByteDatabytea (\x hex)BLOB (X'hex')
json collectionsjsonTEXT
jsonb collectionsjsonbBLOB via jsonb()
Vectorspgvector types, HNSW / IVFFLATTEXT, no vector indexes or operators
GeographyPostGIS geography, spatial operatorsEWKT TEXT, no spatial operators
serialany int column with defaultPersist=serialid column only
like / ilikelike is case-sensitiveboth are case-insensitive for ASCII
Non-btree indexescreated as declaredomitted, with a warning

The Duration and int types share a 64-bit integer column on both dialects. The BigInt type is text on both, not a numeric type.