ObjectStackObjectStack Protocol

ObjectUI Overview

Server-driven UI protocol - Define interfaces as data, not code

ObjectUI: The UI Protocol

ObjectUI is ObjectStack's UI abstraction layer that treats interfaces as data. Instead of hardcoding React/Vue components, you define layouts, forms, and dashboards as JSON/YAML configurations that renderers interpret at runtime.

The Core Problem

Traditional frontend development chains you to specific frameworks and platforms:

  • UI Changes Require Deployments: Change a form layout? Rebuild, test, redeploy, wait for app store approval
  • Platform Fragmentation: Build the same form 3 times (Web React, iOS SwiftUI, Android Jetpack Compose)
  • Business User Lock-out: Analysts can't configure dashboardsβ€”they must file tickets and wait for developer sprints
  • Validation Duplication: Same validation rules written 3 times (Frontend, Backend, Database)
  • Framework Lock-in: Switching from React to Vue requires months of rewrites

Result: 6-week lead time to add a single field to a form. Business agility destroyed by technical constraints.

The ObjectUI Solution

Deploy UI in Seconds

Update forms, dashboards, and reports without rebuilding apps. Change JSON metadata, refresh pageβ€”done.

Write Once, Run Everywhere

One JSON definition renders natively on Web (React/Vue), iOS (SwiftUI), Android (Compose), Desktop (Tauri).

Empower Business Users

Non-technical users configure dashboards and reports themselves. IT enables, doesn't gatekeep.

Consistent UX Guarantee

Same form layout on every platform. Validation enforced everywhere automatically. Zero drift.

Architecture Overview

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                     Application Layer                        β”‚
β”‚  Business Users configure dashboards, forms, actions         β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                            ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                   ObjectUI Protocol (JSON)                   β”‚
β”‚  Layout DSL β€’ Widget Contract β€’ Action Definitions           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                            ↓
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        ↓                   ↓                     ↓
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
  β”‚   Web    β”‚      β”‚  Mobile  β”‚         β”‚ Desktop  β”‚
  β”‚ Renderer β”‚      β”‚ Renderer β”‚         β”‚ Renderer β”‚
  β”‚  React   β”‚      β”‚  Swift   β”‚         β”‚  Tauri   β”‚
  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Key Insight: The protocol is the contract. Any renderer that implements ObjectUI can paint the same interface.

Core Components

Quick Example

Traditional Approach (React)

// CustomerForm.tsx (300+ lines of code)
import { useState, useEffect } from 'react';
import { TextField, Select, Button } from '@mui/material';

function CustomerForm({ customerId }) {
  const [data, setData] = useState({});
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    fetch(`/api/customers/${customerId}`)
      .then(res => res.json())
      .then(data => { setData(data); setLoading(false); });
  }, [customerId]);
  
  const handleSubmit = async () => {
    if (!data.name || data.name.length < 3) {
      alert('Name must be at least 3 characters');
      return;
    }
    await fetch(`/api/customers/${customerId}`, {
      method: 'PUT',
      body: JSON.stringify(data)
    });
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <TextField
        label="Customer Name"
        value={data.name || ''}
        onChange={e => setData({...data, name: e.target.value})}
        required
        minLength={3}
      />
      <Select
        label="Status"
        value={data.status || 'active'}
        onChange={e => setData({...data, status: e.target.value})}
      >
        <option value="active">Active</option>
        <option value="inactive">Inactive</option>
      </Select>
      <Button type="submit">Save</Button>
    </form>
  );
}

Problems:

  • 300+ lines for a 2-field form
  • Validation logic duplicated (frontend + backend)
  • Change field order? Edit code, rebuild, redeploy
  • Mobile app? Rewrite entire form in Swift

ObjectUI Approach

# customer.formview.yml (15 lines)
name: customer_edit
object: customer
mode: edit
layout:
  sections:
    - label: Basic Information
      columns: 2
      fields:
        - name
        - status
actions:
  - type: standard_save
    label: Save

Benefits:

  • 15 lines vs 300 lines (95% reduction)
  • Validation inherited from customer.object.yml (no duplication)
  • Change field order? Edit YAML, refresh page (5 seconds)
  • Mobile app? Same YAML file renders natively (zero code)

Result: What took 2 days now takes 2 minutes.

