Fields
Comprehensive guide to field configuration, validation, and customization
Fields
Fields are the building blocks of Collections. They define the schema of your data, control how it's validated, determine how it's stored in the database, and govern how it appears in your APIs.
Fields can be endlessly customized in their appearance and behavior without affecting their underlying data structure. They are designed to be composable, extensible, and type-safe.
Overview
Each field combines:
- A Field Type - Determines what kind of data it accepts (text, number, relation, etc.)
- Configuration - Labels, descriptions, defaults, validation rules
- Behavior - Hooks, computed values, conditional logic
- Presentation - Admin UI options, custom components
There are three main categories of fields in Collections:
- Data Fields - Store data in the database
- Computed Fields - Derive values from other fields
- Relation Fields - Link to other collections
Basic Field Syntax
Fields are defined within a collection's fields property:
import { collection, field } from '@deessejs/collections'
import { text, email } from '@deessejs/collections/fields'
export const users = collection({
slug: 'users',
fields: {
name: field({
type: text({ min: 2, max: 100 }),
label: 'Full Name',
required: true
}),
email: field({
type: email(),
label: 'Email Address',
unique: true,
required: true
})
}
})Field Configuration
Every field is configured with an object containing its type and options:
Prop
Type
Example:
field({
type: text({ min: 2, max: 100 }),
label: 'Name',
description: 'Enter your full name',
default: 'Anonymous',
required: true,
unique: true,
indexed: true
})Field Categories
Field Properties
name (implicit)
The field name is the key used in the fields object. This is the property name that will be used in TypeScript types and database columns:
fields: {
// 'firstName' is the field name
firstName: field({ type: text() })
}
// Usage:
user.firstName // Type-safe accessField names must be unique within a collection.
type
Required. Defines the field type:
type: text({ min: 3, max: 255 })
type: email()
type: enumField(['draft', 'published'])
type: relation('users')See Field Types for all available types.
label
Human-readable label for the field. Used in API responses, error messages, and Admin UI:
label: 'Email Address'With i18n:
label: {
en: 'Email Address',
fr: 'Adresse e-mail',
es: 'Dirección de correo'
}If not provided, the field name will be formatted as the label (e.g., firstName → "First Name").
description
Additional context about the field. Shown in admin panels and API documentation:
description: 'We will never share your email with third parties'With i18n:
description: {
en: 'A short biography',
fr: 'Une courte biographie'
}default
Default value for the field. Applied on create operations if no value is provided:
// Static value
status: field({
type: enumField(['draft', 'published']),
default: 'draft'
})
// Function
createdAt: field({
type: timestamp(),
default: () => new Date()
})required
Make a field required (cannot be null or undefined):
email: field({
type: email(),
required: true
})Required fields are validated both at the API level and in the database schema.
unique
Add a unique constraint to the field in the database:
username: field({
type: text(),
unique: true
})indexed
Add a database index for faster queries:
email: field({
type: email(),
indexed: true
})Use indexed on fields that are frequently used in where clauses or for sorting.
nullable
Allow null values:
middleName: field({
type: text(),
nullable: true
})Most fields are nullable by default. Use required: true to enforce non-null values.
hidden
Hide field from auto-generated forms and API responses:
apiKey: field({
type: text(),
hidden: true
})Hidden fields are still stored in the database but not exposed publicly.
Validation
Built-in Validation
Fields are automatically validated based on their type:
// Text validation
name: field({
type: text({ min: 2, max: 100 })
})
// Email validation
email: field({
type: email()
})
// Number validation
age: field({
type: number({ min: 0, max: 120 })
})Custom Validation
Add custom validation logic with the validate function:
password: field({
type: text({ min: 8 }),
validate: (value) => {
if (!/[A-Z]/.test(value)) {
throw new Error('Password must contain at least one uppercase letter')
}
if (!/[0-9]/.test(value)) {
throw new Error('Password must contain at least one number')
}
if (!/[!@#$%^&*]/.test(value)) {
throw new Error('Password must contain at least one special character')
}
return true
}
})Validation Messages
Customize error messages with i18n support:
email: field({
type: email(),
messages: {
required: {
en: 'Email is required',
fr: 'Email est requis',
es: 'El correo electrónico es obligatorio'
},
invalid: {
en: 'Please enter a valid email address',
fr: 'Veuillez entrer une adresse e-mail valide',
es: 'Por favor, introduzca una dirección de correo electrónico válida'
}
}
})Available message keys:
Prop
Type
Computed Fields
Computed fields derive values from other fields without storing them in the database:
Basic Computed Field
fields: {
price: field({ type: number() }),
taxRate: field({ type: number(), default: 0.1 }),
total: field({
type: number(),
computed: {
from: ['price', 'taxRate'],
compute: ({ price, taxRate }) => {
return price + (price * taxRate)
}
}
})
}Computed Fields with Transformations
fields: {
firstName: field({ type: text() }),
lastName: field({ type: text() }),
// Create URL-safe slug
slug: field({
type: text(),
computed: {
from: ['firstName', 'lastName'],
compute: ({ firstName, lastName }) => {
return `${firstName.toLowerCase()}-${lastName.toLowerCase()}`
}
}
})
}Computed fields are:
- Read-only (cannot be set directly)
- Calculated on every read operation
- Not stored in the database
- Available in API responses
Field Modifiers
Chain modifiers to fields for cleaner syntax:
email: field({
type: email()
})
.required()
.unique()
.indexed()Available modifiers:
.required()- Make field required.unique()- Add unique constraint.indexed()- Add database index.default(value)- Set default value.validate(fn)- Add custom validation
Admin Options
Customize how fields appear and behave in admin panels:
title: field({
type: text(),
admin: {
placeholder: 'Enter your title...',
helpText: 'Keep it under 60 characters for best display',
width: '50%',
style: {
fontWeight: 'bold'
},
className: 'custom-title-field',
readOnly: false,
disabled: false
}
})Available admin options:
Prop
Type
Conditional Logic
Show or hide fields based on other field values:
fields: {
hasDiscount: field({
type: boolean(),
default: false
}),
discountPercentage: field({
type: number({ min: 0, max: 100 }),
admin: {
condition: (data, siblingData) => {
return siblingData.hasDiscount === true
}
}
})
}Condition function receives:
data- The entire document's datasiblingData- Only fields within the same parentuser- Currently authenticated user (if available)
Field-Level Hooks
Execute logic at specific points in a field's lifecycle:
fields: {
slug: field({
type: text(),
hooks: {
beforeCreate: [({ data, value }) => {
// Auto-generate slug from title
return slugify(data.title)
}],
beforeUpdate: [({ data, value }) => {
// Only regenerate if title changed
if (data.titleChanged) {
return slugify(data.title)
}
return value
}]
}
})
}Available field-level hooks:
beforeCreate- Before field is saved on createafterCreate- After field is saved on createbeforeUpdate- Before field is saved on updateafterUpdate- After field is saved on update
Complete Example
Best Practices
- Use descriptive names - Field names should be clear and consistent
- Provide labels - Always include human-readable labels
- Add descriptions - Help users understand what each field does
- Validate input - Use built-in and custom validation
- Use i18n - Make your fields accessible to international users
- Index strategically - Add indexes to frequently queried fields
- Document computed fields - Explain how computed values are derived
- Use conditionals - Hide irrelevant fields to reduce complexity
Next Steps
- Field Types - Explore all available field types
- Collections - Learn about collections and advanced features
- i18n - Internationalization support
- Plugins - Extend functionality with plugins