ObjectOS LogoObjectOS

Data Modeling Guide

ObjectOS uses declarative YAML files to define data models. This guide covers how to model your business data using the ObjectQL metadata format.

Overview

Data modeling in ObjectOS is done entirely in YAML files with the .object.yml extension. These files define:

  • Objects: Business entities (like Contact, Account, Order)
  • Fields: Properties of objects (name, email, price, etc.)
  • Relationships: How objects relate to each other
  • Validation: Rules that data must satisfy
  • Permissions: Who can read, create, update, or delete records

Creating Your First Object

Step 1: Create the File

Create a file named contact.object.yml in your objects/ directory:

name: contacts
label: Contact
icon: user
description: Customer and prospect contact information

Step 2: Add Fields

Add fields to describe your contact:

name: contacts
label: Contact
icon: user

fields:
  first_name:
    type: text
    label: First Name
    required: true
    max_length: 100
  
  last_name:
    type: text
    label: Last Name
    required: true
    max_length: 100
  
  email:
    type: email
    label: Email Address
    unique: true
    required: true
  
  phone:
    type: text
    label: Phone Number
  
  birthdate:
    type: date
    label: Birth Date

Step 3: Add Permissions

Define who can access this object:

permission_set:
  allowRead: true                    # Everyone can read
  allowCreate: ['sales', 'admin']    # Sales and admin can create
  allowEdit: ['sales', 'admin']      # Sales and admin can edit
  allowDelete: ['admin']             # Only admin can delete

Object Structure

Object Attributes

AttributeTypeRequiredDescription
namestringYesAPI name (snake_case, no spaces)
labelstringNoDisplay name for UI
iconstringNoIcon name (from icon library)
descriptionstringNoObject description
enable_apibooleanNoEnable REST API (default: true)
enable_auditbooleanNoEnable audit logging (default: false)

Example

name: projects
label: Project
icon: folder
description: Project management and tracking
enable_api: true
enable_audit: true

Field Types

Text Fields

Short Text (text)

first_name:
  type: text
  label: First Name
  required: true
  max_length: 100

Long Text (textarea)

description:
  type: textarea
  label: Description
  max_length: 5000

Email (email)

email:
  type: email
  label: Email Address
  unique: true
  required: true

URL (url)

website:
  type: url
  label: Website

Numeric Fields

Number (number)

age:
  type: number
  label: Age
  min: 0
  max: 150

Currency (currency)

annual_revenue:
  type: currency
  label: Annual Revenue
  currency_code: USD
  precision: 2

Percent (percent)

discount_rate:
  type: percent
  label: Discount Rate
  min: 0
  max: 100
  precision: 2

Date Fields

Date (date)

birth_date:
  type: date
  label: Birth Date

DateTime (datetime)

created_at:
  type: datetime
  label: Created At
  default: now

Selection Fields

Select (select)

Single choice dropdown:

status:
  type: select
  label: Status
  options:
    - value: draft
      label: Draft
    - value: active
      label: Active
    - value: archived
      label: Archived
  default: draft

Multi-Select (multiselect)

Multiple choice:

interests:
  type: multiselect
  label: Interests
  options:
    - value: tech
      label: Technology
    - value: sports
      label: Sports
    - value: music
      label: Music

Boolean (boolean)

is_active:
  type: boolean
  label: Active
  default: true

Relationships

Many-to-One (Lookup)

A Contact belongs to one Account:

# In contact.object.yml
fields:
  account:
    type: lookup
    label: Account
    reference_to: accounts
    on_delete: set_null  # When account deleted, set to null

One-to-Many (Inverse of Lookup)

One Account has many Contacts. This is automatically created by the lookup field above.

Master-Detail

Like lookup, but with cascade delete:

fields:
  account:
    type: master_detail
    label: Account
    reference_to: accounts
    # on_delete: cascade is automatic

When the Account is deleted, all related Contacts are also deleted.

Many-to-Many (Junction Object)

Create a junction object for many-to-many relationships:

# opportunity_contact.object.yml
name: opportunity_contacts
label: Opportunity Contact
fields:
  opportunity:
    type: master_detail
    reference_to: opportunities
  
  contact:
    type: master_detail
    reference_to: contacts
  
  role:
    type: select
    options:
      - Decision Maker
      - Influencer
      - User

Field Validation

Required Fields

email:
  type: email
  required: true

Unique Fields

email:
  type: email
  unique: true

Min/Max Values

age:
  type: number
  min: 18
  max: 100

price:
  type: currency
  min: 0

String Length

name:
  type: text
  min_length: 2
  max_length: 100

Pattern Matching

phone:
  type: text
  pattern: '^\+?[1-9]\d{1,14}$'  # E.164 phone format

Default Values

Set default values for new records:

status:
  type: select
  default: draft

created_at:
  type: datetime
  default: now

is_active:
  type: boolean
  default: true

Field-Level Permissions