Server-Driven UI Flow

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Client  β”‚                                    β”‚  Server  β”‚
β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜                                    β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜
     β”‚                                               β”‚
     β”‚  1. Request Layout                            β”‚
     β”‚  GET /api/ui/layouts/customer/form            β”‚
     │──────────────────────────────────────────────>β”‚
     β”‚                                               β”‚
     β”‚                                     2. Resolve Layout
     β”‚                              (Merge: Base + Admin Config + User Prefs)
     β”‚                                               β”‚
     β”‚  3. Return Layout JSON                        β”‚
     β”‚  { type: 'form', fields: [...] }              β”‚
     β”‚<──────────────────────────────────────────────│
     β”‚                                               β”‚
4. Render Layout                                     β”‚
(Traverse JSON, instantiate components)              β”‚
     β”‚                                               β”‚
     β”‚  5. User Interaction (Click Save)             β”‚
     β”‚                                               β”‚
     β”‚  6. Submit Data                               β”‚
     β”‚  POST /api/data/customer/123                  β”‚
     │──────────────────────────────────────────────>β”‚
     β”‚                                               β”‚
     β”‚                                   7. Validate & Save
     β”‚                              (Run validation rules from schema)
     β”‚                                               β”‚
     β”‚  8. Return Success                            β”‚
     β”‚<──────────────────────────────────────────────│
     β”‚                                               β”‚
9. Update UI                                         β”‚
(Show success toast, refresh list)                   β”‚

View Types

FormView: Record Editing

FormView.create({
  object: 'project',
  layout: {
    mode: 'tabbed',  # or 'simple' | 'wizard'
    tabs: [
      {
        label: 'Details',
        sections: [
          { label: 'Basic Info', columns: 2, fields: ['name', 'status'] },
          { label: 'Dates', columns: 2, fields: ['start_date', 'end_date'] }
        ]
      },
      {
        label: 'Team',
        sections: [
          { label: 'Members', fields: ['manager', 'team_lead'] }
        ]
      }
    ]
  }
});

Renders as:

  • Web: Material-UI tabs with collapsible sections
  • Mobile: SwiftUI NavigationView with grouped lists
  • Desktop: Native tabs with keyboard shortcuts

ListView: Data Tables

ListView.create({
  object: 'project',
  viewType: 'grid',  # or 'kanban' | 'calendar' | 'gantt'
  columns: [
    { field: 'name', width: 200, sortable: true },
    { field: 'status', width: 120, filter: true },
    { field: 'due_date', width: 150, sortable: true }
  ],
  filters: {
    default: [{ field: 'status', operator: 'in', value: ['active', 'pending'] }]
  },
  actions: [
    { type: 'standard_new', label: 'New Project' },
    { type: 'standard_edit', label: 'Edit' }
  ]
});

Features:

  • Sorting, filtering, pagination (all server-side)
  • Bulk actions (select multiple rows)
  • Inline editing (double-click cell)
  • Export to CSV/Excel

Dashboard: Analytics Composition

Dashboard.create({
  name: 'sales_overview',
  label: 'Sales Overview',
  layout: {
    rows: [
      {
        widgets: [
          { type: 'metric', title: 'Revenue', value: '$1.2M', span: 3 },
          { type: 'metric', title: 'Deals', value: '47', span: 3 },
          { type: 'metric', title: 'Win Rate', value: '68%', span: 3 },
          { type: 'metric', title: 'Avg Deal', value: '$25K', span: 3 }
        ]
      },
      {
        widgets: [
          { type: 'chart', chartType: 'line', title: 'Revenue Trend', span: 8 },
          { type: 'chart', chartType: 'pie', title: 'By Stage', span: 4 }
        ]
      },
      {
        widgets: [
          { type: 'list', object: 'opportunity', view: 'closing_soon', span: 12 }
        ]
      }
    ]
  }
});

Widget Types:

  • Metric: KPI cards with trend indicators
  • Chart: Bar, line, pie, donut, area, scatter
  • List: Embedded list views
  • Custom: Register your own widget components

Design Principles

1. Declarative Over Imperative

// ❌ Imperative (React)
function TaskList() {
  const [tasks, setTasks] = useState([]);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    fetch('/api/tasks').then(res => res.json()).then(data => {
      setTasks(data);
      setLoading(false);
    });
  }, []);
  
  return loading ? <Spinner /> : <Table rows={tasks} />;
}

// βœ… Declarative (ObjectUI)
ListView.create({
  object: 'task',
  columns: [{ field: 'title' }, { field: 'status' }]
});

