Object Types
Object Types define the data model for your vertical application. They are typed schema definitions that describe the structure of your domain entities -- their fields, validation rules, relationships, and available actions. Object Types are defined in TypeScript, seeded to the Configurator service, and used by ResourceAPI at runtime for data validation.
What Object Types Are
An Object Type is similar to a database table definition, but richer. It specifies:
- Properties -- The fields on each resource, with types, validation, and defaults
- Actions -- Custom operations that can be executed on a resource (e.g., "submit", "approve")
- Link Types -- Relationships to other resource types (e.g., an Application has many Documents)
- Storage backend -- Where data is persisted (PostgreSQL or Cosmos DB)
- Status -- Whether the type is published (active) or in draft
Schema Format
Object Types are defined in src/eai.config/object-types.ts and exported as a map of tenant slug to array of type definitions:
import { ObjectTypeDefinition } from './types';
export const objectTypes: Record<string, ObjectTypeDefinition[]> = {
'my-tenant': [
{
name: 'Application', // PascalCase (enforced by Configurator)
displayName: 'Application', // Human-friendly label for UI
description: 'Permit applications submitted by users',
properties: [/* ... */],
linkTypes: [/* ... */],
actions: [/* ... */],
storageBackend: 'postgresql', // postgresql | cosmosdb
status: 'published', // draft | published | deprecated
},
],
};
Field Types
Every property on an Object Type has a type that determines what data it can store. The platform supports 8 field types:
| Type | Description | Example Use |
|---|---|---|
text | String value | Names, emails, addresses, identifiers |
number | Numeric value (integer or float) | Counts, amounts, scores |
boolean | True/false flag | isVerified, isActive, isComplete |
date | ISO 8601 datetime string | submittedAt, createdAt, dueDate |
select | Enum with predefined options (requires options array) | Status, category, priority |
multi-select | Multiple values from predefined options | Tags, categories, features |
relationship | Reference to another resource by ID | parentId, ownerId, assigneeId |
rich-text | Formatted text content (HTML or Markdown) | Descriptions, notes, comments |
Property Definition
Each property is defined with the following fields:
interface PropertyDefinition {
name: string; // camelCase property name (unique within the type)
type: FieldType; // One of the 8 field types above
required?: boolean; // Whether the field must be provided on create
indexed?: boolean; // Add a database index for faster queries
defaultValue?: any; // Default value if not provided
options?: Array<{ // Required for 'select' and 'multi-select' types
label: string;
value: string;
}>;
description?: string; // Help text displayed in UI
}
Property Examples
properties: [
// Text field
{
name: 'applicantName',
type: 'text',
required: true,
description: 'Full legal name of the applicant',
},
// Number field
{
name: 'estimatedCost',
type: 'number',
required: false,
defaultValue: 0,
},
// Boolean field
{
name: 'isVerified',
type: 'boolean',
defaultValue: false,
},
// Date field
{
name: 'submittedAt',
type: 'date',
indexed: true,
},
// Select field (requires options)
{
name: 'status',
type: 'select',
required: true,
defaultValue: 'draft',
options: [
{ label: 'Draft', value: 'draft' },
{ label: 'Submitted', value: 'submitted' },
{ label: 'Under Review', value: 'under-review' },
{ label: 'Approved', value: 'approved' },
{ label: 'Rejected', value: 'rejected' },
],
},
// Relationship field
{
name: 'assignedTo',
type: 'relationship',
description: 'Staff member assigned to review this application',
},
// Rich text field
{
name: 'notes',
type: 'rich-text',
description: 'Internal review notes',
},
],
Actions
Actions are custom operations that can be executed on a resource. They define state transitions with validation rules, required roles, and side effects. Actions are executed via the ResourceAPI action endpoint.
actions: [
{
name: 'submit',
displayName: 'Submit Application',
requiredRole: 'tenant-user',
validationRules: {
requiredFields: ['applicantName', 'applicantEmail'],
requiredStatus: 'draft',
},
sideEffects: [
{ type: 'set_field', field: 'status', value: 'submitted' },
{ type: 'set_timestamp', field: 'submittedAt' },
],
},
{
name: 'approve',
displayName: 'Approve Application',
requiredRole: 'tenant-staff',
validationRules: {
requiredStatus: 'under-review',
},
sideEffects: [
{ type: 'set_field', field: 'status', value: 'approved' },
{ type: 'set_timestamp', field: 'approvedAt' },
{ type: 'set_user', field: 'approvedBy' },
],
},
],
Side Effect Types
| Type | Description | Example |
|---|---|---|
set_field | Set a field to a specific value | { type: 'set_field', field: 'status', value: 'submitted' } |
set_timestamp | Set a field to the current ISO timestamp | { type: 'set_timestamp', field: 'submittedAt' } |
set_user | Set a field to the current user's ID | { type: 'set_user', field: 'reviewedBy' } |
Link Types
Link Types define relationships between Object Types. They enable you to model connections between resources, such as an Application having many Documents.
linkTypes: [
{
name: 'documents',
targetObjectType: 'Document', // PascalCase target type name
cardinality: 'one-to-many', // one-to-one | one-to-many | many-to-one | many-to-many
cascadeDelete: true, // Delete linked resources when parent is deleted
},
{
name: 'reviewer',
targetObjectType: 'StaffMember',
cardinality: 'many-to-one',
cascadeDelete: false,
},
],
Cardinality Options
| Cardinality | Description | Example |
|---|---|---|
one-to-one | Each resource links to exactly one target | Application -> PrimaryContact |
one-to-many | Each resource links to many targets | Application -> Documents |
many-to-one | Many resources link to one target | Applications -> Reviewer |
many-to-many | Many resources link to many targets | Applications -> Tags |
Complete Example
Here is a complete Object Type definition for a permit application system:
export const objectTypes = {
'permits-tenant': [
{
name: 'Application',
displayName: 'Permit Application',
description: 'Building permit applications submitted by property owners',
properties: [
{ name: 'applicantName', type: 'text', required: true },
{ name: 'applicantEmail', type: 'text', required: true },
{ name: 'projectAddress', type: 'text', required: true, indexed: true },
{ name: 'estimatedCost', type: 'number', required: true },
{ name: 'projectDescription', type: 'rich-text' },
{
name: 'status',
type: 'select',
required: true,
defaultValue: 'draft',
options: [
{ label: 'Draft', value: 'draft' },
{ label: 'Submitted', value: 'submitted' },
{ label: 'Under Review', value: 'under-review' },
{ label: 'Approved', value: 'approved' },
{ label: 'Rejected', value: 'rejected' },
],
},
{ name: 'isExpedited', type: 'boolean', defaultValue: false },
{ name: 'submittedAt', type: 'date', indexed: true },
{ name: 'approvedAt', type: 'date' },
{ name: 'approvedBy', type: 'relationship' },
],
linkTypes: [
{
name: 'documents',
targetObjectType: 'Document',
cardinality: 'one-to-many',
cascadeDelete: true,
},
{
name: 'inspections',
targetObjectType: 'Inspection',
cardinality: 'one-to-many',
cascadeDelete: false,
},
],
actions: [
{
name: 'submit',
displayName: 'Submit for Review',
requiredRole: 'tenant-user',
validationRules: {
requiredFields: ['applicantName', 'applicantEmail', 'projectAddress'],
requiredStatus: 'draft',
},
sideEffects: [
{ type: 'set_field', field: 'status', value: 'submitted' },
{ type: 'set_timestamp', field: 'submittedAt' },
],
},
{
name: 'approve',
displayName: 'Approve',
requiredRole: 'tenant-staff',
validationRules: { requiredStatus: 'under-review' },
sideEffects: [
{ type: 'set_field', field: 'status', value: 'approved' },
{ type: 'set_timestamp', field: 'approvedAt' },
{ type: 'set_user', field: 'approvedBy' },
],
},
],
storageBackend: 'postgresql',
status: 'published',
},
],
};
Validation Rules
| Rule | Description |
|---|---|
Object type name must be PascalCase | Configurator auto-generates a kebab-case slug from the name |
Property name must be unique within the type | camelCase is recommended |
options array is required for select and multi-select types | Each option has label and value |
storageBackend defaults to postgresql | Use cosmosdb for document-heavy workloads |
status must be published for the type to be active | draft types are not available via ResourceAPI |
Validating and Seeding
Use the EAI CLI to validate your object type definitions and seed them to Configurator:
# Validate object type definitions locally (checks schema, naming, required fields)
eai types validate
# Seed object types to Configurator (creates or updates types in the platform)
eai types seed
# Seed types for a specific tenant
eai types seed --tenant my-tenant
The seed process pushes your TypeScript definitions to Configurator via the PublicAPI orchestrate endpoint. Once seeded, ResourceAPI uses these schemas to validate all incoming data for that tenant.
Seeding is additive by default. Existing types are updated, not deleted. If you rename a type, the old type will remain in Configurator and must be removed manually.