| title | Validation rules — reject bad records |
|---|---|
| navTitle | Field Validation Rules |
| description | 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'`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 |
| Property | Type | Default | Validation Behavior |
|---|---|---|---|
maxLength |
number |
— | Rejects values exceeding character count |
minLength |
number |
— | Rejects values below character count |
format |
string |
— | Not validated. No write-time check reads it — a regex here is accepted and ignored. On text it is a display hint only (a small set of words such as phone, email or url promote the cell to a richer renderer; see the Field Type Gallery). To constrain the value's shape, use the email / url / phone field type or a format validation rule |
valueDomain |
'iana_time_zone' | 'iso_4217_currency' | 'iso_3166_alpha2' |
— | Constrains the written value to a published standard — an IANA time zone (judged by the Intl.DateTimeFormat probe, so UTC and Asia/Kolkata are members and Europe/Munich is not), an ISO 4217 currency code or an ISO 3166-1 alpha-2 country code (both exact uppercase). Membership, not shape: a pattern such as ^[A-Z]{2}$ admits ZZ; the domain does not. The same closed vocabulary and the same membership test as a settings specifier's valueDomain; a non-member is refused on the write path with the field error code value_domain. text only — declaring it on any other type is refused at parse. |
Default constraints: None. Unbounded text unless maxLength is set.
| 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.
| Property | Type | Default | Validation Behavior |
|---|---|---|---|
maxLength |
number |
— | Rejects values exceeding character count |
minLength |
number |
— | Rejects values below character count |
Default constraints: Must contain an @ and a domain with a dot — a lightweight pattern check, not full RFC 5322 validation. The check is keyed on type: 'email' itself and has nothing to configure; a field-level format key is not read.
| Property | Type | Default | Validation Behavior |
|---|---|---|---|
maxLength |
number |
— | Rejects values exceeding character count |
minLength |
number |
— | Rejects values below character count |
Default constraints: Must be a valid URL with protocol prefix. The check is keyed on type: 'url' itself and has nothing to configure; a field-level format key is not read.
| Property | Type | Default | Validation Behavior |
|---|---|---|---|
maxLength |
number |
— | Rejects values exceeding character count |
minLength |
number |
— | Rejects values below character count |
Default constraints: Accepts digits, + ( ) - . and spaces (minimum 5 characters) — a lenient character-set check, not strict E.164 structural validation. The check is keyed on type: 'phone' itself and has nothing to configure; a field-level format key is not read. For a stricter shape, add a format validation rule with a regex.
| 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.
| 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).
| Property | Type | Default | Validation Behavior |
|---|---|---|---|
maxLength |
number |
— | Character limit on raw markdown source |
Default constraints: Accepts valid Markdown syntax. No length limit by default.
| 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.
| 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.
| 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.
| Property | Type | Default | Validation Behavior |
|---|---|---|---|
min |
number |
— | Minimum monetary value |
max |
number |
— | Maximum monetary value |
currencyConfig.currencyMode |
enum |
dynamic |
fixed pins the field to defaultCurrency; dynamic displays the tenant default currency (the localization.currency setting). Not a per-record choice |
currencyConfig.defaultCurrency |
string |
CNY |
3-character currency code (ISO 4217 or crypto): the field's currency under fixed; not the displayed currency under dynamic |
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 currency code is never stored per record: it is field configuration, carried by currencyConfig above. Decimal places are the currency's ISO 4217 minor unit (2 for USD, 0 for JPY, 3 for KWD) and are declared nowhere — currencyConfig has no decimal-places key (currencyConfig.precision is refused, with a prescription to delete it), and the field-level precision is the total digit count, never compared with the currency.
| 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.
Required props: None.
Default constraints: Validates ISO 8601 date format (YYYY-MM-DD). No range restrictions.
Required props: None.
Default constraints: Validates ISO 8601 datetime format (YYYY-MM-DDTHH:mm:ssZ). Stored in UTC.
Required props: None.
Default constraints: Validates ISO 8601 time format (HH:mm:ss). 24-hour format.
| Property | Type | Default | Validation Behavior |
|---|---|---|---|
defaultValue |
boolean |
— | Initial value when field is empty |
Default constraints: Only accepts true or false. Renders as checkbox.
| 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.
| Property | Type | Default | Validation Behavior |
|---|---|---|---|
options |
SelectOption[] |
— | Required — a non-empty static option list, or a picklist naming a shared list instead. With neither (or options: []) the field is refused at parse. |
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.
| Property | Type | Default | Validation Behavior |
|---|---|---|---|
options |
SelectOption[] |
— | Static option list. Optional: without options the field takes free-form values (tags mode). |
Default constraints: Stores array of selected option values. Each value validated against options.
| Property | Type | Default | Validation Behavior |
|---|---|---|---|
options |
SelectOption[] |
— | Required — as for select: a non-empty list or a picklist, refused at parse with neither. |
Default constraints: Single-value selection. Same validation as select with radio button UI.
| Property | Type | Default | Validation Behavior |
|---|---|---|---|
options |
SelectOption[] |
— | Static option list. Parses without one, but draws a completeness warning: a checkbox group with no boxes renders nothing. |
Default constraints: Multi-value selection. Same validation as multiselect with checkbox group UI.
| 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.
| 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.
| 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.
| Property | Type | Default | Validation Behavior |
|---|---|---|---|
reference |
string |
— | Optional. If given, must be this object's own name — a tree whose reference names any other object is refused at parse (fields.<field>.reference); a link to a different object is a lookup |
Default constraints: Self-referencing lookup for hierarchical structures — reference is optional and, if given, must be this object. 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.
| 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).
| 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
}Default constraints: Single image upload. Typically constrained to square aspect ratio and small file sizes.
Default constraints: Accepts common video MIME types. The field type has no per-field size/type config property in the schema.
Default constraints: Accepts common audio MIME types. The field type has no per-field size/type config property in the schema.
| Property | Type | Default | Validation Behavior |
|---|---|---|---|
expression |
string |
— | Required. CEL formula expression |
returnType |
enum |
— | Optional inferred result type: number, text, boolean, date, or currency |
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'
}expression on a stored field. A field of any other type may declare expression too, and it validates differently from a formula. The engine computes the value on every insert and every update by id, and stores it. A value a client sends for the field never stands: it is overwritten by the computed one, and the write still succeeds. When the value sent differs from the computed one, the write reports the field as dropped, under reason: 'stored_expression': the engine's onFieldsDropped listener hears it, and the data API answers it as droppedFields. A client that sends the computed value, or no value, is told nothing. An in-process caller that passes the engine write option strictReadonlyWrites: true is refused instead, with ERR_READONLY_FIELD_REJECTED, and nothing is written; a request body cannot set that option (see WriteObservabilityOptions). Without it, the one refusal is a predicate update that moves an input: when the matched rows would compute different values, the update is refused with VALIDATION_FAILED and an invalid_value error naming the field, and nothing is written. See expression on a stored field and the expression row of FieldSchema.
| 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.
| 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.
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.
Default constraints: Single embedded sub-object. Stored as a JSON object map (Record<string, unknown>). No per-type config properties.
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.
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.
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.
Default constraints: Stored as structured object with street, city, state, postalCode, country, countryCode, formatted (all parts optional). No per-type config properties.
| 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.
Default constraints: Must be valid JSON. Parsed and validated on save.
Default constraints: Stores the color value as a string. No per-type config properties.
| 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.
| 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.
Default constraints: Stored as base64-encoded image data. Immutable after creation in most workflows.
Default constraints: Stores the encoded value; rendered as a scannable code. No per-type config properties.
| 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.
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.
| 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
}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.
| Field Type | Required Props | Key Constraints |
|---|---|---|
text |
— | maxLength, minLength, valueDomain (format is a display hint, not a constraint) |
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 (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 |
— | 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 |