|
| 1 | +--- |
| 2 | +title: Field Types |
| 3 | +description: Every field type you can declare on an object — what it stores, what options it accepts, how it surfaces in REST, Studio, and the AI Builder. |
| 4 | +--- |
| 5 | + |
| 6 | +# Field Types |
| 7 | + |
| 8 | +53 built-in field types, grouped by family. The full Zod schema is in |
| 9 | +[`packages/spec/src/data/field.zod.ts`](https://github.com/objectstack-ai/framework/blob/main/packages/spec/src/data/field.zod.ts) — this page is a working summary. |
| 10 | + |
| 11 | +## Core properties (every field) |
| 12 | + |
| 13 | +| Property | Type | Default | Purpose | |
| 14 | +|:--|:--|:--|:--| |
| 15 | +| `name` | string (snake_case) | — | Machine identifier — REST path segment, SQL column | |
| 16 | +| `label` | string | — | Display label in Studio | |
| 17 | +| `type` | FieldType | — | See type tables below | |
| 18 | +| `required` | boolean | `false` | NOT NULL constraint | |
| 19 | +| `unique` | boolean | `false` | Unique index | |
| 20 | +| `searchable` | boolean | `false` | Indexed for `/api/v1/search` | |
| 21 | +| `multiple` | boolean | `false` | Store an array of values | |
| 22 | +| `defaultValue` | unknown | — | Initial value (literal or CEL) | |
| 23 | +| `columnName` | string | = `name` | Override physical DB column | |
| 24 | +| `hidden` | boolean | `false` | Hide from default Studio views | |
| 25 | +| `readonly` | boolean | `false` | Disable in forms | |
| 26 | +| `system` | boolean | `false` | Auto-injected (id, created_at, …) | |
| 27 | +| `index` | boolean | `false` | Create a DB index | |
| 28 | +| `externalId` | boolean | `false` | Eligible for upsert via external key | |
| 29 | +| `inlineHelpText` | string | — | Tooltip / helper text | |
| 30 | +| `conditionalRequired` | `P` predicate | — | Required when CEL is true | |
| 31 | +| `auditTrail` | boolean | `false` | Track every change in audit log | |
| 32 | +| `encryptionConfig` | object | — | Field-level encryption (see [Security](./security)) | |
| 33 | +| `maskingRule` | string | — | PII masking pattern | |
| 34 | + |
| 35 | +## Text family |
| 36 | + |
| 37 | +| Type | Use for | Key options | |
| 38 | +|:--|:--|:--| |
| 39 | +| `text` | short strings | `maxLength`, `minLength` | |
| 40 | +| `textarea` | multi-line | `maxLength` | |
| 41 | +| `email` | email | format-validated; lowercased | |
| 42 | +| `url` | URL | format-validated | |
| 43 | +| `phone` | phone | E.164 | |
| 44 | +| `password` | secrets | hashed; never returned by GET | |
| 45 | +| `markdown` | markdown body | rendered in Studio preview | |
| 46 | +| `html` | sanitised HTML | DOMPurify on write | |
| 47 | +| `richtext` | WYSIWYG | Studio editor + serialised JSON | |
| 48 | + |
| 49 | +## Numbers |
| 50 | + |
| 51 | +| Type | Use for | Key options | |
| 52 | +|:--|:--|:--| |
| 53 | +| `number` | floats | `min`, `max`, `precision`, `scale` | |
| 54 | +| `currency` | money | `currencyConfig: { precision, currencyMode: 'fixed' \| 'dynamic', defaultCurrency }` | |
| 55 | +| `percent` | 0–100 % | `min`, `max`, `scale` | |
| 56 | + |
| 57 | +> `integer` and `decimal` aren't separate types — use `number` with |
| 58 | +> `scale: 0` for integer, `precision`+`scale` for fixed decimal. |
| 59 | +
|
| 60 | +## Date / time |
| 61 | + |
| 62 | +| Type | Stores | Notes | |
| 63 | +|:--|:--|:--| |
| 64 | +| `date` | calendar date | no timezone | |
| 65 | +| `datetime` | instant | UTC | |
| 66 | +| `time` | wall-clock time | no date | |
| 67 | + |
| 68 | +## Logic |
| 69 | + |
| 70 | +| Type | Notes | |
| 71 | +|:--|:--| |
| 72 | +| `boolean` | Checkbox | |
| 73 | +| `toggle` | Same as boolean, switch UI | |
| 74 | + |
| 75 | +## Selection |
| 76 | + |
| 77 | +| Type | Notes | |
| 78 | +|:--|:--| |
| 79 | +| `select` | Single choice — options declared inline or referenced from a picklist | |
| 80 | +| `multiselect` | Many choices, stored as an array | |
| 81 | +| `radio` | UI alias for `select` with radio rendering | |
| 82 | +| `checkboxes` | UI alias for `multiselect` with checkbox rendering | |
| 83 | + |
| 84 | +Options shape: |
| 85 | + |
| 86 | +```ts |
| 87 | +options: [ |
| 88 | + { value: 'low', label: 'Low' }, |
| 89 | + { value: 'high', label: 'High', color: '#e02' }, |
| 90 | + { value: 'urgent', label: 'Urgent', color: '#c00' } |
| 91 | +] |
| 92 | +``` |
| 93 | + |
| 94 | +## Relations |
| 95 | + |
| 96 | +| Type | Cardinality | Semantics | |
| 97 | +|:--|:--|:--| |
| 98 | +| `lookup` | many-to-one | Loose reference; deleting the parent doesn't delete the child by default | |
| 99 | +| `master_detail` | many-to-one, cascading | Child cannot exist without parent; permissions inherit from parent | |
| 100 | +| `tree` | self-reference | Hierarchical (parent_id on the same object) | |
| 101 | + |
| 102 | +Common options: |
| 103 | + |
| 104 | +```ts |
| 105 | +{ |
| 106 | + type: 'lookup', |
| 107 | + reference: 'account', // target object name |
| 108 | + referenceFilters: ['status:active'], // narrow the lookup picker |
| 109 | + deleteBehavior: 'set_null' // 'set_null' | 'cascade' | 'restrict' |
| 110 | +} |
| 111 | +``` |
| 112 | + |
| 113 | +## Computed |
| 114 | + |
| 115 | +| Type | What it does | Key option | |
| 116 | +|:--|:--|:--| |
| 117 | +| `formula` | Derived value, evaluated on read or recalc | `expression: F\`record.qty * record.unit_price\`` — see [CEL](./cel) | |
| 118 | +| `summary` | Roll-up over a child relation | `summaryOperations: { object, field, function }` (`count` \| `sum` \| `avg` \| `min` \| `max`) | |
| 119 | +| `autonumber` | Auto-incrementing display number | `format` (e.g. `TKT-{0000}`), `startAt` | |
| 120 | + |
| 121 | +Formula example: |
| 122 | + |
| 123 | +```ts |
| 124 | +{ |
| 125 | + name: 'profit_margin', |
| 126 | + type: 'formula', |
| 127 | + expression: F`(record.revenue - record.cost) / record.revenue * 100` |
| 128 | +} |
| 129 | +``` |
| 130 | + |
| 131 | +## Media |
| 132 | + |
| 133 | +| Type | Use for | Key option | |
| 134 | +|:--|:--|:--| |
| 135 | +| `image` | image attachments | `fileAttachmentConfig` (see below) | |
| 136 | +| `file` | any file | same | |
| 137 | +| `avatar` | profile pictures | square crop, sensible defaults | |
| 138 | +| `video` | video uploads | duration + thumbnail capture | |
| 139 | +| `audio` | audio | waveform preview | |
| 140 | + |
| 141 | +```ts |
| 142 | +fileAttachmentConfig: { |
| 143 | + maxSize: 10_000_000, // bytes |
| 144 | + allowedTypes: ['image/png','image/jpeg'], |
| 145 | + virusScan: true, |
| 146 | + storageProvider: 's3', // see Configure → Storage |
| 147 | + imageValidation: { minWidth: 200, maxWidth: 4096, generateThumbnails: ['sm','md','lg'] } |
| 148 | +} |
| 149 | +``` |
| 150 | + |
| 151 | +## Structured |
| 152 | + |
| 153 | +| Type | Stores | Notes | |
| 154 | +|:--|:--|:--| |
| 155 | +| `json` | arbitrary JSON | Stored as JSONB on Postgres | |
| 156 | +| `composite` | sub-record with named fields | Inline struct, not a separate table | |
| 157 | +| `repeater` | array of composite values | One-to-many without a child object | |
| 158 | + |
| 159 | +## Enhanced UI |
| 160 | + |
| 161 | +| Type | Notes | |
| 162 | +|:--|:--| |
| 163 | +| `location` | lat/long + accuracy | |
| 164 | +| `address` | street / city / region / postal / country | |
| 165 | +| `code` | source-code field — `language`, `theme`, `lineNumbers` | |
| 166 | +| `color` | `colorFormat: 'hex' \| 'rgb' \| 'rgba' \| 'hsl'`, `presetColors[]` | |
| 167 | +| `rating` | 1–N stars — `max`, `icon` | |
| 168 | +| `slider` | bounded number with slider UI | |
| 169 | +| `signature` | drawn signature, stored as image | |
| 170 | +| `qrcode` | renders the value as a QR — `qrErrorCorrection: 'L' \| 'M' \| 'Q' \| 'H'` | |
| 171 | +| `barcode` | EAN/UPC/Code128 — `barcodeFormat` | |
| 172 | +| `progress` | derived percent rendered as a bar | |
| 173 | +| `tags` | free-form tag array with autocomplete | |
| 174 | +| `vector` | embedding column — `vectorConfig: { dimensions, distanceMetric: 'cosine' \| 'euclidean', indexed, indexType: 'hnsw' \| 'ivfflat' }` | |
| 175 | + |
| 176 | +## System fields (auto-injected on every object) |
| 177 | + |
| 178 | +| Field | Type | Notes | |
| 179 | +|:--|:--|:--| |
| 180 | +| `id` | text (ULID) | primary key | |
| 181 | +| `created_at` | datetime | UTC insert time | |
| 182 | +| `updated_at` | datetime | UTC last-write time | |
| 183 | +| `created_by` | lookup → `user` | who inserted | |
| 184 | +| `updated_by` | lookup → `user` | who last wrote | |
| 185 | +| `version` | integer | optimistic-concurrency token | |
| 186 | + |
| 187 | +You don't declare these — opt out per object with |
| 188 | +`ObjectSpec.systemFields: false` (rarely a good idea). |
| 189 | + |
| 190 | +## How fields flow through the stack |
| 191 | + |
| 192 | +``` |
| 193 | +*.object.ts (field spec) |
| 194 | + │ |
| 195 | + ├─► Postgres / MySQL / SQLite column + index + constraint |
| 196 | + ├─► REST: validated on POST/PATCH, exposed on GET |
| 197 | + ├─► Studio: form widget + list column |
| 198 | + ├─► AI Builder: tool argument schema (so the AI knows what to ask) |
| 199 | + └─► Audit: change-tracked if `auditTrail: true` |
| 200 | +``` |
| 201 | + |
| 202 | +## See also |
| 203 | + |
| 204 | +- [Build → Data model](/docs/build/data-model) — composing fields into objects |
| 205 | +- [CEL](./cel) — `expression`, `conditionalRequired`, validations |
| 206 | +- [ObjectQL](./objectql) — querying these fields |
| 207 | +- [REST API](./rest-api) — endpoints that produce / consume them |
| 208 | +- [`@objectstack/spec/data/field.zod.ts`](https://github.com/objectstack-ai/framework/blob/main/packages/spec/src/data/field.zod.ts) — authoritative schema |
0 commit comments