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
Layout DSL
Declarative page structure with responsive grid system
Widget Contract
Standard props and events for UI components
Action Protocol
Buttons, triggers, and navigation definitions
UI as Data Concept
Philosophy and benefits of metadata-driven UIs
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: SaveBenefits:
- 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
- phoneBreakpoints (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-validatedWhat ObjectUI Inherits from ObjectQL:
- Field Types:
textβ Text input,selectβ Dropdown - Validation Rules:
required,maxLength,unique - Default Values: Pre-populate forms
- Relationships:
lookupfield β 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: periodicValue:
- UI consistency eliminates confusion
- $100K/year saved on support tickets
What's Next?
UI as Data Concept
Deep dive into Server-Driven UI philosophy and benefits
Layout DSL
Master the page, view, and section composition syntax
Widget Contract
Standard props and events for all UI components
Action Protocol
Define buttons, triggers, and navigation flows
Additional Resources
For Implementers
Building a renderer for ObjectUI?
- Renderer Interface - Required methods and lifecycle
- Component Registry - Register custom widgets
- Test Suite - Conformance tests
For Users
Using ObjectUI to build applications?
- FormView Guide - Build complex forms
- ListView Guide - Tables, kanban, calendars
- Dashboard Examples - Analytics dashboards
- Theming Guide - Customize colors and typography
Related Protocols
- ObjectQL (Data Protocol) - Define objects and queries
- ObjectOS (System Protocol) - Runtime configuration
- UI Reference - Complete API reference