Field Type Selection Guide
Complete reference for choosing the right Field type from @objectstack/spec/data for your HotCRM objects
Field Type Selection Guide
Every field in a HotCRM object is defined using Field.* builders from @objectstack/spec/data. This guide covers all available field types, when to use each, and provides real examples from the codebase.
Quick Reference
import { ObjectSchema, Field } from '@objectstack/spec/data';Decision Tree
Use this flowchart to select the right field type:
-
Is it a relationship to another object?
- Optional association →
Field.lookup() - Required parent-child (cascade delete) →
Field.masterDetail() - Aggregate child data on parent →
Field.summary()
- Optional association →
-
Is it a choice from predefined options?
- Single choice →
Field.select() - Multiple choices →
Field.select({ multiple: true })
- Single choice →
-
Is it a computed/auto-generated value?
- Formula from other fields →
Field.formula() - Auto-incrementing number →
Field.autonumber()
- Formula from other fields →
-
What kind of data?
- Short text →
Field.text() - Long text →
Field.textarea() - Whole/decimal number →
Field.number() - Yes/No →
Field.boolean() - Date only →
Field.date() - Date and time →
Field.datetime() - Money →
Field.currency() - Percentage →
Field.percent() - Email address →
Field.email() - Phone number →
Field.phone() - Web URL →
Field.url()
- Short text →
-
Is it a file or media?
- Document/attachment →
Field.file() - Photo/avatar →
Field.image()
- Document/attachment →
-
Is it geographic data?
- GPS coordinates →
Field.location() - Postal address →
Field.address()
- GPS coordinates →
Relationship Types
Field.lookup() — Optional Association
Creates an optional reference to another object. Records exist independently.
// packages/finance/src/invoice.object.ts
account: Field.lookup('account', { label: 'Account', required: true }),
contract: Field.lookup('contract', { label: 'Contract' }),When to use: The child record can exist without the parent, or the relationship is informational (e.g., Invoice → Account, Contact → Account).
Note: Even with required: true, a lookup does not cascade delete. Use masterDetail for cascade behavior.
Field.masterDetail() — Required Parent-Child
Creates a required parent-child relationship with cascade delete. The child cannot exist without the parent.
// packages/finance/src/invoice_line.object.ts
invoice: Field.masterDetail('invoice', { label: 'Invoice', required: true }),
// packages/support/src/case_comment.object.ts
case_id: Field.masterDetail('case', { label: 'Case', required: true }),
// packages/products/src/quote_line_item.object.ts
quote_id: Field.masterDetail('quote', { label: 'Quote', required: true }),
// packages/products/src/product_bundle_component.object.ts
bundle: Field.masterDetail('product_bundle', { label: 'Bundle', required: true }),When to use: The child record has no meaning without the parent (e.g., Invoice Line → Invoice, Case Comment → Case). Deleting the parent deletes all children.
Enables: Field.summary() rollup fields on the parent object.
Field.summary() — Rollup Aggregation
Aggregates data from child records (requires a masterDetail relationship). Defined on the parent object.
// packages/crm/src/account.object.ts
total_opportunities: Field.summary({
label: 'Total Opportunities',
summarizedObject: 'opportunity',
summarizeFunction: 'count'
}),
total_open_opportunities_amount: Field.summary({
label: 'Total Open Opportunities Amount',
summarizedObject: 'opportunity',
summarizeFunction: 'sum',
summarizedField: 'amount'
}),
// packages/finance/src/invoice.object.ts
line_item_count: Field.summary({
label: 'Line Item Count',
summarizedObject: 'invoice_line',
summarizeFunction: 'count'
}),
calculated_subtotal: Field.summary({
label: 'Calculated Subtotal',
summarizedObject: 'invoice_line',
summarizeFunction: 'sum',
summarizedField: 'amount'
}),
// packages/products/src/product_bundle.object.ts
component_count: Field.summary({
label: 'Component Count',
summarizedObject: 'product_bundle_component',
summarizeFunction: 'count'
}),Summary functions: count, sum, min, max
When to use: You need real-time aggregated data from child records (e.g., total invoice amount, number of line items).
Data Types
Field.text() — Short Text
Single-line text input with optional length constraints.
name: Field.text({
label: 'Account Name',
required: true,
unique: true,
maxLength: 255
}),
account_number: Field.text({
label: 'Account Number',
unique: true,
maxLength: 40
}),Options: required, unique, maxLength, defaultValue, helpText
Field.textarea() — Long Text
Multi-line text for descriptions, notes, and comments.
description: Field.textarea({
label: 'Description',
maxLength: 32000
}),Field.number() — Numeric
Integer or decimal numbers.
quantity: Field.number({
label: 'Quantity',
required: true,
scale: 2 // Decimal places
}),
number_of_employees: Field.number({
label: 'Number of Employees'
}),Options: min, max, scale (decimal places), required
Field.boolean() — Yes/No
True/false toggle.
is_active: Field.boolean({
label: 'Active',
defaultValue: true
}),
is_decision_maker: Field.boolean({
label: 'Decision Maker'
}),Field.select() — Single Choice
Dropdown selection from predefined options.
type: Field.select({
label: 'Account Type',
options: [
{ label: 'Prospect', value: 'prospect' },
{ label: 'Customer', value: 'customer' },
{ label: 'Partner', value: 'partner' },
{ label: 'Competitor', value: 'competitor' }
]
}),
status: Field.select({
label: 'Status',
defaultValue: 'draft',
options: [
{ label: 'Draft', value: 'draft' },
{ label: 'Posted', value: 'posted' },
{ label: 'Paid', value: 'paid' },
{ label: 'Void', value: 'void' }
]
}),Field.select({ multiple: true }) — Multi-Select
Multiple selections from predefined options.
// packages/support/src/knowledge_article.object.ts
related_case_types: Field.select({
label: 'Related Case Types',
multiple: true,
options: []
}),
// packages/support/src/sla_policy.object.ts
applicable_priorities: Field.select({
label: 'Applicable Priorities',
multiple: true,
options: []
}),When to use: Users need to select more than one option (e.g., applicable categories, supported regions).
Field.date() — Date Only
Date without time component.
close_date: Field.date({
label: 'Close Date',
required: true
}),
start_date: Field.date({ label: 'Start Date' }),Field.datetime() — Date and Time
Full timestamp with date and time.
created_date: Field.datetime({
label: 'Created Date'
}),
last_activity_date: Field.datetime({
label: 'Last Activity Date'
}),Field.currency() — Money
Monetary values with currency formatting.
annual_revenue: Field.currency({
label: 'Annual Revenue'
}),
unit_price: Field.currency({
label: 'Unit Price'
}),
amount: Field.currency({
label: 'Amount'
}),Field.percent() — Percentage
Percentage values (0–100).
probability: Field.percent({
label: 'Probability'
}),
discount_percent: Field.percent({
label: 'Discount Percent'
}),Field.email() — Email Address
Validated email field.
email: Field.email({
label: 'Email',
required: true
}),Field.phone() — Phone Number
Phone number with formatting.
phone: Field.phone({
label: 'Phone'
}),
mobile_phone: Field.phone({
label: 'Mobile Phone'
}),Field.url() — Web URL
Validated URL field rendered as a link.
website: Field.url({
label: 'Website'
}),Field.formula() — Computed Value
Read-only field computed from other fields.
full_name: Field.formula({
label: 'Full Name',
formula: 'CONCAT(first_name, " ", last_name)',
returnType: 'text'
}),Return types: text, number, currency, percent, date, datetime, boolean
Field.autonumber() — Auto-Incrementing
Automatically generated sequential number.
// packages/finance/src/invoice.object.ts
invoice_number: Field.autonumber({
label: 'Invoice Number',
format: 'INV-{YYYY}-{000000}'
}),Format tokens: {YYYY} (year), {MM} (month), {0000} (zero-padded sequence)
Advanced Types
Field.file() — File Attachment
Document or file upload.
// packages/support/src/case.object.ts
attachments: Field.file({ label: 'Attachments' }),
// packages/support/src/knowledge_article.object.ts
attachment: Field.file({ label: 'Attachment' }),Field.image() — Image Upload
Photo or image upload with preview.
// packages/hr/src/employee.object.ts
photo: Field.image({ label: 'Photo' }),
// packages/products/src/product.object.ts
product_image: Field.image({ label: 'Product Image' }),Field.location() — GPS Coordinates
Geographic point with latitude and longitude.
// packages/crm/src/account.object.ts
headquarters_location: Field.location({ label: 'Headquarters Location' }),Field.address() — Postal Address
Structured mailing address with street, city, state, postal code, country.
// packages/crm/src/account.object.ts
billing_address: Field.address({ label: 'Billing Address' }),
shipping_address: Field.address({ label: 'Shipping Address' }),
// packages/hr/src/employee.object.ts
home_address: Field.address({ label: 'Home Address' }),Common Field Options
These options are available on most field types:
| Option | Type | Description |
|---|---|---|
label | string | Display label |
required | boolean | Whether the field is mandatory |
unique | boolean | Whether values must be unique across records |
defaultValue | varies | Default value for new records |
helpText | string | Tooltip help text |
maxLength | number | Maximum character length (text fields) |
min / max | number | Value range (number fields) |
scale | number | Decimal places (number fields) |
Complete Object Example
Here's a full object using multiple field types:
import { ObjectSchema, Field } from '@objectstack/spec/data';
export const InvoiceLine = ObjectSchema.create({
name: 'invoice_line',
label: 'Invoice Line',
pluralLabel: 'Invoice Lines',
icon: 'list',
description: 'Line items for an invoice',
fields: {
// Relationship: required parent
invoice: Field.masterDetail('invoice', { label: 'Invoice', required: true }),
// Relationship: optional association
product: Field.lookup('product', { label: 'Product' }),
// Data fields
description: Field.text({ label: 'Description' }),
quantity: Field.number({ label: 'Quantity', required: true, scale: 2 }),
unit_price: Field.currency({ label: 'Unit Price' }),
amount: Field.currency({ label: 'Amount' })
}
});