Type System
Complete reference for ObjectQL field types - Scalars, relationships, computed fields, and advanced types
ObjectQL provides 20+ specialized field types that encode business semantics, not just data storage primitives. Each type understands its purpose and automatically configures database schemas, UI renderers, validation rules, and API serialization.
Type Philosophy
Traditional databases:
-- Just stores data
CREATE TABLE customer (
revenue DECIMAL(18,2) -- Is this USD? EUR? Monthly? Annual?
);ObjectQL:
# Encodes business meaning
revenue:
type: currency
label: Annual Revenue
scale: 2
precision: 18
# Automatically knows:
# - Store amount + currency code
# - UI shows currency symbol
# - Validate numeric precision
# - Format for display ($1,234.56)Type Categories
Scalar Types
Primitive values: text, numbers, dates, booleans
Relationship Types
Lookups, master-detail, hierarchical (tree) references
Computed Types
Formulas, rollup summaries, auto-numbers
Complex Types
JSON, tags, geolocation, file attachments
1. Scalar Types
Text Types
text
Single-line text field.
company_name:
type: text
label: Company Name
maxLength: 255
required: true
searchable: trueDatabase mapping:
- SQL driver:
TEXTon every dialect.maxLengthis enforced by record validation, not by the column type — the DDL does not read it. - MongoDB:
String
UI rendering:
<input type="text" maxlength="255" required />Use cases:
- Names, titles, identifiers
- Email addresses, phone numbers
- Short descriptions
textarea
Multi-line plain text.
description:
type: textarea
label: Description
maxLength: 5000Database mapping:
- SQL driver:
TEXT - MongoDB:
String
UI rendering:
<textarea rows="10" maxlength="5000"></textarea>Use cases:
- Comments, notes
- Descriptions
- Plain text content
html
Rich text with HTML markup.
bio:
type: html
label: BiographyDatabase mapping:
- SQL driver:
TEXT - MongoDB:
String
UI rendering:
<!-- Rich text editor (TinyMCE, Quill, etc.) -->
<div class="rich-text-editor"></div>Use cases:
- Blog posts, articles
- Product descriptions
- Email templates
email
Email address with validation.
email:
type: email
label: Email Address
required: true
unique: organizationValidation: a deliberately permissive, ReDoS-safe shape check — a local part,
an @, and a dotted domain (invalid_email otherwise). It is not an RFC 5322
parser, the value is not lowercased, and no DNS/MX lookup is performed. Stricter
rules belong in a validation rule or custom validator.
Database mapping:
- SQL driver:
VARCHAR(255) - MongoDB:
String
Use cases:
- User emails
- Contact information
- Notification addresses
url
URL with protocol validation.
website:
type: url
label: WebsiteValidation: accepts any scheme://… (not just http/https — libsql://,
postgres://, s3://, file:// all pass), plus root-/protocol-relative refs
(/path, //host/path) and data: / blob: URIs. A bare scheme-less string
with no leading / is rejected (invalid_url). There is no domain-format
check and no reachability check.
Use cases:
- Company websites
- Social media links
- API endpoints
phone
Phone number with international format.
phone:
type: phone
label: Phone NumberStorage format: the string as entered — a VARCHAR(255) column. The engine
does not normalize to E.164 and does not reformat for display.
Validation: a shape check only — at least 5 characters drawn from digits and
+ ( ) - . and whitespace (invalid_phone otherwise). There is no country-code
verification and no SMS-capability check. If you need E.164 at rest, normalize in
a before-save trigger or a custom validator.
Numeric Types
number
General-purpose numeric field.
quantity:
type: number
label: Quantity
min: 0
max: 9999
scale: 0 # Integer
defaultValue: 1Configuration:
scale: Decimal places (0 = integer)precision: Total digitsmin/max: Range validation
Database mapping:
- SQL driver: a floating-point column (
REALon PostgreSQL/SQLite,FLOATon MySQL).precision/scaleare validation and display metadata — the DDL does not emitNUMERIC(precision, scale). - MongoDB:
Number
Use cases:
- Quantities, counts
- Ratings, scores
- General measurements
currency
Money amount with currency code.
annual_revenue:
type: currency
label: Annual Revenue
currencyConfig:
precision: 2
currencyMode: fixed
defaultCurrency: USDStorage: a bare number — the same value shape as number.
1234.56A currency value is not a { value, currency } object. The currency code
lives once on the field definition (currencyConfig.defaultCurrency), not on
every stored value. The old per-value object shape (CurrencyValueSchema) was
never consumed by the validator, the driver, or import coercion and is
deprecated in the spec.
Features:
currencyMode: fixed | dynamicand adefaultCurrencycode on the field- Codes are validated by length only (3 characters), so ISO 4217 (
USD,EUR,CNY) and non-ISO codes (BTC,ETH) both pass precision(0–10, default 2) for decimal places
Database mapping:
- SQL driver: a floating-point column (
REAL/FLOAT) — one column, no companion currency column and no JSON blob - MongoDB:
Number
percent
Percentage value (0-100).
discount_rate:
type: percent
label: Discount
scale: 2
min: 0
max: 100Display: 25.5% (automatically adds % symbol)
Storage: the percentage number itself — 25.5 means 25.5%, matching the
min: 0 / max: 100 bounds above. It is not rescaled to a 0–1 ratio on write.
Physically it is the same floating-point column as number.
The separate percent template filter ({{ record.rate | percent }}) does
take a 0–1 ratio and render it as 42%. That is a formatting choice at render
time, not the field type's storage convention — don't mix the two.
Use cases:
- Discounts, margins
- Completion rates
- Tax rates
Date/Time Types
date
Calendar date (no time component).
birth_date:
type: date
label: Date of BirthStorage format: a timezone-naive calendar day — the YYYY-MM-DD string
2024-01-15, never an instant (ADR-0053). A Date collapses to its UTC
calendar day; a longer ISO string is truncated to its leading 10 characters. The
same normalization is applied on write, on read, and to every filter comparand,
so the two sides of a comparison can never disagree about what a date is.
A date is never converted to a timestamp and never timezone-shifted. Storing
UTC-midnight instants is exactly the "date-as-instant" mistake ADR-0053 removed —
it renders as the previous day for any viewer west of UTC. If a value genuinely
depends on a timezone, it is a datetime, not a date.
Database mapping:
- SQL driver:
DATEon PostgreSQL/MySQL,TEXTon SQLite — holdingYYYY-MM-DDon every dialect - MongoDB: no DDL (schemaless); the driver stores the value it is given
Use cases:
- Birthdays, anniversaries
- Contract dates
- Deadlines
datetime
A UTC instant.
meeting_time:
type: datetime
label: Meeting TimeStorage format: the canonical, fixed-width, zone-explicit UTC instant
YYYY-MM-DDTHH:MM:SS.sssZ — e.g. 2024-01-15T14:30:00.000Z. Milliseconds are
always present and the zone is always Z; an offset-bearing input such as
…T22:30:00+08:00 is rewritten to the equivalent …Z instant so text ordering
stays chronological ordering.
Database mapping:
- PostgreSQL:
timestamptz - MySQL:
DATETIME(3)— deliberately notTIMESTAMP, which is a 32-bit epoch (a 2038 ceiling on the column every list view sorts by), drops the milliseconds, and converts on read/write using the session timezone. The canonical instant is bound as a MySQL literal (YYYY-MM-DD HH:MM:SS.sss, noT/Z) because MySQL rejects ISO-8601 in a datetime literal. - SQLite:
TEXTholding the canonical string — fixed width plus UTC means lexicographic order is chronological order, so range filters use the index - MongoDB: no DDL (schemaless)
Filter comparands go through the same canonicalization function as writes, on
every dialect. That is what makes $gte/$lt windows and $eq behave
identically on SQLite, PostgreSQL, and MySQL instead of depending on the shape
the caller happened to pass.
time
Time of day (no date component).
business_hours_start:
type: time
label: Business Hours Start
defaultValue: "09:00:00"Storage format: HH:MM:SS, gaining a .fff millisecond suffix only when
the milliseconds are non-zero (14:30:00, but 14:30:00.100). Input accepts
HH:MM or HH:MM:SS (with an optional fractional part and Z/offset); 14:30
is completed to 14:30:00, so one wall clock can never split into several stored
values. A Date, an epoch, or a full timestamp folds to its UTC time-of-day.
A time is a wall-clock value, not an instant: it is validated as a time-of-day,
not parsed as a date.
Database mapping:
- PostgreSQL:
time - MySQL:
TIME(3)— bareTIMEis zero-precision and rounds a fractional literal (14:30:00.500→14:30:01), which would change the stored wall clock - SQLite:
TEXTholding the canonical string - MongoDB: no DDL (schemaless)
As with date and datetime, filter comparands are canonicalized by the same
function as writes, so 09:00 <= t <= 18:00 windows compare like against like.
Use cases:
- Business hours
- Recurring event times
- Time-based triggers
defaultValue: 'NOW()' on temporal fields
NOW() is a framework convention meaning "use the database clock at insert
time". The driver translates it into a dialect-native default that resolves
against the UTC clock on every dialect, for date, datetime, and time
alike:
opened_at:
type: datetime
label: Opened At
defaultValue: "NOW()"For date and time on PostgreSQL and MySQL the driver emits an explicit
UTC expression default rather than a bare CURRENT_TIMESTAMP, which resolves
the calendar day / wall clock in the server's timezone on PostgreSQL and the
inserting session's timezone on MySQL — one instant producing three different
stored values across the three dialects (and MySQL 8.0 rejects a bare
CURRENT_TIMESTAMP default on DATE/TIME columns outright). On SQLite all
three types use strftime(…, 'now') expressions that emit the canonical form
directly. datetime on PostgreSQL/MySQL keeps the native now(), which is
already UTC — the driver pins every MySQL connection with
SET time_zone = '+00:00'.
A DDL default only governs newly created columns. A column created before
this convention keeps its legacy default and can still emit a zone-naive value on
a defaulted insert; the read path repairs those to canonical form, so find()
stays uniform without a data migration.
Boolean Types
boolean
True/false value.
is_active:
type: boolean
label: Active
defaultValue: trueStorage:
- SQL driver:
BOOLEAN(SQLite stores1/0; the driver coerces it back to a real JS boolean on read) - MongoDB:
Boolean
UI rendering:
<input type="checkbox" />
<!-- or -->
<select>
<option value="true">Yes</option>
<option value="false">No</option>
</select>toggle
Boolean displayed as a toggle switch.
email_opt_in:
type: toggle
label: Subscribe to Newsletter
defaultValue: falseDifference from boolean:
- Always renders as a toggle switch (not a dropdown)
- Typically used for consent, preferences, on/off settings
Selection Types
select
Dropdown/picklist from predefined options.
priority:
type: select
label: Priority
options:
- value: low
label: Low
color: green
- value: medium
label: Medium
color: yellow
- value: high
label: High
color: orange
- value: critical
label: Critical
color: red
defaultValue: medium
required: trueStorage: Stores value (not label). Option values must be lowercase
machine identifiers — the spec rejects New, In Progress, or Closed_Won.
Database mapping:
- SQL driver:
VARCHAR(255)— the driver never emits a nativeENUM, so adding an option is a metadata change, not a schema migration - MongoDB:
String
Use cases:
- Status, stage, priority
- Categories, types
- Fixed value lists
multiselect
Multiple selection from options.
tags:
type: multiselect
label: Tags
options:
- { value: customer, label: Customer }
- { value: partner, label: Partner }
- { value: vendor, label: Vendor }
# multiple: true # Implied by typeStorage:
- SQL driver: a
JSONcolumn holding the serialized array — not a nativeTEXT[], so the same DDL works on SQLite and MySQL - MongoDB:
[String]
Example value: ['customer', 'partner']
radio
Radio button group (single selection).
contact_method:
type: radio
label: Preferred Contact Method
options:
- { value: email, label: Email }
- { value: phone, label: Phone }
- { value: sms, label: SMS }Difference from select:
- All options visible (no dropdown)
- Better UX for 2-5 options
2. Relationship Types
lookup
Foreign key reference to another object.
account_id:
type: lookup
label: Account
reference: account
required: true
lookupFilters:
- field: is_active
operator: eq
value: true
deleteBehavior: set_null # or restrict, cascadeStorage: Stores id of referenced record
Query behavior:
// Expand the account lookup
const opportunities = await engine.find('opportunity', {
fields: ['name', 'account.company_name'] // Expands account
});On Delete Options:
set_null: Set field to null when referenced record is deletedrestrict: Prevent deletion if references existcascade: Delete this record when referenced record is deleted
Required foreign keys. A
required: truelookup cannot be nulled, so the defaultset_nullautomatically escalates torestricton such a field — deleting the parent is refused with409 DELETE_RESTRICTED(the response carriesdependentObjectanddependentCount) instead of a confusing "<field> is required" validation error. To delete the children along with the parent, setdeleteBehavior: cascadeexplicitly. An explicitset_nullorcascadeis always honored as written.The refusal carries two messages, for two audiences.
erroris written for the person who clicked delete: it is rendered in the caller's locale from the built-in catalog and names the objects and the field by their labels, so a client may show it to an end user as-is.developerMessageis the operator's copy — English, API names, and thedeleteBehavior: cascaderemedy — and should not be surfaced to end users. Override any locale's sentence with atranslationitem undererrors.delete_restricted/errors.delete_restricted_required.
Multiple lookups:
contacts:
type: lookup
reference: contact
multiple: true # Many-to-manyDatabase mapping:
- SQL driver:
VARCHAR(255)holding the related record id. Not a nativeUUIDcolumn — record ids are opaque strings. Amultiple: truelookup becomes aJSONcolumn instead. - MongoDB:
String
A relationship field authored with reference: gets no database-level
FOREIGN KEY constraint. The SQL driver's FK DDL is gated on a reference_to
property that the spec's reference never populates, and master_detail /
tree do not reach that branch at all. Referential integrity is enforced by the
engine instead: deleteBehavior is applied on delete, which is what produces
the 409 DELETE_RESTRICTED above.
master_detail
Strong parent-child relationship.
project_id:
type: master_detail
label: Project
reference: project
deleteBehavior: cascade # Enforced by type usuallyCharacteristics:
- Ownership: Child cannot exist without parent
- Cascade delete: Deleting parent deletes all children
- Sharing inheritance: Child inherits parent's permissions
- Rollup support: Parent can aggregate child data
Difference from lookup:
| Feature | Lookup | Master-Detail |
|---|---|---|
| Deletion | Configurable | Always cascade |
| Permissions | Independent | Inherited |
| Required | Optional | Always required |
| Use case | Loose reference | Ownership |
Example:
# Order (Master)
name: order
# Order Line Item (Detail)
name: order_line_item
fields:
order_id:
type: master_detail
reference: ordertree
Hierarchical reference for parent-child trees.
parent:
type: tree
label: Parent
reference: categoryStorage: Stores the id of the referenced parent record.
Use cases:
- Category / folder hierarchies
- Org charts
- Any self-referential tree
A single polymorphic field type (one field that can point at several different
object types, e.g. Account or Contact) is not currently a built-in field
type. The FieldType enum exposes lookup, master_detail, and tree; the
reference property accepts a single target object name, not a list. Model
"related to any object" use cases (activity feeds, attachments, comments) with
separate lookups or an application-level type discriminator.
3. Computed Types
formula
Calculated field based on other fields.
total_price:
type: formula
label: Total Price
expression: "record.quantity * record.unit_price"Formula expressions use CEL (Common Expression Language). Fields on the
current record are referenced via the record. scope. CEL syntax differs from
spreadsheet/Salesforce formula languages:
- Arithmetic:
+,-,*,/,% - Comparison:
==,!=,>,<,>=,<= - Logical:
&&,||,! - Conditional: ternary
cond ? a : b(there is noIF()function) - Functions:
today(),daysFromNow(),daysBetween(),len(),size(),upper(),lower(),trim(),contains(),startsWith(),coalesce(),min(),max(),abs(),round(), and more (see the formula reference).
Examples:
# Simple calculation
discount_amount:
type: formula
expression: "record.price * (record.discount_rate / 100)"
# Conditional logic (CEL ternary)
status_label:
type: formula
expression: "record.is_active ? 'Active' : 'Inactive'"
# Cross-object formula (via expanded lookup)
account_revenue_tier:
type: formula
expression: "record.account.annual_revenue > 1000000 ? 'Enterprise' : 'SMB'"
# Date calculation
days_until_due:
type: formula
expression: "daysBetween(today(), record.due_date)"summary (Rollup)
Aggregate child records in master-detail relationship.
# On Account object
total_opportunities:
type: summary
label: Total Opportunities
summaryOperations:
object: opportunity
function: count
total_opportunity_value:
type: summary
label: Pipeline Value
summaryOperations:
object: opportunity
field: amount
function: sumSummary Types:
count: Count child recordssum: Sum a numeric fieldmin: Minimum valuemax: Maximum valueavg: Average value
Requirements:
- Aggregates a child object that references this object (via its
lookup/master_detailfield) - Set
relationshipFieldonly when the child has more than one reference back to this object
Optional filter: a where-style FilterCondition restricting which child
rows are aggregated, ANDed with the parent-FK match. This is what lets several
summaries roll the same child object into different totals:
total_signups:
type: summary
summaryOperations:
object: engagement
function: count
filter: { type: signup }Implementation: the summary is a real numeric column on the parent,
recomputed by the ObjectQL engine when a child row is inserted, updated, or
deleted. There is no materialized view, no MongoDB aggregation pipeline, and no
batch/hybrid recalculation mode — a child moving in or out of the filter
recomputes the parent on its next write like any other child update.
autonumber
Auto-incrementing unique identifier.
case_number:
type: autonumber
label: Case Number
autonumberFormat: "CASE-{0000}"Example values: CASE-0001, CASE-0002, ...
Format tokens:
{0000}: Zero-padded counter{YYYY}/{MM}/{DD}/{YYYYMMDD}: date tokens (business timezone){field_name}: interpolates another field's value
The counter resets per rendered prefix — AD{YYYYMMDD}{0000} therefore
restarts at 1 each day.
Complex formats:
invoice_number:
type: autonumber
autonumberFormat: "INV-{YYYY}-{0000}"
# Generates: INV-2024-0001, INV-2024-0002, ...Database implementation: a VARCHAR(255) column, numbered from an atomic
counter row in the driver's own _objectstack_sequences table (bootstrapped from
the existing MAX on first use, and scoped per tenant when the object is
tenant-scoped). The driver does not create a native PostgreSQL SEQUENCE.
4. Complex Types
json
Unstructured JSON data.
metadata:
type: json
label: MetadataStorage:
- SQL driver: a
JSONcolumn (jsonon PostgreSQL/MySQL,TEXTon SQLite) — the driver usesjson, notjsonb - MongoDB: Native object
Query support:
// Query JSON properties
const products = await engine.find('product', {
where: { 'metadata.color': 'red' }
});Use cases:
- Product attributes (varying by category)
- Integration payloads
- User preferences
- Dynamic configurations
tags
Simple list of free-form string tags.
tags:
type: tags
label: TagsThere is no generic array field type. To store multiple values, use tags
(free-form strings), multiselect (multiple choices from options), or set
multiple: true on a scalar/lookup field to store an array of that type.
Storage:
- SQL driver: a
JSONcolumn holding the serialized string array - MongoDB:
[String]
address
Structured address with geocoding.
billing_address:
type: address
label: Billing AddressStructure:
{
"street": "123 Main St",
"city": "San Francisco",
"state": "CA",
"postalCode": "94105",
"country": "USA",
"countryCode": "US",
"formatted": "123 Main St, San Francisco, CA 94105"
}Database mapping:
- SQL driver: a
JSONcolumn (not a composite type) - MongoDB: Embedded document
location
Geographic coordinates.
office_location:
type: location
label: Office LocationStorage:
{
"lat": 37.7749,
"lng": -122.4194
}altitude and accuracy (both in metres) are optional additional members. Note
the keys are lat/lng — the { latitude, longitude } spelling was never
consumed by the runtime and has been retired from the value contract.
Proximity / radius ("near") search is not a built-in filter operator.
ObjectQL's filter language exposes only $eq, $ne, $gt, $gte, $lt,
$lte, $in, $nin, $between, $contains, $notContains, $startsWith,
$endsWith, $null, and $exists. A location field stores coordinates;
geospatial querying is not part of ObjectQL's portable filter language — it would
rely on the underlying database's native geospatial support (e.g. MongoDB
geospatial indexes), which the SQL and in-memory drivers do not provide.
Database mapping:
- SQL driver: a
JSONcolumn — the driver does not emit a nativePOINT/GEOGRAPHYcolumn, which is the other half of why proximity search is not available - MongoDB: an embedded document
file
File attachment reference.
avatar:
type: file
label: Profile Picture
multiple: false # set true to store an array of file referencesStored value: an opaque sys_file id string. The expanded read form is the
media metadata object, whose only required member is url:
{
"url": "https://cdn.example.com/files/abc123.jpg",
"name": "profile.jpg",
"size": 1024000,
"mimeType": "image/jpeg"
}alt and duration are the other optional members. The keys are name and
mimeType — not filename / content_type.
Deployments predating the file-as-reference migration may still hold the inline
metadata object (or a bare URL) as the stored value. The engine warns rather
than rejects until os migrate files-to-references --apply has run, so an
existing database keeps working while it is backfilled.
Storage backends (configured on the file-storage connector, not the field):
local: Server filesystems3: Amazon S3azure_blob: Azure Blob Storagegcs: Google Cloud Storage
…plus dropbox, box, onedrive, google_drive, sharepoint, ftp, and
custom. Upload-time processing — thumbnail generation, virus scanning — is
configured there too, under contentProcessing.
image
Image file with transformations.
product_image:
type: image
label: Product ImageFeatures:
Per-field image-processing options (thumbnail generation, dimension validation,
EXIF handling) are not Field-schema properties. Those capabilities are
configured on the file-storage connector's contentProcessing
(generateThumbnails, thumbnailSizes, …). The image field type itself stores
the uploaded file reference.
Type Conversion Matrix
The column each type gets from the SQL driver, per dialect:
| ObjectQL Type | PostgreSQL | MySQL | SQLite |
|---|---|---|---|
text | TEXT | TEXT | TEXT |
email / url / phone | VARCHAR(255) | VARCHAR(255) | VARCHAR(255) |
number / currency / percent | REAL | FLOAT | REAL |
date | DATE | DATE | TEXT (YYYY-MM-DD) |
datetime | TIMESTAMPTZ | DATETIME(3) | TEXT (canonical …Z) |
time | TIME | TIME(3) | TEXT (HH:MM:SS[.fff]) |
boolean / toggle | BOOLEAN | BOOLEAN | INTEGER 0/1 |
select / radio | VARCHAR(255) | VARCHAR(255) | VARCHAR(255) |
multiselect / tags | JSON | JSON | TEXT (JSON) |
lookup / master_detail / tree | VARCHAR(255) | VARCHAR(255) | VARCHAR(255) |
summary | REAL | FLOAT | REAL |
autonumber | VARCHAR(255) | VARCHAR(255) | VARCHAR(255) |
formula | (no column — virtual) | (no column) | (no column) |
json / location / address | JSON | JSON | TEXT (JSON) |
Any field flagged multiple: true becomes a JSON column regardless of its
type. Relationship columns are plain id strings with no database FOREIGN KEY
constraint (see lookup above). The MongoDB driver is schemaless — it issues no
DDL and stores the value it is given.
There is no Redis persistence backend. ObjectQL's data drivers are SQL (PostgreSQL / MySQL / SQLite, plus a SQLite-WASM build of the same driver for the browser), MongoDB, and in-memory. Redis appears in the platform only as an optional cluster-primitives driver (pub/sub, locks, KV, counters), never as a place records are stored.
Sensitive Data: Masking & Encryption
There is no user-defined "custom type" registry. For sensitive values, use the
dedicated secret field type, which encrypts on write and masks on read, and
restrict reader access with field-level security
(readable: false) or hidden.
# Reversible secret (API keys, DB passwords)
api_key:
type: secret
label: API KeyThe per-field
maskingRuleandencryptionConfigproperties were pruned from the Field schema in 2026-06 — they were declared surface with no runtime consumer. In 2026-07 the masking shapes were removed from the spec entirely (ADR-0056 D8);EncryptionConfigSchemaremains in the System namespace as[EXPERIMENTAL]roadmap surface. See Security & Access Control for status.
Type Selection Guide
For text data:
- Short, single-line →
text - Multi-line →
textarea - Rich formatting →
html - Email address →
email - URL →
url - Phone →
phone
For numbers:
- General purpose →
number - Money →
currency - Percentage →
percent - Unique ID →
autonumber
For dates:
- Date only →
date - Date + time →
datetime - Time only →
time
For selections:
- Fixed options →
select - Multiple selections →
multiselect - 2-5 visible options →
radio - Yes/No →
booleanortoggle
For relationships:
- Loose reference →
lookup - Parent-child →
master_detail - Self-referential hierarchy →
tree
For calculations:
- Derived value →
formula - Aggregate children →
summary
For complex data:
- Flexible schema →
json - List of tags →
tags(ormultiple: true) - Geographic data →
locationoraddress - File upload →
fileorimage