Skip to content

Fields & validation

The 16 field types Vaultbase supports, their options, and how validation runs server-side.

A field is one column on a collection. Vaultbase has 16 types, all with server-side validation, type-aware encoding, and per-field options.

TypeSQL storageAPI shapeNotes
textTEXTstringmin/max length, regex pattern, unique
numberREALnumbermin/max value, unique
boolINTEGER (0/1)boolean—
emailTEXTstringformat-validated
urlTEXTstringhttp(s) only
dateINTEGER (unix seconds)number or ISO string on inputalways returns number
autodateINTEGERnumberserver-managed; opt-in onCreate / onUpdate
selectTEXT (single) / TEXT JSON (multi)string or string[]values whitelist required, multi flag
relationTEXT (target id)stringtarget collection name; cascade behavior; indexed automatically (single relations, since v0.26.5)
fileTEXT (filename) / TEXT JSON (multi)string or string[]maxSize, mimeTypes, multiple, protected, thumbs
jsonTEXT (stringified)anyround-tripped via JSON.parse
passwordTEXT (Argon2 hash)write-onlyhashed via Bun.password.hash; never returned
editorTEXT (HTML)stringQuill rich text in admin
geoPointTEXT JSON {lat,lng}{lat,lng}lat ∈ [-90,90], lng ∈ [-180,180]
vectorTEXT JSON number[]number[]dimensions (1-4096) required; exact length, finite numbers
colorTEXTstringhex color, stored lowercase #rrggbb (#rrggbbaa with alpha); #rgb / any case accepted and normalised

All types accept these via the schema editor’s options panel:

  • required: bool — non-empty on create; for update, only checked if the field is provided (allows partial updates).
  • unique: bool — uniqueness check at validate time, backed by a unique index in the database. Set it inside options ("options": { "unique": true }); required sits on the field itself.

Type-specific options:

// FieldOptions
{
// text: min/max length; editor: max only; password: min/max
min?: number;
max?: number;
pattern?: string; // regex; text only
unique?: boolean;
// select
values?: string[]; // required allowlist
multiple?: boolean; // multi-select → string[]
// file
maxSize?: number; // bytes
mimeTypes?: string[]; // patterns; "image/*" supported
multiple?: boolean;
protected?: boolean; // gates GET behind a token — see Files
thumbs?: string[]; // allowed ?thumb= sizes for non-admins, e.g. ["100x100", "800x0"]
// vector
dimensions?: number; // 1-4096, required
// relation
cascade?: "setNull" | "cascade" | "restrict";
// color
alpha?: boolean; // also accept #rgba / #rrggbbaa (stored as #rrggbbaa)
// text / json (encryptable types)
encrypted?: boolean; // AES-GCM at rest, requires VAULTBASE_ENCRYPTION_KEY
// text / editor / email / url
searchable?: boolean; // full-text index, queried with ?search= (see Full-text search)
}

Every create/update goes through validateRecord():

  1. Skip system fields, implicit fields (auth email/verified), and autodate fields.
  2. For each remaining field:
    • Required check (create only — empty allowed on update unless explicitly required).
    • Type check + per-type rules (length, range, pattern, format, allowed values).
    • Unique check (DB query against vb_<col>).
    • Relation existence check (DB query against the target table).
    • File ownership check: a non-admin may only set a file field to files uploaded to that same record and field (admins are exempt).
  3. Aggregate all errors into a single ValidationError — the API returns 422 with details: { fieldName: message } so clients can surface per-field errors at once.

Multi-error response example:

{
"error": "Validation failed",
"code": 422,
"details": {
"title": "title must be at least 3 characters",
"age": "age must be at least 18"
}
}

When you delete a record, Vaultbase finds every other collection with a relation field pointing at this one and applies the configured behavior:

  • setNull (default) — bulk UPDATE clearing the foreign key column.
  • cascade — recursively delete referencing records (chains through multiple levels; cycle-protected).
  • restrict — refuse the delete with 409 while references exist.

Order: restrict references are checked first, then the beforeDelete hook runs (and can still refuse the delete), and only then are cascades applied. A delete that cascades runs in a transaction: if any step fails (a nested restrict, a hook error), nothing is deleted or nulled.

For text/email/url/json fields, set encrypted: true in the options. Values are encrypted with AES-GCM before insert (using VAULTBASE_ENCRYPTION_KEY) and decrypted on read. Set the key once and persist it — losing it makes encrypted values unreadable.

Terminal window
# Generate a 32-byte key (base64)
openssl rand -base64 32
export VAULTBASE_ENCRYPTION_KEY="<output>"

These are auto-generated and read-only:

  • id — UUID, set on create
  • created_at (returned as created) — unix seconds, set on create
  • updated_at (returned as updated) — unix seconds, refreshed on every update

Fields named id, created, updated, created_at, updated_at, collectionId, collectionName or expand are refused with 422. In filters and rules, created / created_at and updated / updated_at both work.