Field types reference
Every field type with its options, validation, encoding, and API shape.
| Type | Storage | Options | API in/out |
|---|---|---|---|
| text | TEXT | min, max, pattern, unique, encrypted | string |
| number | REAL | min, max, unique | number |
| bool | INTEGER (0/1) | — | boolean |
TEXT | unique, encrypted | string (validated) | |
| url | TEXT | unique, encrypted | string (http/https) |
| date | INTEGER (unix sec) | — | in: number or ISO; out: number |
| autodate | INTEGER | onCreate, onUpdate | server-managed |
| select | TEXT (single) / TEXT JSON (multi) | values (req’d), multiple | string or string[] |
| relation | TEXT (target id) | cascade | string (id) |
| file | TEXT (filename) / TEXT JSON (multi) | maxSize, mimeTypes, multiple, protected, viewRule, requireAuth, oneTimeToken, bindTokenIp, auditDownloads | string or string[] |
| json | TEXT (JSON) | encrypted | any |
| password | TEXT (Argon2 hash) | min, max | write-only; never returned |
| editor | TEXT (HTML) | max, encrypted | string (HTML) |
| geoPoint | TEXT JSON {lat,lng} | — | {lat,lng} |
| vector | TEXT JSON number[] | dimensions (1-4096) | number[] |
| color | TEXT | alpha, unique | string (#rrggbb, or #rrggbbaa with alpha) |
| group | TEXT (JSON) | fields (req’d), multiple, min, max, encrypted | items [{id, ...}], or one object |
Common to all: required, system (read-only flag).
{ "name": "title", "type": "text", "required": true, "options": { "min": 3, "max": 200, "pattern": "^[a-z0-9-]+$", "unique": true } }min/max— character countpattern— JS regex (string)unique— DB-side check at validate timeencrypted— AES-GCM at rest
number
Section titled “number”{ "name": "age", "type": "number", "options": { "min": 0, "max": 120 } }Stored as SQLite REAL — handles floats. min/max are value bounds.
{ "name": "published", "type": "bool" }Stored as 0/1 integer. API in/out is boolean.
{ "name": "email", "type": "email", "options": { "unique": true } }Format check: ^[^\s@]+@[^\s@]+\.[^\s@]+$. min, max and pattern don’t apply.
{ "name": "site", "type": "url" }Format check: must start with http:// or https://.
{ "name": "starts_at", "type": "date" }Accepts on input: unix seconds (number) or any Date.parse-able string.
Always returns unix seconds.
autodate
Section titled “autodate”{ "name": "published_at", "type": "autodate", "onCreate": false, "onUpdate": true }Server-managed. onCreate: true sets it on insert; onUpdate: true
refreshes it on every PATCH. The system columns created_at (set on insert)
and updated_at (set on every write) are returned as created / updated;
you can’t declare fields with those names.
select
Section titled “select”// single{ "name": "status", "type": "select", "options": { "values": ["draft", "live", "archived"] } }
// multi{ "name": "tags", "type": "select", "options": { "values": ["a", "b", "c"], "multiple": true } }values is required — empty rejects everything. Multi stored as a JSON
array; UI uses PrimeReact’s MultiSelect with chips.
relation
Section titled “relation”{ "name": "author", "type": "relation", "collection": "users", "options": { "cascade": "setNull" } }collection = target collection name (string). The record stores the
target’s id: one record. For several, put a relation to this collection on
the other one and read them back with a back-relation (<target>_via_<field>,
see Rules).
options.multiple on a relation is refused since 0.36.0 (no record could be
written with it); a collection that already has one keeps it.
cascade modes (when the referenced record is deleted):
"setNull"(default) — bulk SQL update clearing the FK column."cascade"— recursively delete referencing records (cycle-protected)."restrict"— refuse the delete with409while references exist.
Existence is checked at write time — pointing at a non-existent record
returns 422.
{ "name": "avatar", "type": "file", "options": { "maxSize": 5242880, "mimeTypes": ["image/*"], "multiple": false, "protected": false } }maxSize— bytes;0= unlimitedmimeTypes— patterns;image/*matches anyimage/*multiple— store an array of filenames (JSON)protected— gates GET behind a?token=<jwt>; see FilesviewRule— per-field rule, AND-combined with collectionview_rulerequireAuth— reject anonymous downloads even on a public collectiononeTimeToken— minted tokens are single-use; replay returns410bindTokenIp— minted tokens carry the client IP and reject mismatchesauditDownloads— emit afiles.downloadrow per fetch
The record stores filenames; the bytes live in <dataDir>/uploads/. A value
that isn’t a stored file name (a path, a URL) is rejected with 422, and so,
for non-admins, is a file uploaded to another record or field.
See Rule-based file protection for the rule language extras and worked examples.
{ "name": "metadata", "type": "json", "options": { "encrypted": true } }Any JSON-serializable value. Stored as JSON.stringify’d text. encrypted
puts the stringified value through AES-GCM at rest.
password
Section titled “password”{ "name": "secret", "type": "password", "options": { "min": 8 } }Hashed via Bun.password.hash (Argon2 by default) on write. Every value is
hashed, one that already looks like a hash too (until 0.32.1 a value starting
with $argon2 or $2a$/$2b$ was stored as sent). Never returned
from the API. The records API automatically strips password fields from
all responses.
editor
Section titled “editor”{ "name": "body", "type": "editor", "options": { "max": 100000 } }Stored as HTML string. Admin UI uses Quill for editing. encrypted: true
encrypts it at rest (since 0.32.1; before, the table kept it in the clear
and only record history encrypted it). Values
stored before stay readable and are encrypted when next written. An
encrypted editor field can’t be searchable.
geoPoint
Section titled “geoPoint”{ "name": "location", "type": "geoPoint" }{ lat: number, lng: number } with lat ∈ [-90, 90] and lng ∈ [-180, 180].
Stored as JSON.
vector
Section titled “vector”{ "name": "embedding", "type": "vector", "options": { "dimensions": 1536 } }number[] of exactly dimensions finite values; non-numeric or wrong-length
arrays fail validation with 422. Stored as JSON.
Search via the nearVector query params on the list endpoint:
GET /api/v1/docs?nearVector=[0.1,0.2,...]&nearVectorField=embedding&nearLimit=10Cosine similarity in [-1, 1] is returned per row as _score. v1 is pure-JS;
suitable for collections up to ~50 K rows. See Vector search.
{ "name": "accent", "type": "color", "options": { "alpha": false } }A hex color, stored as lowercase #rrggbb. On write, #rgb and any case are
accepted and normalised (#ABC → #aabbcc), so equality filters and unique
match however the value was typed. Anything else — names like red,
rgb(...), a missing # — fails validation with 422.
With alpha: true the field also accepts #rgba and #rrggbbaa, stored as
#rrggbbaa; without it, a value with an alpha channel is rejected. The admin
shows a color picker plus the hex value in the record form, and a swatch in the
records table.
{ "name": "items", "type": "group", "options": { "min": 1, "max": 100, "fields": [ { "name": "sku", "type": "text", "required": true }, { "name": "qty", "type": "number", "required": true, "options": { "min": 1 } }, { "name": "price", "type": "number" }, { "name": "gift", "type": "bool" } ] } }Structured items whose shape the schema declares: line items on an order, an address book on a user, the steps of a recipe. The value is a list of objects:
[{ "id": "k3f9a8b2c1d0e4f", "sku": "A-1", "qty": 2, "price": 9.5 }]- Subfields (
options.fields, 1 to 50) are declared like top-level fields and validated the same way:required,min/max,pattern, select values, email, url, date and color all behave as they do on a field of their own, and values are stored in the same normalised form. Allowed types:text,number,bool,select(single or multiple),email,url,date,color,editor,json. Groups can’t be nested. A subfield can’t beunique,searchableorencrypted, andidis reserved. - Item ids. Every item gets a 15-character
idfrom the server. Send it back when you update the list and it’s kept; an item without one (or withnull) is new and gets a fresh id. Ids are unique within the list; you may send your own (1 to 64 letters, digits,_or-). - Count.
minandmaxbound the number of items (maxdefaults to 100, at most 1,000).requiredmeans at least one item. - Strict keys. A key that isn’t a subfield fails with
422, and errors name the item:items[2].qty must be at least 1. An empty optional subfield is left out of the item. - One object. With
"multiple": falsethe value is a single object with noid, such as an address.min/maxdon’t apply. Required means at least one subfield has a value (an empty string or list isn’t one), so{}is refused. - Updates replace the whole value, as with every field. Two clients editing the same list at once: the last write wins.
- Empty.
nullor""clears the field (stored asnull). encrypted: trueencrypts the whole value at rest; it then can’t be filtered. A group can’t beunique,searchableor a sort key (sort=itemsis ignored).
Filter on items with ? operators, :each, :length and :any(...); see
Filters over group items.
Changing the subfields
Section titled “Changing the subfields”- Add one: existing items read without it until they’re written.
- Remove one: its stored values are deleted in the same schema change, as a dropped column’s are, so a subfield added back under that name starts empty.
- Rename one: send the new name with
"renamedFrom": "<old name>"on the subfield. Stored items are rewritten in the same transaction as the schema change, without touchingupdated. Renaming one subfield to two names, or onto a name that still exists, is refused. So is renaming a field onto the name of one removed in the same save: save the removal first. - Change a subfield’s type, or switch
multipleon the group or on a select subfield: refused, as top-level type changes are. Remove it and add a new one. - A view whose query names a subfield being renamed or removed blocks the
change (
409) until you change the view. - Restoring a history entry from before a change fits its items to the current subfields.
Implicit fields (auth collections only)
Section titled “Implicit fields (auth collections only)”Auto-injected on auth-collection create:
email— typeemail, required, unique, markedimplicit: trueverified— typebool, default false, markedimplicit: true. Stored and returned asemail_verified(0/1), and filtered on under that name; there is noverifiedkey in API output. Rules read it as@request.auth.verified. Auth records also returntotp_enabledandis_anonymous(0/1).
You can edit their options but not their names or types. v0.11+ stores
them as inline columns on vb_<name> (alongside the rest of the auth
columns: password_hash, totp_secret, totp_enabled, is_anonymous,
password_reset_at).
System fields
Section titled “System fields”Always present, never editable, never declarable as user fields (a field named
id, created, updated, created_at, updated_at, collectionId,
collectionName or expand is refused with 422):
id— text, UUID, set on createcreated_at(returned ascreated) — set on createupdated_at(returned asupdated) — set on every write
created / created_at and updated / updated_at both work in filters, rules and sort.