Field Validation Rules
Default validation behavior, required properties, and constraints for each ObjectStack field type
Every ObjectStack field type has built-in validation behavior that runs automatically at the schema level. This reference documents the default constraints, required properties, and validation semantics for each field type.
Source: packages/spec/src/data/field.zod.ts
Import: import { FieldSchema, FieldType } from '@objectstack/spec/data'
Universal Field Constraints
These properties apply to all field types and are validated by the base FieldSchema:
| Property | Type | Default | Validation Behavior |
|---|---|---|---|
name | string | — | Must match ^[a-z_][a-z0-9_]*$ (snake_case) |
label | string | — | Human-readable display name |
type | FieldType | — | Must be a member of the FieldType enum |
required | boolean | false | Rejects null/undefined at runtime when true |
unique | boolean | false | Enforces database-level uniqueness constraint |
multiple | boolean | false | Stores value as array (applicable for select, lookup, file, image) |
hidden | boolean | false | Excluded from default UI rendering |
readonly | boolean | false | Blocks user edits in UI forms; server-enforced on both create and update — a non-system write to the field is silently dropped from the payload (system-context writes such as import/seed/migration are exempt) |
sortable | boolean | true | Whether field appears in list view sort options |
externalId | boolean | false | Marks field as external ID for upsert operations |
trackHistory | boolean | — | Render this field's value changes as entries on the record activity timeline |
visibleWhen | string | Expression | — | CEL predicate; field is shown only when TRUE |
readonlyWhen | string | Expression | — | CEL predicate; field is read-only when TRUE |
requiredWhen | string | Expression | — | CEL predicate; field is required when TRUE |
Text Types
text
| Property | Type | Default | Validation Behavior |
|---|---|---|---|
maxLength | number | — | Rejects values exceeding character count |
minLength | number | — | Rejects values below character count |
format | string | — | Validates against format pattern (e.g., regex) |
Default constraints: None. Unbounded text unless maxLength is set.
textarea
| Property | Type | Default | Validation Behavior |
|---|---|---|---|
maxLength | number | — | Rejects values exceeding character count |
minLength | number | — | Rejects values below character count |
Default constraints: None. Multi-line text with no length limit by default.
email
| Property | Type | Default | Validation Behavior |
|---|---|---|---|
format | string | email | Validates a basic local@domain shape |
Default constraints: Must contain an @ and a domain with a dot — a lightweight pattern check, not full RFC 5322 validation.
url
| Property | Type | Default | Validation Behavior |
|---|---|---|---|
format | string | url | Validates URL format (protocol required) |
Default constraints: Must be a valid URL with protocol prefix.
phone
| Property | Type | Default | Validation Behavior |
|---|---|---|---|
format | string | phone | Validates a permissive phone-number character set |
Default constraints: Accepts digits, + ( ) - . and spaces (minimum 5 characters) — a lenient character-set check, not strict E.164 structural validation.
password
| Property | Type | Default | Validation Behavior |
|---|---|---|---|
maxLength | number | — | Maximum password length |
minLength | number | — | Minimum password length |
Default constraints: Validated the same as text (maxLength/minLength only). On a generic (non-better-auth) object a password-typed value is stored plaintext at rest but masked to •••••••• on read through the normal query path (ADR-0100) — the engine does not hash or encrypt it. One-way hashing and verification of real login credentials are owned entirely by the auth subsystem (better-auth); its own credential column (sys_account.password) is a hashed Field.text() column that is exempt from this masking. For a reversible, encrypted-at-rest value on your own objects, use the secret type instead.
secret
| Property | Type | Default | Validation Behavior |
|---|---|---|---|
maxLength | number | — | Maximum length of the cleartext value |
minLength | number | — | Minimum length of the cleartext value |
Default constraints: Reversible, encrypted-at-rest value (DB password, API key, token) — ADR-0100. Fail-closed: with no ICryptoProvider registered, a write throws rather than persisting cleartext. The value is encrypted on write into a sys_secret row, only an opaque handle is persisted on the record, and reads are masked. Distinct from password, which is plaintext at rest (or one-way hashed inside the auth subsystem).
Rich Content Types
markdown
| Property | Type | Default | Validation Behavior |
|---|---|---|---|
maxLength | number | — | Character limit on raw markdown source |
Default constraints: Accepts valid Markdown syntax. No length limit by default.
html
| Property | Type | Default | Validation Behavior |
|---|---|---|---|
maxLength | number | — | Character limit on raw HTML source |
Default constraints: HTML content is sanitized to prevent XSS. No length limit by default.
richtext
| Property | Type | Default | Validation Behavior |
|---|---|---|---|
maxLength | number | — | Character limit on serialized content |
Default constraints: Stored as structured rich text (e.g., ProseMirror/Tiptap JSON). Sanitized on save.
Number Types
number
| Property | Type | Default | Validation Behavior |
|---|---|---|---|
min | number | — | Rejects values below minimum |
max | number | — | Rejects values above maximum |
precision | number | — | Total number of digits allowed |
scale | number | — | Number of decimal places |
Default constraints: Any valid number. No range or precision limits by default.
currency
| Property | Type | Default | Validation Behavior |
|---|---|---|---|
min | number | — | Minimum monetary value |
max | number | — | Maximum monetary value |
currencyConfig.precision | number | 2 | Decimal places (0–10) |
currencyConfig.currencyMode | enum | dynamic | dynamic (user-selectable) or fixed (single currency) |
currencyConfig.defaultCurrency | string | CNY | 3-character currency code (ISO 4217 or crypto) |
Default constraints: Stored as a bare number (a finite numeric scalar — valueSchemaFor routes currency to z.number().finite()); there is no { value, currency } envelope on the value path. The per-record currency code is a separate concern, carried by currencyConfig above. Precision defaults to 2 decimal places.
percent
| Property | Type | Default | Validation Behavior |
|---|---|---|---|
min | number | — | Minimum percentage value |
max | number | — | Maximum percentage value |
precision | number | — | Total digits |
scale | number | — | Decimal places |
Default constraints: Stored as decimal (e.g., 0.85 for 85%). No range limit by default.
Date & Time Types
date
Required props: None.
Default constraints: Validates ISO 8601 date format (YYYY-MM-DD). No range restrictions.
datetime
Required props: None.
Default constraints: Validates ISO 8601 datetime format (YYYY-MM-DDTHH:mm:ssZ). Stored in UTC.
time
Required props: None.
Default constraints: Validates ISO 8601 time format (HH:mm:ss). 24-hour format.
Boolean Types
boolean
| Property | Type | Default | Validation Behavior |
|---|---|---|---|
defaultValue | boolean | — | Initial value when field is empty |
Default constraints: Only accepts true or false. Renders as checkbox.
toggle
| Property | Type | Default | Validation Behavior |
|---|---|---|---|
defaultValue | boolean | — | Initial value when field is empty |
Default constraints: Identical validation to boolean. Distinct UI — renders as toggle switch.
Selection Types
select
| Property | Type | Default | Validation Behavior |
|---|---|---|---|
options | SelectOption[] | — | Required. Static option list |
defaultValue | string | — | Must match an option value |
Option validation: Each option value must be a lowercase system identifier — starts with a letter, then letters/digits/underscores/dots (^[a-z][a-z0-9_.]*$), minimum 2 characters.
multiselect
| Property | Type | Default | Validation Behavior |
|---|---|---|---|
options | SelectOption[] | — | Required. Static option list |
Default constraints: Stores array of selected option values. Each value validated against options.
radio
| Property | Type | Default | Validation Behavior |
|---|---|---|---|
options | SelectOption[] | — | Required. Static option list |
Default constraints: Single-value selection. Same validation as select with radio button UI.
checkboxes
| Property | Type | Default | Validation Behavior |
|---|---|---|---|
options | SelectOption[] | — | Required. Static option list |
Default constraints: Multi-value selection. Same validation as multiselect with checkbox group UI.
Relational Types
lookup
| Property | Type | Default | Validation Behavior |
|---|---|---|---|
reference | string | — | Required. Target object name |
referenceFilters | string[] | — | Removed (#2377, ADR-0049) — no longer part of the Field schema; use lookupFilters + dependsOn instead |
deleteBehavior | enum | set_null | set_null, cascade, or restrict |
multiple | boolean | false | Allow multiple references; the value is validated as an array of ids |
Default constraints: Validates that referenced record exists. Foreign key integrity enforced.
Multi-value lookups (multiple: true). The empty set is representable: an emptied
multi-value lookup is stored and read back as [], never null — including when
deleteBehavior: 'set_null' removes the last remaining member. The declared contract
lives in the multiple and required doc blocks of
packages/spec/src/data/field.zod.ts (rendered in the
Field reference), and it makes required on a
multi-value lookup mean non-empty array.
The required-means-non-empty half is enforced by the record validator (#9476, per
the #9447 ruling): an explicit [] is rejected on insert, and on any update that
supplies the field — exactly as null and "" are on a single-value required field. An
update that omits the field never fails this check, so legacy rows stay editable. No
application-level emptiness check is needed.
user
| Property | Type | Default | Validation Behavior |
|---|---|---|---|
multiple | boolean | false | true stores a JSON array of user ids |
defaultValue | 'current_user' | — | Stamps the acting user's id on insert |
Default constraints: Person picker — a lookup specialized to the built-in sys_user object, so reference is implied and must not be authored. Stored as a foreign key to sys_user.id and resolved through the same $expand machinery as lookup; with multiple: true the stored value is a JSON array of ids.
master_detail
| Property | Type | Default | Validation Behavior |
|---|---|---|---|
reference | string | — | Required. Parent object name |
deleteBehavior | enum | cascade | Master-detail cascades at runtime unless set to restrict |
Default constraints: Enforces parent-child ownership. Child records cascade-delete with the parent by default.
tree
| Property | Type | Default | Validation Behavior |
|---|---|---|---|
reference | string | — | Required. Self-referencing object name |
Default constraints: Self-referencing lookup for hierarchical structures. Stored and expanded like a lookup; the engine does not run a cycle check on write, so a self-reference chain that loops back on itself is not automatically rejected.
Media Types
image
| Property | Type | Default | Validation Behavior |
|---|---|---|---|
multiple | boolean | false | Allow multiple image uploads |
Default constraints: Accepts common image MIME types. The schema has no field-level attachment-config property — fileAttachmentConfig was removed in the 16.x line (#2377).
file
| Property | Type | Default | Validation Behavior |
|---|---|---|---|
multiple | boolean | false | Allow multiple file uploads |
Default constraints: Accepts any file type. The Field schema exposes no field-level upload-validation config — fileAttachmentConfig (and its maxSize / allowedTypes / virus-scan sub-keys) was removed in the 16.x line (#2377). File-storage constraints such as virus scanning are configured on the file-storage integration, not on the field.
// File field
{
name: 'contract_pdf',
label: 'Contract',
type: 'file',
multiple: false
}avatar
Default constraints: Single image upload. Typically constrained to square aspect ratio and small file sizes.
video
Default constraints: Accepts common video MIME types. The field type has no per-field size/type config property in the schema.
audio
Default constraints: Accepts common audio MIME types. The field type has no per-field size/type config property in the schema.
Computed & System Types
formula
| Property | Type | Default | Validation Behavior |
|---|---|---|---|
expression | string | — | Required. CEL formula expression |
returnType | enum | — | Optional inferred result type: number, text, boolean, or date |
Default constraints: Read-only. Value computed at runtime from expression. Not directly writable.
{
name: 'full_name',
label: 'Full Name',
type: 'formula',
expression: 'record.first_name + " " + record.last_name'
}summary
| Property | Type | Default | Validation Behavior |
|---|---|---|---|
summaryOperations | object | — | Required. Roll-up definition |
summaryOperations.object | string | — | Child object to aggregate |
summaryOperations.field | string | — | Field to aggregate |
summaryOperations.function | enum | — | count, sum, min, max, or avg |
Default constraints: Read-only. Value computed from child records. Only valid on master objects.
autonumber
| Property | Type | Default | Validation Behavior |
|---|---|---|---|
autonumberFormat | string | — | Display format pattern (e.g., CASE-{0000}) |
Default constraints: Read-only. Auto-incremented. Cannot be manually set after creation.
Embedded Structured Types
These types store structured values as JSON on the parent row — no separate table and no
foreign key. Sub-field shapes are declared on the field, and the stored value is validated
as an open object map (valueSchemaFor), so sub-keys are not constrained by the field type
itself.
composite
Default constraints: Single embedded sub-object. Stored as a JSON object map (Record<string, unknown>). No per-type config properties.
repeater
Default constraints: Repeating embedded sub-object array. Stored as a JSON array of object maps (Array<Record<string, unknown>>); a non-array value is rejected. No per-type config properties.
record
Default constraints: Name-keyed map of embedded sub-objects (Record<string, SubObject>) — ADR-0007. Stored as a JSON object map; insertion order is display order. No per-type config properties.
Enhanced Types
location
Default constraints: Stored as { lat, lng, altitude?, accuracy? } — the keys are lat/lng, not latitude/longitude (LocationValueSchema, ADR-0104 D1). lat: -90 to 90. lng: -180 to 180. No per-type config properties.
address
Default constraints: Stored as structured object with street, city, state, postalCode, country, countryCode, formatted (all parts optional). No per-type config properties.
code
| Property | Type | Default | Validation Behavior |
|---|---|---|---|
language | string | — | Programming language for syntax highlighting |
Default constraints: Stored as plain text. Language used for UI syntax highlighting only.
json
Default constraints: Must be valid JSON. Parsed and validated on save.
color
Default constraints: Stores the color value as a string. No per-type config properties.
rating
| Property | Type | Default | Validation Behavior |
|---|---|---|---|
max | number | 5 | Maximum rating value (set by the Field.rating(max) factory) |
Default constraints: Integer between 0 and max.
slider
| Property | Type | Default | Validation Behavior |
|---|---|---|---|
min | number | — | Minimum slider value |
max | number | — | Maximum slider value |
step | number | 1 | Step increment |
Default constraints: Numeric value between min and max in step increments.
signature
Default constraints: Stored as base64-encoded image data. Immutable after creation in most workflows.
qrcode
Default constraints: Stores the encoded value; rendered as a scannable code. No per-type config properties.
progress
| Property | Type | Default | Validation Behavior |
|---|---|---|---|
min | number | — | Minimum value (typically 0) |
max | number | — | Maximum value (typically 100) |
Default constraints: Numeric value rendered as a progress bar. Usually 0–100.
tags
Default constraints: Stored as array of strings. A lone scalar value is coerced into a single-element array; the engine does not trim or deduplicate entries.
AI/ML Types
vector
| Property | Type | Default | Validation Behavior |
|---|---|---|---|
dimensions | number | — | Required. Vector size (1–10,000) |
Default constraints: Must be a numeric array of exactly dimensions length. Set the flat dimensions property directly — the legacy nested vectorConfig object was removed in the 16.x line (#2377) and is no longer part of the schema.
{
name: 'content_embedding',
label: 'Content Embedding',
type: 'vector',
dimensions: 1536
}Security & Compliance Properties
For sensitive data, use the properties and types the platform actually enforces:
| Property | Type | Default | Description |
|---|---|---|---|
requiredPermissions | string[] | — | Capabilities required to read/edit the field — masked on read, denied on write unless the caller holds all of them (ADR-0066 D3) |
trackHistory | boolean | — | Render the field's value changes as entries on the record activity timeline |
For reversible encrypted-at-rest values (API keys, tokens, DB passwords), use the
secret field type — it is the type with an enforced encryption/masking code path
(encrypted at rest via sys_secret, masked on read). password is validated like
plain text and stored plaintext at rest with no built-in hashing or encryption; it is
masked to •••••••• on read but offers no at-rest protection (see the password
section above). See the
Field Type Gallery.
Quick Validation Summary
| Field Type | Required Props | Key Constraints |
|---|---|---|
text | — | maxLength, minLength, format |
textarea | — | maxLength, minLength |
email | — | Basic local@domain shape (not full RFC 5322) |
url | — | Valid URL with protocol |
phone | — | Permissive character set, not strict E.164 |
password | — | Validated like text; masked on read but plaintext at rest (no hashing/encryption) |
secret | — | Encrypted at rest via sys_secret, masked on read; fail-closed — writes throw with no ICryptoProvider |
markdown | — | maxLength |
html | — | Sanitized, maxLength |
richtext | — | Sanitized, maxLength |
number | — | min, max, precision, scale |
currency | — | currencyConfig (precision, mode, code) |
percent | — | min, max, stored as decimal |
date | — | ISO 8601 date |
datetime | — | ISO 8601 datetime, UTC |
time | — | ISO 8601 time, 24h |
boolean | — | true / false only |
toggle | — | true / false only |
select | options | Option values must be lowercase system identifiers |
multiselect | options | Array of valid option values |
radio | options | Single value from options |
checkboxes | options | Array of valid option values |
lookup | reference | Foreign key integrity |
user | — | Lookup specialized to sys_user; multiple: true stores an id array |
master_detail | reference | Cascade delete, ownership |
tree | reference | Self-referencing; no automatic cycle check |
image | — | Common image MIME types; multiple for many |
file | — | Any file type; multiple for many (no field-level upload config) |
avatar | — | Single image, typically square |
video | — | Common video MIME types |
audio | — | Common audio MIME types |
formula | expression | Read-only, computed at runtime |
summary | summaryOperations | Read-only, roll-up from children |
autonumber | — | Read-only, auto-incremented |
composite | — | Single embedded sub-object; stored as a JSON object map |
repeater | — | Embedded sub-object array; stored as a JSON array of object maps |
record | — | Name-keyed map of embedded sub-objects (ADR-0007) |
location | — | Lat: -90–90, Lng: -180–180 |
address | — | Structured object (street, city, …) |
code | — | Plain text, language for highlighting |
json | — | Must be valid JSON |
color | — | Stored as a string |
rating | — | 0 to max (default 5) |
slider | — | min to max in step increments |
signature | — | Base64 image, typically immutable |
qrcode | — | Format-specific validation |
progress | — | Numeric, typically 0–100 |
tags | — | String array; no automatic trim/dedup |
vector | dimensions | Numeric array of exact dimensions length |