| title | Protocol Architecture |
|---|---|
| description | How the Data, System, and UI protocols work together as one cohesive system |
import { Database, Layout, Cpu, ArrowRight, CheckCircle, Workflow, Bot, Cloud } from 'lucide-react';
The architecture is built on foundational protocols that work together as a unified system:
} title="Data Protocol" description="ObjectQL: Structure, queries, and constraints." /> } title="UI Protocol" description="ObjectUI: Presentation, interaction, and routing." /> } title="System Protocol" description="ObjectOS: Control, runtime, and governance." /> } title="Automation Protocol" description="Business Logic: Flow, Workflow, Triggers." /> } title="AI Protocol" description="Intelligence: Agents, RAG, Models." /> } title="Cloud Protocol" description="Management: Multi-tenancy, Marketplace, Licensing." />Traditional applications tightly couple data, business logic, and presentation. This creates Implementation Coupling — changing one layer forces changes across the entire stack.
ObjectStack enforces Separation of Concerns through protocol boundaries:
┌───────────────────────────────────────────────────────────┐
│ UI Protocol & AI Protocol │
│ Apps, Views, Dashboards, Agents, RAG │
│ "How do users and agents interact?" │
└───────────────────────────┬───────────────────────────────┘
│ Interface
┌───────────────────────────┴───────────────────────────────┐
│ System Protocol & Automation Protocol & Cloud Protocol │
│ Auth, Permissions, Orchestration, Multi-tenancy │
│ "Who/What can do what, when, and where?" │
└───────────────────────────┬───────────────────────────────┘
│ Control
┌───────────────────────────┴───────────────────────────────┐
│ Data Protocol │
│ Objects, Fields, Queries, Drivers │
│ "What is the data structure?" │
└───────────────────────────────────────────────────────────┘
Role: Define the Structure and Intent of data.
Responsibilities:
- Object schema definitions (what is a "Customer"?)
- Field types and validation rules
- Query language (filtering, sorting, aggregation)
- Database drivers (Postgres, MongoDB, SQLite)
Key Principle: ObjectQL knows nothing about users, permissions, or UI. It only cares about data structure and queries.
// packages/crm/src/objects/customer.object.ts
import { ObjectSchema, Field } from '@objectstack/spec/data';
export const Customer = ObjectSchema.create({
name: 'customer',
label: 'Customer',
icon: 'building',
fields: {
name: Field.text({
label: 'Company Name',
required: true,
maxLength: 120,
}),
industry: Field.select({
label: 'Industry',
options: [
{ label: 'Technology', value: 'technology' },
{ label: 'Finance', value: 'finance' },
{ label: 'Healthcare', value: 'healthcare' },
{ label: 'Retail', value: 'retail' },
],
}),
annual_revenue: Field.currency({
label: 'Annual Revenue',
scale: 2,
}),
primary_contact: Field.lookup('contact', {
label: 'Primary Contact',
}),
},
});This definition is pure metadata. It doesn't know:
- Who can see this data
- How to render a form
- When to trigger workflows
That's the job of the other layers.
Role: Manage the Lifecycle and Governance of requests.
Responsibilities:
- Authentication (who is this user?)
- Authorization (can they access this field?)
- Workflows and automations (what happens after save?)
- Event processing (audit logs, notifications)
- Multi-tenancy and data isolation
Key Principle: ObjectOS acts as the Gateway. No layer can directly access the database; all requests must pass through the OS Kernel.
// packages/crm/src/permissions/sales_rep.permission.ts
import { definePermissionSet } from '@objectstack/spec/security';
// A permission set is keyed by object name. Each object grants the four
// CRUD verbs (allowCreate / allowRead / allowEdit / allowDelete), and
// optional field-level security keyed by "object.field".
export const SalesRep = definePermissionSet({
name: 'sales_rep',
isProfile: true,
objects: {
customer: {
allowCreate: true,
allowRead: true,
allowEdit: true,
allowDelete: false, // Only managers can delete
},
},
fields: {
'customer.annual_revenue': { readable: true, editable: false }, // Read-only
},
});Automations are authored with defineFlow. A flow is a node graph —
a typed start/end plus decision and action nodes wired together by
edges — driven by a trigger type (here record_change):
// packages/crm/src/flows/high_value_customer.flow.ts
import { defineFlow } from '@objectstack/spec';
export const HighValueCustomer = defineFlow({
name: 'high_value_customer',
label: 'High-Value Customer Routing',
type: 'record_change', // runs when a customer record changes
nodes: [
{ id: 'start', type: 'start', label: 'Customer created' },
{ id: 'assign', type: 'action', label: 'Assign to enterprise team' },
{ id: 'notify', type: 'action', label: 'Alert sales leadership' },
{ id: 'end', type: 'end', label: 'End' },
],
edges: [
// Only branch when annual_revenue > 1,000,000 (CEL condition)
{ id: 'e1', source: 'start', target: 'assign', condition: 'record.annual_revenue > 1000000' },
{ id: 'e2', source: 'assign', target: 'notify' },
{ id: 'e3', source: 'notify', target: 'end' },
],
});ObjectOS orchestrates these rules at runtime, independent of the data structure or UI.
Role: Render the Presentation and handle User Interaction.
Responsibilities:
- App navigation and branding
- List views (grid, kanban, calendar)
- Form layouts (simple, tabbed, wizard)
- Dashboards and reports
- Actions and buttons
Key Principle: ObjectUI is a Rendering Engine, not a hardcoded interface. It asks ObjectQL "What is the schema?" and dynamically generates the UI.
// packages/crm/src/views/customer.view.ts
import { defineView } from '@objectstack/spec';
// A view is a container bound to an object; its `list` slot holds the
// list-view config. Columns are field names; sort is { field, order }.
export const CustomerView = defineView({
object: 'customer',
list: {
label: 'All Customers',
type: 'grid',
columns: ['name', 'industry', 'annual_revenue', 'primary_contact'],
sort: [{ field: 'name', order: 'asc' }],
},
});// packages/crm/src/views/customer.view.ts
import { defineView } from '@objectstack/spec';
// The same view container can also carry a `form` slot. A form is laid out
// with `sections`, each section listing the fields it renders.
export const CustomerView = defineView({
object: 'customer',
form: {
label: 'Customer Details',
type: 'tabbed',
sections: [
{
label: 'Company Information',
columns: 2,
fields: ['name', 'industry', 'annual_revenue'],
},
{
label: 'Contact',
fields: ['primary_contact'],
},
],
},
});The UI doesn't "know" the field types. It asks ObjectQL for the schema and renders accordingly:
Field.text→ Text inputField.select→ DropdownField.lookup→ Autocomplete lookupField.currency→ Number input with currency formatting
Let's trace a real-world scenario: A sales rep creates a new high-value customer.
The snippets in this walkthrough (`Auth.getCurrentUser`, `Permission.check`, `ObjectQL.getSchema`, `driver.insert`, `Workflow.getTriggersFor`, …) are **conceptual pseudo-code** that illustrate the control flow between layers. They are not literal exported APIs — don't search for these exact symbols.User fills out the "Create Customer" form:
- Name: "Acme Corp"
- Industry: "Technology"
- Annual Revenue: $5,000,000
- Primary Contact: "John Doe"
User clicks "Save"
// ObjectUI dispatches an action to ObjectOS
const request = {
action: 'create',
object: 'customer',
data: {
name: 'Acme Corp',
industry: 'technology',
annual_revenue: 5000000,
primary_contact: 'contact_12345',
},
};// Kernel checks: Does this user have permission?
const user = await Auth.getCurrentUser();
const canCreate = await Permission.check({
user,
object: 'customer',
operation: 'create',
});
if (!canCreate) {
throw new Error('Permission denied');
}// Kernel asks ObjectQL: Is this data valid?
const schema = ObjectQL.getSchema('customer');
const validation = schema.validate(request.data);
if (!validation.success) {
throw new ValidationError(validation.errors);
}// ObjectQL compiles the request into a database operation
const driver = ObjectQL.getDriver(); // Postgres, MongoDB, etc.
const result = await driver.insert('customer', {
name: 'Acme Corp',
industry: 'technology',
annual_revenue: 5000000,
primary_contact_id: 'contact_12345',
});// Kernel checks: Are there any workflows for this event?
const workflows = Workflow.getTriggersFor('customer', 'after_create');
for (const workflow of workflows) {
if (workflow.conditionsMet(result)) {
await workflow.execute(result);
}
}
// In this case:
// ✅ Annual revenue > $1M
// → Assign to enterprise sales team
// → Send alert email to leadership// Kernel returns success response
// UI optimistically updates the screen
// UI shows toast notification: "Customer created successfully"
// UI navigates to the new customer detail pageHere's how all three protocols collaborate for a Kanban Board feature:
import { ObjectSchema, Field } from '@objectstack/spec/data';
export const Opportunity = ObjectSchema.create({
name: 'opportunity',
label: 'Opportunity',
icon: 'target',
fields: {
title: Field.text({
label: 'Title',
required: true,
}),
stage: Field.select({
label: 'Stage',
options: [
{ label: 'Prospecting', value: 'prospecting', default: true },
{ label: 'Qualification', value: 'qualification' },
{ label: 'Proposal', value: 'proposal' },
{ label: 'Closed Won', value: 'closed_won' },
],
}),
amount: Field.currency({
label: 'Amount',
scale: 2,
}),
customer: Field.lookup('customer', {
label: 'Customer',
}),
},
});import { defineFlow } from '@objectstack/spec';
export const OpportunityWon = defineFlow({
name: 'opportunity_won',
label: 'On Opportunity Won',
type: 'record_change',
nodes: [
{ id: 'start', type: 'start', label: 'Stage changed' },
{ id: 'invoice', type: 'action', label: 'Create invoice' },
{ id: 'notify', type: 'action', label: 'Notify sales team' },
{ id: 'end', type: 'end', label: 'End' },
],
edges: [
{ id: 'e1', source: 'start', target: 'invoice', condition: "record.stage == 'closed_won'" },
{ id: 'e2', source: 'invoice', target: 'notify' },
{ id: 'e3', source: 'notify', target: 'end' },
],
});import { defineView } from '@objectstack/spec';
// For a kanban list, set type: 'kanban' and put the board config under
// `kanban` (groupByField selects the column axis). `columns` is the list
// of card fields. Drag-and-drop between columns is built in.
export const OpportunityBoard = defineView({
object: 'opportunity',
list: {
type: 'kanban',
columns: ['title', 'amount', 'customer'],
kanban: {
groupByField: 'stage',
summarizeField: 'amount',
columns: ['title', 'amount'],
},
},
});When a user drags an opportunity card from "Proposal" to "Closed Won":
- ObjectUI captures the drag-drop event
- ObjectOS checks if the user has permission to update the
stagefield - ObjectQL validates that
"closed_won"is a valid option - ObjectQL writes the update to the database
- ObjectOS triggers the workflow (create invoice, send notification)
- ObjectUI updates the kanban board to reflect the new state
All from metadata. Zero hardcoded logic.
Swap implementations without breaking the system:
Same Metadata Definitions
↓
┌─────────┼─────────┐
ObjectQL: │ │
Postgres │ MongoDB
│
ObjectOS: │
Node.js │ Python
│
ObjectUI: │
React │ Flutter
Teams can work independently on each layer:
- Data Team: Define objects in ObjectQL
- Backend Team: Build workflows in ObjectOS
- Frontend Team: Create views in ObjectUI
All communicate through protocol contracts, not code dependencies.
Adopt ObjectStack gradually:
- Phase 1: Use ObjectQL as an ORM replacement
- Phase 2: Add ObjectOS for permissions and workflows
- Phase 3: Build ObjectUI views to replace custom forms
Each layer is independently useful.
Mock any layer for testing:
// Test ObjectOS workflows without a real database
const mockObjectQL = {
getSchema: () => CustomerSchema,
insert: jest.fn(),
};
// Test ObjectUI rendering without a real backend
const mockObjectOS = {
checkPermission: () => true,
executeQuery: () => mockData,
};| Layer | Role | Knows About | Doesn't Know About |
|---|---|---|---|
| ObjectQL | Data structure & queries | Schema, fields, drivers | Users, permissions, UI |
| ObjectOS | Runtime & governance | Auth, workflows, events | Data structure, UI layout |
| ObjectUI | Presentation & interaction | Layout, navigation, actions | Business logic, data storage |
The three protocols are loosely coupled but tightly integrated:
- They communicate through standard contracts (Zod schemas)
- They can be swapped or upgraded independently
- They form a complete system when combined
- ObjectQL: Data Protocol - Full data protocol specification
- ObjectUI: UI Protocol - Full view protocol specification
- ObjectOS: System Protocol - Full control protocol specification
- Developer Guide - Build your first ObjectStack application
Understanding the dependency chain helps you design applications correctly.
Field Protocol (Core)
↓
├→ Object Protocol (uses Fields)
├→ Query Protocol (references Fields)
├→ Filter Protocol (filters on Fields)
└→ Validation Protocol (validates Fields)
↓
└→ Hook Protocol (extends validation with code)
Theme Protocol (Foundation)
↓
View Protocol (uses Object, Query)
├→ ListView (uses Query, Filter)
├→ FormView (uses Object, Field)
└→ Dashboard (uses View, Widget)
↓
├→ Page Protocol (composes Views)
└→ App Protocol (organizes Pages)
↓
└→ Action Protocol (UI interactions)
Data Driver Contracts (Database Abstraction)
↓
Datasource Protocol (Connection Config)
↓
├→ Context Protocol (Runtime State)
├→ Events Protocol (Event Bus)
└→ Plugin Protocol (Extensibility)
↓
├→ Security Protocol (Access Control)
├→ API Protocol (External Access)
└→ Automation Protocol (Workflows)
↓
└→ AI Protocol (Intelligence)
UI auto-generated from data definitions:
Object → Field → View → Page → App
Custom UI connected to data:
Page → Component → View (Custom) → Query → Object
Business logic automation:
Object → Hook → Flow → Automation → API/Webhook
AI capabilities on top of data:
Object → RAG Pipeline → Agent → Conversation → UI