Why: Declarative definitions are:

  • Easier to understand: No lifecycle logic, just "what" not "how"
  • Easier to maintain: Change a field? Edit one line
  • Easier to test: Validate JSON schema instead of testing React hooks

2. Configuration Over Code

UI changes should never require rebuilding:

Traditional:  Change Code β†’ Build β†’ Test β†’ Deploy β†’ (iOS) App Store Wait
              ────────────────────── 2-14 days ──────────────────────────

ObjectUI:     Change Config β†’ Refresh Page
              ───────── 5 seconds ─────────

Why: Business requirements change daily. Code deployment cycles are weekly at best.

3. Responsive by Default

All layouts use a 12-column grid that adapts automatically:

sections:
  - label: Contact Info
    columns: 2  # Desktop: 2 columns, Tablet: 1 column, Mobile: 1 column
    fields:
      - email
      - phone

Breakpoints (auto-applied):

  • Desktop: columns: 2 β†’ 2 columns
  • Tablet: columns: 2 β†’ 1 column
  • Mobile: columns: 2 β†’ 1 column (stacked)

4. Type-Safe by Default

ObjectUI definitions are Zod schemas:

import { FormViewSchema } from '@objectstack/spec';

// Runtime validation
const config = FormViewSchema.parse(userConfig);

// TypeScript inference
type FormView = z.infer<typeof FormViewSchema>;

Benefits:

  • Catch errors at configuration time, not runtime
  • IDE autocomplete for all properties
  • Auto-generated documentation

Integration with ObjectQL

ObjectUI is tightly integrated with ObjectQL:

# 1. Define Object (ObjectQL)
# customer.object.yml
name: customer
fields:
  name:
    type: text
    required: true
    maxLength: 100
  email:
    type: email
    unique: true
  status:
    type: select
    options:
      - { value: active, label: Active }
      - { value: inactive, label: Inactive }
    default: active

# 2. Define Form (ObjectUI)
# customer.formview.yml
name: customer_edit
object: customer  # ← References ObjectQL schema
layout:
  sections:
    - fields: [name, email, status]  # ← Fields auto-validated

What ObjectUI Inherits from ObjectQL:

  • Field Types: text β†’ Text input, select β†’ Dropdown
  • Validation Rules: required, maxLength, unique
  • Default Values: Pre-populate forms
  • Relationships: lookup field β†’ Searchable modal
  • Permissions: Hide fields user can't see

Result: Zero duplication. Define once in ObjectQL, UI auto-configures.

Real-World Use Cases

Multi-Tenant SaaS Customization

Challenge: You sell a CRM to 500 customers. Each wants custom branding (logo, colors) and slightly different form layouts.

ObjectUI Solution:

// Tenant A gets blue theme with 3-column layout
tenantA.formview.yml:
  layout: { columns: 3 }
  theme: { primary: '#0066CC' }

// Tenant B gets green theme with 2-column layout
tenantB.formview.yml:
  layout: { columns: 2 }
  theme: { primary: '#00AA00' }

Value:

  • Same codebase serves all 500 customers
  • Launch new tenant in 10 minutes (not 2 weeks)
  • $2M/year saved on custom development

Regulatory Compliance Agility

Challenge: Fintech operates in 50 countries. EU requires GDPR consent checkbox, India requires Aadhaar number, USA requires SSN.

ObjectUI Solution:

# EU variant
formview.eu.yml:
  fields: [name, email, gdpr_consent]

# India variant
formview.in.yml:
  fields: [name, email, aadhaar]

# USA variant
formview.us.yml:
  fields: [name, email, ssn]

Value:

  • New regulation? Update YAML, deploy instantly
  • Avoided $500K regulatory fine
  • Market entry time: 1 week (was 3 months)

Offline-First Mobile Apps

Challenge: Field service technicians work in areas with no connectivity. App must work offline and sync when online.

ObjectUI Solution:

# Same definition used by web and mobile
customer.formview.yml:
  object: customer
  fields: [name, email, phone]

# Mobile renderer caches layout + data
mobile_renderer:
  offline_cache: true
  sync_strategy: periodic

Value:

  • UI consistency eliminates confusion
  • $100K/year saved on support tickets

What's Next?

Additional Resources

For Implementers

Building a renderer for ObjectUI?

For Users

Using ObjectUI to build applications?

On this page