Skip to content

Field types reference

Every field type with its options, validation, encoding, and API shape.

TypeStorageOptionsAPI in/out
textTEXTmin, max, pattern, unique, encryptedstring
numberREALmin, max, uniquenumber
boolINTEGER (0/1)—boolean
emailTEXTunique, encryptedstring (validated)
urlTEXTunique, encryptedstring (http/https)
dateINTEGER (unix sec)—in: number or ISO; out: number
autodateINTEGERonCreate, onUpdateserver-managed
selectTEXT (single) / TEXT JSON (multi)values (req’d), multiplestring or string[]
relationTEXT (target id)cascadestring (id)
fileTEXT (filename) / TEXT JSON (multi)maxSize, mimeTypes, multiple, protected, viewRule, requireAuth, oneTimeToken, bindTokenIp, auditDownloadsstring or string[]
jsonTEXT (JSON)encryptedany
passwordTEXT (Argon2 hash)min, maxwrite-only; never returned
editorTEXT (HTML)max, encryptedstring (HTML)
geoPointTEXT JSON {lat,lng}—{lat,lng}
vectorTEXT JSON number[]dimensions (1-4096)number[]
colorTEXTalpha, uniquestring (#rrggbb, or #rrggbbaa with alpha)
groupTEXT (JSON)fields (req’d), multiple, min, max, encrypteditems [{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 count
  • pattern — JS regex (string)
  • unique — DB-side check at validate time
  • encrypted — AES-GCM at rest
{ "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.

{ "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.

// 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.

{ "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 with 409 while 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 = unlimited
  • mimeTypes — patterns; image/* matches any image/*
  • multiple — store an array of filenames (JSON)
  • protected — gates GET behind a ?token=<jwt>; see Files
  • viewRule — per-field rule, AND-combined with collection view_rule
  • requireAuth — reject anonymous downloads even on a public collection
  • oneTimeToken — minted tokens are single-use; replay returns 410
  • bindTokenIp — minted tokens carry the client IP and reject mismatches
  • auditDownloads — emit a files.download row 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.

{ "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.

{ "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.

{ "name": "location", "type": "geoPoint" }

{ lat: number, lng: number } with lat ∈ [-90, 90] and lng ∈ [-180, 180]. Stored as JSON.

{ "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=10

Cosine 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 be unique, searchable or encrypted, and id is reserved.
  • Item ids. Every item gets a 15-character id from the server. Send it back when you update the list and it’s kept; an item without one (or with null) 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. min and max bound the number of items (max defaults to 100, at most 1,000). required means 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": false the value is a single object with no id, such as an address. min/max don’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. null or "" clears the field (stored as null).
  • encrypted: true encrypts the whole value at rest; it then can’t be filtered. A group can’t be unique, searchable or a sort key (sort=items is ignored).

Filter on items with ? operators, :each, :length and :any(...); see Filters over group items.

  • 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 touching updated. 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 multiple on 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.

Auto-injected on auth-collection create:

  • email — type email, required, unique, marked implicit: true
  • verified — type bool, default false, marked implicit: true. Stored and returned as email_verified (0/1), and filtered on under that name; there is no verified key in API output. Rules read it as @request.auth.verified. Auth records also return totp_enabled and is_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).

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 create
  • created_at (returned as created) — set on create
  • updated_at (returned as updated) — set on every write

created / created_at and updated / updated_at both work in filters, rules and sort.