Control who can see or edit specific fields:

salary:
  type: currency
  label: Salary
  visible_to: ['hr', 'admin']    # Only HR and admin can see
  editable_by: ['hr']            # Only HR can edit

Calculated Fields

Formula Fields

Define calculated fields using formulas:

full_name:
  type: formula
  label: Full Name
  formula: 'first_name + " " + last_name'
  return_type: text

Rollup Summary Fields

Aggregate data from related records:

# In account.object.yml
total_opportunities:
  type: rollup_summary
  label: Total Opportunities
  reference_to: opportunities
  summary_type: count

total_revenue:
  type: rollup_summary
  label: Total Revenue
  reference_to: opportunities
  summary_field: amount
  summary_type: sum
  filters:
    stage: 'closed_won'

Auto-Number Fields

Generate sequential numbers:

ticket_number:
  type: autonumber
  label: Ticket #
  format: 'TKT-{0000}'
  start_number: 1

Complete Example: CRM Objects

Account Object

# objects/account.object.yml
name: accounts
label: Account
icon: building
description: Company or organization

fields:
  name:
    type: text
    label: Account Name
    required: true
    max_length: 255
  
  industry:
    type: select
    label: Industry
    options:
      - value: technology
        label: Technology
      - value: finance
        label: Financial Services
      - value: healthcare
        label: Healthcare
      - value: retail
        label: Retail
  
  website:
    type: url
    label: Website
  
  annual_revenue:
    type: currency
    label: Annual Revenue
    currency_code: USD
  
  employee_count:
    type: number
    label: Number of Employees
    min: 0
  
  type:
    type: select
    label: Type
    options:
      - Customer
      - Prospect
      - Partner
    default: Prospect

permission_set:
  allowRead: true
  allowCreate: ['sales', 'admin']
  allowEdit: ['sales', 'admin']
  allowDelete: ['admin']

Contact Object

# objects/contact.object.yml
name: contacts
label: Contact
icon: user
description: Person associated with an account

fields:
  first_name:
    type: text
    label: First Name
    required: true
  
  last_name:
    type: text
    label: Last Name
    required: true
  
  email:
    type: email
    label: Email
    unique: true
    required: true
  
  phone:
    type: text
    label: Phone
  
  title:
    type: text
    label: Job Title
  
  account:
    type: lookup
    label: Account
    reference_to: accounts
    on_delete: set_null
  
  birthdate:
    type: date
    label: Birth Date
  
  is_primary:
    type: boolean
    label: Primary Contact
    default: false

permission_set:
  allowRead: true
  allowCreate: ['sales', 'admin']
  allowEdit: ['sales', 'admin']
  allowDelete: ['admin']

Opportunity Object

# objects/opportunity.object.yml
name: opportunities
label: Opportunity
icon: currency-dollar
description: Sales opportunity

fields:
  name:
    type: text
    label: Opportunity Name
    required: true
  
  account:
    type: lookup
    label: Account
    reference_to: accounts
    required: true
  
  amount:
    type: currency
    label: Amount
    currency_code: USD
    min: 0
  
  probability:
    type: percent
    label: Probability
    min: 0
    max: 100
  
  expected_revenue:
    type: formula
    label: Expected Revenue
    formula: 'amount * (probability / 100)'
    return_type: currency
  
  stage:
    type: select
    label: Stage
    required: true
    options:
      - value: prospecting
        label: Prospecting
      - value: qualification
        label: Qualification
      - value: proposal
        label: Proposal
      - value: negotiation
        label: Negotiation
      - value: closed_won
        label: Closed Won
      - value: closed_lost
        label: Closed Lost
    default: prospecting
  
  close_date:
    type: date
    label: Expected Close Date
    required: true
  
  owner:
    type: lookup
    label: Owner
    reference_to: users
    required: true

permission_set:
  allowRead: ['sales', 'admin']
  allowCreate: ['sales', 'admin']
  allowEdit: ['sales', 'admin']
  allowDelete: ['admin']

Best Practices

1. Naming Conventions

  • Object names: Use snake_case plural (e.g., contacts, sales_orders)
  • Field names: Use snake_case (e.g., first_name, annual_revenue)
  • Labels: Use Title Case (e.g., "First Name", "Annual Revenue")

2. Required Fields

Make fields required if they are essential for the object to make sense:

# Good: Contact must have a name and email
first_name:
  required: true
email:
  required: true

3. Unique Constraints

Use unique constraints to prevent duplicates:

email:
  unique: true

4. Field Length

Set reasonable maximum lengths:

name:
  max_length: 255  # Standard for most text fields

5. Default Values

Provide sensible defaults:

status:
  default: draft
is_active:
  default: true

Testing Your Models

After creating objects, test them:

# Start the server
pnpm run dev

# Test API endpoints
curl http://localhost:3000/api/metadata/contacts
curl http://localhost:3000/api/data/contacts/query

Next Steps

On this page