ObjectUIObjectUI

Architecture Overview

Deep dive into the ObjectUI 3-layer architecture, data flow, plugin system, state management, and action system.

Architecture Overview

ObjectUI is a Server-Driven UI (SDUI) engine that transforms JSON schemas into fully interactive React interfaces built on Shadcn/Tailwind. This document covers the internal architecture, data flow, and extension points.

The 3-Layer Architecture

ObjectUI enforces a strict separation across three layers. Each layer has clear constraints on what it may import and what it may contain.

┌─────────────────────────────────────────────────────────────────────┐
│  Layer 1: @objectstack/spec v3.0.0 (The Protocol)                  │
│  Pure TypeScript type definitions — 12 export modules               │
│  ❌ No runtime code. No React. No dependencies.                    │
└──────────────────────────────┬──────────────────────────────────────┘
                               │ imports (never redefines)
┌──────────────────────────────▼──────────────────────────────────────┐
│  Layer 2: @object-ui/types (The Bridge)                             │
│  Re-exports spec types + ObjectUI-specific schemas                  │
│  ❌ No runtime code. Zero runtime dependencies.                    │
└──────────────────────────────┬──────────────────────────────────────┘
                               │ consumed by
┌──────────────────────────────▼──────────────────────────────────────┐
│  Layer 3: Implementations (The Runtime)                             │
│  core, react, components (91+), fields (35+), plugins, etc.        │
└─────────────────────────────────────────────────────────────────────┘

Layer 1 — @objectstack/spec (The Protocol)

The upstream JSON specification for all ObjectStack products. ObjectUI imports these types but never redefines them. Located externally; consumed as @objectstack/spec ^3.0.0.

Layer 2 — @object-ui/types (The Bridge)

Lives in packages/types/. Re-exports spec types and adds ObjectUI-specific schemas (component schemas, widget props, field props). Has zero runtime dependencies — only type-level imports of @objectstack/spec and zod for validation schemas. Marked sideEffects: false.

Layer 3 — Implementations (The Runtime)

All packages that ship runnable code: core, react, components, fields, layout, i18n, auth, permissions, tenant, and every plugin-* package.

Package Dependency Graph

@objectstack/spec


@object-ui/types ◄─────────────────────────────────────┐
       │                                                │
       ▼                                                │
@object-ui/core                                         │
       │  (registry, evaluator, actions, validation)    │
       ▼                                                │
@object-ui/react ──────► @object-ui/i18n                │
       │  (SchemaRenderer, hooks, contexts)             │
       ├──────────────────────────────────────┐         │
       ▼                                      ▼         │
@object-ui/components              @object-ui/fields    │
       │  (91+ Shadcn atoms)          (35+ inputs)      │
       ▼                                      ▼         │
@object-ui/layout           @object-ui/plugin-*         │
  (AppShell, Header,          (grid, kanban, charts,    │
   Sidebar, routing)           dashboard, form, etc.)   │
                                      │                 │
                                      └─────────────────┘
                                   (all packages import types)

Strict rules:

PackageMay ImportMust NOT Import
types@objectstack/specAny runtime package
coretypes, lodash, zodReact, any UI library
reactcore, types, i18nPlugin packages directly
componentstypes (for props)core, react
fieldstypes (for FieldWidgetProps)core business logic
plugin-*Any packageOther plugins

Data Flow: Schema → Screen

The rendering pipeline transforms a JSON schema into a live React component tree:

  JSON Schema (from API or static file)


  ┌─────────────┐     ┌──────────────────┐
  │ SchemaRenderer │───►│ ExpressionEvaluator │
  │ (packages/     │     │ Resolves ${...}     │
  │  react/src/)   │     │ expressions         │
  └──────┬────────┘     └──────────────────┘


  ┌─────────────────┐
  │ ComponentRegistry │  ← registry.resolve(schema.type)
  │ (packages/core/  │
  │  src/registry/)  │
  └──────┬──────────┘


  ┌──────────────────┐
  │ React Component   │  ← Wrapped in ErrorBoundary
  │ (Button, Grid,    │     with ARIA attributes
  │  Kanban, etc.)    │
  └──────────────────┘
  1. SchemaRenderer (packages/react/src/SchemaRenderer.tsx) receives a JSON schema object.
  2. It evaluates dynamic expressions via ExpressionEvaluator (packages/core/src/evaluator/).
  3. It looks up the component type in the ComponentRegistry (packages/core/src/registry/Registry.ts).
  4. The matched component renders with schema props, wrapped in a per-component ErrorBoundary with ARIA accessibility attributes (aria-label, aria-describedby, role).

The Plugin System

Plugins are self-contained packages that register heavy or complex views (grids, kanbans, charts). They follow a consistent pattern defined in packages/core/src/registry/PluginSystem.ts.

Plugin Lifecycle

  import 'plugin-kanban'


  PluginSystem.load(plugin)
       │  ├─ Check dependencies
       │  ├─ Prevent duplicate loading
       │  └─ Call plugin.register(scope)

  PluginScope.registerComponent(type, Component, meta)
       │  └─ Auto-prefixes with plugin namespace

  ComponentRegistry.register('kanban-ui', KanbanRenderer, {
    namespace: 'plugin-kanban',
    category: 'plugin'
  })

