Field Types
OpenSaaS Stack provides a comprehensive set of field types for building your schema. Each field type includes validation, access control, and UI configuration options.
Core Field Types
Text Field
String field with validation options:
import { text } from '@opensaas/stack-core/fields'
fields: {
title: text({
validation: {
isRequired: true,
length: { min: 3, max: 100 },
},
ui: {
displayMode: 'input', // or 'textarea'
},
}),
description: text({
ui: {
displayMode: 'textarea',
},
}),
}
Options:
validation.isRequired: Booleanvalidation.length.min: Minimum lengthvalidation.length.max: Maximum lengthui.displayMode:'input'or'textarea'
Integer Field
Number field with validation:
import { integer } from '@opensaas/stack-core/fields'
fields: {
age: integer({
validation: {
isRequired: true,
min: 0,
max: 150,
},
}),
score: integer({
validation: {
min: 0,
max: 100,
},
defaultValue: 0,
}),
}
Options:
validation.isRequired: Booleanvalidation.min: Minimum valuevalidation.max: Maximum valuedefaultValue: Default integer value
Checkbox Field
Boolean field:
import { checkbox } from '@opensaas/stack-core/fields'
fields: {
isPublished: checkbox({
defaultValue: false,
}),
emailVerified: checkbox(),
}
Options:
defaultValue: Boolean default value
Timestamp Field
Date/time field with auto-now support:
import { timestamp } from '@opensaas/stack-core/fields'
fields: {
publishedAt: timestamp(),
createdAt: timestamp({
defaultValue: { kind: 'now' },
}),
updatedAt: timestamp({
db: { updatedAt: true }, // Auto-update on changes
}),
}
Options:
defaultValue.kind:'now'for current timestampdb.updatedAt: Boolean - auto-update on record changes
Password Field
String field automatically excluded from reads:
import { password } from '@opensaas/stack-core/fields'
fields: {
password: password({
validation: {
isRequired: true,
length: { min: 8 },
},
}),
}
Options:
validation.isRequired: Booleanvalidation.length.min: Minimum lengthvalidation.length.max: Maximum length
Password fields are automatically excluded from all read operations for security.
Select Field
Enum field with predefined options:
import { select } from '@opensaas/stack-core/fields'
fields: {
status: select({
options: [
{ label: 'Draft', value: 'draft' },
{ label: 'Published', value: 'published' },
{ label: 'Archived', value: 'archived' },
],
defaultValue: 'draft',
validation: {
isRequired: true,
},
ui: {
displayMode: 'select', // or 'radio', 'segmented-control'
},
}),
}
Options:
options: Array of{ label, value }pairsdefaultValue: Default selected valuevalidation.isRequired: Booleanui.displayMode:'select'|'radio'|'segmented-control'
Relationship Field
Foreign key relationship:
import { relationship } from '@opensaas/stack-core/fields'
fields: {
// One-to-many (User has many posts)
posts: relationship({
ref: 'Post.author',
many: true,
}),
// Many-to-one (Post belongs to one user)
author: relationship({
ref: 'User.posts',
}),
// One-to-one
profile: relationship({
ref: 'Profile.user',
}),
}
Options:
ref: String in format'ListName.fieldName'many: Boolean - true for one-to-many relationships
Third-Party Field Types
Rich Text Field
From @opensaas/stack-tiptap:
import { richText } from '@opensaas/stack-tiptap/fields'
fields: {
content: richText({
ui: {
minHeight: 300,
maxHeight: 800,
},
}),
}
See the Tiptap package documentation for more details.
Image Field
From @opensaas/stack-storage:
import { image } from '@opensaas/stack-storage/fields'
fields: {
avatar: image({
storage: 's3',
validation: {
isRequired: true,
},
}),
}
See the Storage package documentation for more details.
Common Field Options
All field types support these common options:
Access Control
text({
access: {
read: ({ session }) => !!session,
create: ({ session }) => !!session,
update: ({ session, item }) => session?.userId === item.authorId,
},
})
Hooks
text({
hooks: {
resolveInput: async ({ resolvedData, fieldKey }) => {
// Transform input data
return resolvedData[fieldKey]?.toLowerCase()
},
resolveOutput: async ({ item, fieldKey }) => {
// Transform output data
return item[fieldKey]?.toUpperCase()
},
},
})
UI Configuration
text({
ui: {
label: 'Custom Label',
description: 'Help text shown below the field',
placeholder: 'Enter text here...',
// Field-type-specific options
},
})
Creating Custom Field Types
You can create custom field types by implementing the BaseFieldConfig interface:
import type { BaseFieldConfig } from '@opensaas/stack-core'
import { z } from 'zod'
export type SlugField = BaseFieldConfig & {
type: 'slug'
from?: string // Field to generate slug from
}
export function slug(options?: Omit<SlugField, 'type'>): SlugField {
return {
type: 'slug',
...options,
getZodSchema: (fieldName, operation) => {
return z
.string()
.regex(/^[a-z0-9-]+$/)
.optional()
},
getPrismaType: (fieldName) => {
return { type: 'String', modifiers: '?' }
},
getTypeScriptType: () => {
return { type: 'string', optional: true }
},
}
}
See the Custom Fields guide for a complete tutorial.
Field Validation
Validation rules are defined in the validation object:
text({
validation: {
isRequired: true,
length: { min: 3, max: 100 },
},
})
integer({
validation: {
isRequired: true,
min: 0,
max: 100,
},
})
Validation errors are thrown during create/update operations and include:
- Field name
- Error type
- Validation rule that failed
Field Methods
Every field config object provides these methods used by the generator:
getZodSchema(fieldName, operation)
Returns a Zod schema for validation:
getZodSchema: (fieldName, operation) => {
let schema = z.string()
if (validation?.length) {
if (validation.length.min) schema = schema.min(validation.length.min)
if (validation.length.max) schema = schema.max(validation.length.max)
}
return validation?.isRequired ? schema : schema.optional()
}
getPrismaType(fieldName)
Returns the Prisma type and modifiers:
getPrismaType: (fieldName) => {
return { type: 'String', modifiers: '?' }
}
getTypeScriptType()
Returns the TypeScript type and optionality:
getTypeScriptType: () => {
return { type: 'string', optional: true }
}
Best Practices
1. Use Appropriate Field Types
// ✅ Good: Use integer for numbers
age: integer({ validation: { min: 0, max: 150 } })
// ❌ Bad: Don't use text for numbers
age: text({ validation: { length: { max: 3 } } })
2. Add Validation Rules
// ✅ Good: Validate email format
email: text({
validation: {
isRequired: true,
length: { max: 255 },
},
})
// ❌ Bad: No validation
email: text()
3. Use Relationships for Foreign Keys
// ✅ Good: Use relationship field
author: relationship({ ref: 'User.posts' })
// ❌ Bad: Don't use text for IDs
authorId: text()
Next Steps
- Hooks System - Transform field data
- Custom Fields Guide - Create custom field types
- API Reference - Complete field API