| title | Type System |
|---|---|
| description | Complete reference for ObjectQL field types - Scalars, relationships, computed fields, and advanced types |
import { Type, Link, Calculator, Database } from 'lucide-react';
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.
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)Single-line text field.
company_name:
type: text
label: Company Name
max_length: 255
required: true
index: trueDatabase mapping:
- PostgreSQL:
VARCHAR(max_length)orTEXT - MongoDB:
String - Redis:
String
UI rendering:
<input type="text" maxlength="255" required />Use cases:
- Names, titles, identifiers
- Email addresses, phone numbers
- Short descriptions
Multi-line plain text.
description:
type: textarea
label: Description
max_length: 5000
rows: 10Database mapping:
- PostgreSQL:
TEXT - MongoDB:
String
UI rendering:
<textarea rows="10" maxlength="5000"></textarea>Use cases:
- Comments, notes
- Descriptions
- Plain text content
Rich text with HTML markup.
bio:
type: html
label: Biography
sanitize: true # Remove dangerous tags
allowed_tags: [p, br, strong, em, ul, li]Database mapping:
- PostgreSQL:
TEXT - MongoDB:
String
Sanitization:
- Removes
<script>,<iframe>,onclickhandlers - Prevents XSS attacks
- Configurable allowed tags
UI rendering:
<!-- Rich text editor (TinyMCE, Quill, etc.) -->
<div class="rich-text-editor"></div>Use cases:
- Blog posts, articles
- Product descriptions
- Email templates
Email address with validation.
email:
type: email
label: Email Address
required: true
unique: true
index: trueValidation:
- RFC 5322 compliant regex
- Lowercase normalization
- Domain validation (optional DNS check)
Database mapping:
- PostgreSQL:
VARCHAR(255) - MongoDB:
String
Use cases:
- User emails
- Contact information
- Notification addresses
URL with protocol validation.
website:
type: url
label: Website
protocols: [http, https]Validation:
- Must include protocol (
http://,https://) - Valid domain format
- Optional reachability check
Use cases:
- Company websites
- Social media links
- API endpoints
Phone number with international format.
phone:
type: phone
label: Phone Number
format: international # E.164 format
region: USStorage format: E.164 (+12025551234)
Display format: (202) 555-1234
Validation:
- Valid phone number format
- Country code verification
- Optional SMS capability check
General-purpose numeric field.
quantity:
type: number
label: Quantity
min_value: 0
max_value: 9999
scale: 0 # Integer
default_value: 1Configuration:
scale: Decimal places (0 = integer)precision: Total digitsmin_value/max_value: Range validation
Database mapping:
- PostgreSQL:
NUMERIC(precision, scale) - MongoDB:
NumberorDecimal128
Use cases:
- Quantities, counts
- Ratings, scores
- General measurements
Money amount with currency code.
annual_revenue:
type: currency
label: Annual Revenue
scale: 2
precision: 18
default_currency: USDStorage:
{
"amount": 1234.56,
"currency": "USD"
}Features:
- Multi-currency support
- Automatic formatting ($1,234.56)
- Exchange rate conversion (optional)
- ISO 4217 currency codes
Database mapping:
- PostgreSQL:
NUMERIC(18,2)+VARCHAR(3)orJSONB - MongoDB:
{ amount: Decimal128, currency: String }
Percentage value (0-100).
discount_rate:
type: percent
label: Discount
scale: 2
min_value: 0
max_value: 100Display: 25.5% (automatically adds % symbol)
Storage: As decimal (0.255 for 25.5%)
Use cases:
- Discounts, margins
- Completion rates
- Tax rates
Calendar date (no time component).
birth_date:
type: date
label: Date of Birth
min_value: "1900-01-01"
max_value: "{{TODAY()}}"Storage format: ISO 8601 (2024-01-15)
Database mapping:
- PostgreSQL:
DATE - MongoDB:
Date(time set to 00:00:00 UTC)
Use cases:
- Birthdays, anniversaries
- Contract dates
- Deadlines
Date and time with timezone.
meeting_time:
type: datetime
label: Meeting Time
timezone: user # Store in user's timezoneStorage format: ISO 8601 with timezone (2024-01-15T14:30:00Z)
Timezone handling:
utc: Store as UTCuser: Store in user's timezonefixed: Store in specific timezone
Database mapping:
- PostgreSQL:
TIMESTAMP WITH TIME ZONE - MongoDB:
Date
Time of day (no date component).
business_hours_start:
type: time
label: Business Hours Start
default_value: "09:00:00"Storage format: HH:MM:SS
Use cases:
- Business hours
- Recurring event times
- Time-based triggers
True/false value.
is_active:
type: boolean
label: Active
default_value: trueStorage:
- PostgreSQL:
BOOLEAN - MongoDB:
Boolean - Redis:
1or0
UI rendering:
<input type="checkbox" />
<!-- or -->
<select>
<option value="true">Yes</option>
<option value="false">No</option>
</select>Boolean displayed as checkbox.
email_opt_in:
type: checkbox
label: Subscribe to Newsletter
default_value: falseDifference from boolean:
- Always renders as checkbox (not dropdown)
- Typically used for consent, preferences
Dropdown/picklist from predefined options.
priority:
type: select
label: Priority
options:
- value: low
label: Low
color: green
icon: arrow-down
- value: medium
label: Medium
color: yellow
icon: minus
- value: high
label: High
color: orange
icon: arrow-up
- value: critical
label: Critical
color: red
icon: alert
default_value: medium
required: trueStorage: Stores value (not label)
Database mapping:
- PostgreSQL:
VARCHARorENUM - MongoDB:
String
Dynamic options:
country:
type: select
options_source: api
options_endpoint: /api/countriesUse cases:
- Status, stage, priority
- Categories, types
- Fixed value lists
Multiple selection from options.
tags:
type: multi_select
label: Tags
options:
- { value: customer, label: Customer }
- { value: partner, label: Partner }
- { value: vendor, label: Vendor }
multiple: trueStorage:
- PostgreSQL:
TEXT[](array) orJSONB - MongoDB:
[String]
Example value: ['customer', 'partner']
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 }
orientation: horizontal # or verticalDifference from select:
- All options visible (no dropdown)
- Better UX for 2-5 options
Foreign key reference to another object.
account_id:
type: lookup
label: Account
reference_to: account
required: true
reference_filters:
- ['is_active', '=', true]
on_delete: set_null # or restrict, cascadeStorage: Stores _id of referenced record
Query behavior:
// Automatic JOIN
const opportunities = await ObjectQL.query({
object: 'opportunity',
fields: ['name', 'account.company_name'] // Joins 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
Multiple lookups:
contacts:
type: lookup
reference_to: contact
multiple: true # Many-to-manyDatabase mapping:
- PostgreSQL:
UUID+ Foreign Key constraint - MongoDB:
ObjectIdorString
Strong parent-child relationship.
project_id:
type: master_detail
label: Project
reference_to: project
cascade_delete: trueCharacteristics:
- 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_to: orderReference to multiple object types.
parent:
type: polymorphic
label: Related To
reference_to: [account, contact, opportunity]Storage:
{
"_id": "123",
"_type": "account"
}Query:
const activity = await ObjectQL.findOne('activity', id, {
expand: ['parent'] // Resolves based on _type
});
// activity.parent = { _id: '123', _type: 'account', company_name: 'Acme' }Database mapping:
- PostgreSQL: Two columns
parent_id+parent_type - MongoDB:
{ _id: String, _type: String }
Use cases:
- Activity feeds (related to Account OR Contact)
- Attachments (attach to any object)
- Comments (comment on any entity)
Calculated field based on other fields.
total_price:
type: formula
label: Total Price
formula: "quantity * unit_price"
data_type: currency
virtual: true # Not stored, calculated on readFormula syntax:
- Arithmetic:
+,-,*,/,^ - Comparison:
=,!=,>,<,>=,<= - Logical:
AND,OR,NOT - Functions:
IF(),SUM(),MIN(),MAX(),LEN(),UPPER(), etc.
Examples:
# Simple calculation
discount_amount:
type: formula
formula: "price * (discount_rate / 100)"
data_type: currency
# Conditional logic
status_label:
type: formula
formula: "IF(is_active, 'Active', 'Inactive')"
data_type: text
# Cross-object formula (via lookup)
account_revenue_tier:
type: formula
formula: "IF(account.annual_revenue > 1000000, 'Enterprise', 'SMB')"
data_type: text
# Date calculation
days_until_due:
type: formula
formula: "due_date - TODAY()"
data_type: numberStorage options:
virtual: true: Calculate on every read (default)virtual: false: Store value, recalculate on write
Performance:
- Virtual formulas: No storage cost, query-time calculation
- Stored formulas: Faster reads, slower writes
Aggregate child records in master-detail relationship.
# On Account object
total_opportunities:
type: summary
label: Total Opportunities
summary_object: opportunity
summary_type: count
total_opportunity_value:
type: summary
label: Pipeline Value
summary_object: opportunity
summary_field: amount
summary_type: sum
summary_filters:
- ['stage', 'in', ['Prospecting', 'Qualification', 'Proposal']]Summary Types:
count: Count child recordssum: Sum a numeric fieldmin: Minimum valuemax: Maximum valueavg: Average value
Requirements:
- Must have master-detail relationship
- Only available on master side
- Filters apply to child records
Database implementation:
- Materialized view (PostgreSQL)
- Aggregation pipeline (MongoDB)
- Background job recalculation
Performance:
- Real-time: Recalculate on child write (slow)
- Batch: Update every N minutes (stale data)
- Hybrid: Increment/decrement on simple changes
Auto-incrementing unique identifier.
case_number:
type: auto_number
label: Case Number
format: "CASE-{0000}"
start: 1Example values: CASE-0001, CASE-0002, ...
Format tokens:
{0000}: Zero-padded number{YYYY}: Year{MM}: Month{DD}: Day
Complex formats:
invoice_number:
type: auto_number
format: "INV-{YYYY}-{0000}"
# Generates: INV-2024-0001, INV-2024-0002, ...Database implementation:
- PostgreSQL:
SEQUENCE - MongoDB: Atomic counter collection
Unstructured JSON data.
metadata:
type: json
label: Metadata
schema: # Optional JSON Schema validation
type: object
properties:
color:
type: string
size:
type: string
weight:
type: numberStorage:
- PostgreSQL:
JSONB - MongoDB: Native object
Query support:
// Query JSON properties
const products = await ObjectQL.query({
object: 'product',
filters: [
['metadata.color', '=', 'red']
]
});Use cases:
- Product attributes (varying by category)
- Integration payloads
- User preferences
- Dynamic configurations
Ordered list of values.
tags:
type: array
label: Tags
item_type: text
max_items: 10Storage:
- PostgreSQL:
TEXT[]orJSONB - MongoDB:
[String]
Query:
// Contains check
filters: [['tags', 'contains', 'important']]
// Array length
filters: [['tags.length', '>', 3]]Structured address with geocoding.
billing_address:
type: address
label: Billing Address
geocode: true # Auto-geocode on saveStructure:
{
"street": "123 Main St",
"city": "San Francisco",
"state": "CA",
"postal_code": "94105",
"country": "USA",
"latitude": 37.7749,
"longitude": -122.4194
}Features:
- Auto-complete from Google Maps API
- Geocoding (address → lat/lng)
- Reverse geocoding (lat/lng → address)
- Distance calculations
Database mapping:
- PostgreSQL:
JSONBor composite type - MongoDB: Embedded document
Geographic coordinates.
office_location:
type: location
label: Office LocationStorage:
{
"latitude": 37.7749,
"longitude": -122.4194
}Query:
// Find nearby locations (within 10 miles)
const nearby = await ObjectQL.query({
object: 'store',
filters: [
['location', 'near', { lat: 37.7749, lng: -122.4194, distance: 10, unit: 'miles' }]
]
});Database mapping:
- PostgreSQL:
POINTorGEOGRAPHY - MongoDB: GeoJSON
File attachment reference.
avatar:
type: file
label: Profile Picture
accept: [image/png, image/jpeg]
max_size: 5242880 # 5MB
storage: s3Storage:
{
"filename": "profile.jpg",
"content_type": "image/jpeg",
"size": 1024000,
"url": "https://cdn.example.com/files/abc123.jpg"
}Storage backends:
local: Server filesystems3: Amazon S3azure: Azure Blob Storagegcs: Google Cloud Storage
Features:
- Automatic upload handling
- Content-type validation
- Virus scanning (optional)
- CDN integration
Image file with transformations.
product_image:
type: image
label: Product Image
transformations:
thumbnail: { width: 150, height: 150, crop: center }
large: { width: 1200, height: 800, crop: fit }Features:
- Automatic image resizing
- Format conversion (JPEG, PNG, WebP)
- Lazy loading support
- Responsive srcset generation
How types convert between databases:
| ObjectQL Type | PostgreSQL | MongoDB | Redis |
|---|---|---|---|
text |
VARCHAR / TEXT |
String |
String |
number |
NUMERIC |
Number |
String |
currency |
JSONB |
Object |
String (JSON) |
date |
DATE |
Date |
String (ISO) |
datetime |
TIMESTAMP |
Date |
String (ISO) |
boolean |
BOOLEAN |
Boolean |
0 / 1 |
select |
VARCHAR / ENUM |
String |
String |
lookup |
UUID + FK |
ObjectId |
String |
formula |
GENERATED |
Virtual | Computed |
json |
JSONB |
Object |
String (JSON) |
array |
TEXT[] |
Array |
String (JSON) |
location |
POINT |
GeoJSON |
String (JSON) |
Define reusable custom types:
// objectstack.config.ts
export default {
types: {
social_security: {
base: 'text',
pattern: /^\d{3}-\d{2}-\d{4}$/,
format: (value) => value.replace(/(\d{3})(\d{2})(\d{4})/, '$1-$2-$3'),
mask: '***-**-####', // Show only last 4 digits
},
credit_card: {
base: 'text',
pattern: /^\d{13,19}$/,
encrypt: true, // PCI compliance
tokenize: true, // Store token, not actual number
}
}
};Usage:
ssn:
type: social_security
label: Social Security Number
card_number:
type: credit_card
label: Credit CardFor 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 →
auto_number
For dates:
- Date only →
date - Date + time →
datetime - Time only →
time
For selections:
- Fixed options →
select - Multiple selections →
multi_select - 2-5 visible options →
radio - Yes/No →
booleanorcheckbox
For relationships:
- Loose reference →
lookup - Parent-child →
master_detail - Multiple types →
polymorphic
For calculations:
- Derived value →
formula - Aggregate children →
summary
For complex data:
- Flexible schema →
json - List of items →
array - Geographic data →
locationoraddress - File upload →
fileorimage