Lazy Loading

Plugins use React.lazy() wrapped by LazyPluginLoader (packages/react/src/LazyPluginLoader.tsx):

  • Retry logic: 2 retries with 1-second delay by default
  • Custom fallbacks: Loading skeleton + error boundary
  • Tree-shaking: Plugins are code-split from the initial bundle

Plugin Registration Pattern

Every plugin follows the same structure (see packages/plugin-kanban/, packages/plugin-grid/):

// 1. Lazy-load the heavy implementation
const LazyKanban = React.lazy(() => import('./KanbanImpl'));

// 2. Define a thin wrapper with Suspense
const KanbanRenderer: React.FC<Props> = ({ schema }) => (
  <Suspense fallback={<Skeleton />}>
    <LazyKanban schema={schema} />
  </Suspense>
);

// 3. Register in the global registry
ComponentRegistry.register('kanban-ui', KanbanRenderer, {
  namespace: 'plugin-kanban',
  label: 'Kanban Board',
  category: 'plugin',
  inputs: [/* schema config */]
});

Scaffold a new plugin with npx @object-ui/create-plugin.

State Management

ObjectUI uses React Context for all state management, with contexts scoped to specific concerns:

Core Contexts (packages/react/src/context/)

ContextPurpose
SchemaRendererContextData scope + debug mode for the rendering tree
ActionContextAction runner instance for handling user interactions
ThemeContextTheme tokens and dark/light mode
NotificationContextToast and notification system
DndContextDrag-and-drop state for sortable views

Domain Contexts (separate packages)

ContextPackagePurpose
AuthContextpackages/auth/Authentication state
PermissionContextpackages/permissions/RBAC/ABAC permissions
TenantContextpackages/tenant/Multi-tenant isolation
I18nProviderpackages/i18n/Locale and translations

Contexts are composed at the application root and consumed by plugins and components via hooks (e.g., useActionRunner, useExpression, useViewData).

Expression Evaluation

The expression engine lives in packages/core/src/evaluator/ and powers dynamic schemas:

Components

  • ExpressionEvaluator.ts — Main engine: parses and evaluates ${...} template expressions.
  • ExpressionContext.ts — Variable scope: provides data, user, params to expressions.
  • ExpressionCache.ts — Caches parsed expressions for repeated evaluations.
  • FormulaFunctions.ts — Built-in functions available inside expressions.

Expression Types

{
  "visible": "${data.role === 'admin'}",
  "label": "Hello, ${data.user.name}!",
  "disabled": "${data.status !== 'draft'}",
  "className": "${data.priority === 'high' ? 'text-red-500' : 'text-gray-500'}"
}

Expressions support:

  • String interpolation: "Welcome, ${data.name}"
  • Conditionals: "${data.age > 18}"
  • Ternary operators: "${data.active ? 'Yes' : 'No'}"
  • Variable references: data.*, user.*, params.*

Action System

The action system (packages/core/src/actions/) handles user interactions defined in schemas.

ActionRunner (ActionRunner.ts)

Executes action schemas and returns directives:

  User Click → Schema Action


  ActionRunner.execute(action, context)
       │  ├─ Check confirmation dialog
       │  ├─ Evaluate conditions
       │  └─ Execute by action type

  ActionResult
    ├─ reload: boolean
    ├─ redirect: string
    ├─ modal: SchemaObject
    └─ toast: { message, type }

Supported Action Types

TypeDescription
scriptExecute inline JavaScript
urlNavigate to a URL
apiMake an API request (AJAX)
modalOpen a modal with a nested schema
flowExecute a multi-step action sequence

TransactionManager (TransactionManager.ts)

Wraps multi-step actions in transactions for consistency, supporting rollback on failure.

Key Directories Reference

packages/
├── types/src/           # Layer 2 — Pure type definitions
├── core/src/
│   ├── evaluator/       # Expression engine
│   ├── registry/        # Component & plugin registries
│   ├── actions/         # ActionRunner, TransactionManager
│   ├── validation/      # Schema validation engine
│   ├── adapters/        # Data source adapters (API, Value)
│   ├── data-scope/      # DataScopeManager
│   ├── query/           # Query AST (filtering/sorting)
│   ├── theme/           # ThemeEngine
│   └── builder/         # Schema builder utilities
├── react/src/
│   ├── SchemaRenderer.tsx
│   ├── LazyPluginLoader.tsx
│   ├── context/         # All React contexts
│   └── hooks/           # 20+ hooks (useExpression, useActionRunner, etc.)
├── components/          # 91+ Shadcn UI atoms
├── fields/              # 35+ field renderers
├── layout/              # AppShell, Header, Sidebar
├── i18n/                # Internationalization
├── auth/                # AuthContext + providers
├── permissions/         # RBAC/ABAC
├── tenant/              # Multi-tenancy
└── plugin-*/            # 20 plugin packages
    ├── plugin-grid/
    ├── plugin-kanban/
    ├── plugin-charts/
    ├── plugin-dashboard/
    ├── plugin-form/
    └── ... (15 more)

Further Reading

On this page