The Universal Schema-Driven UI Engine
AI writes the schema; Object UI renders it — production React, no component code
Documentation | Quick Start | Examples | Changelog | Roadmap
Object UI is the View layer of the ObjectStack ecosystem — a standalone, schema-driven renderer that turns a JSON schema (or ObjectStack metadata) into production-grade React UI. Use it on its own with any backend, like Amis or Formily — or let it render ObjectStack apps end to end. Schema-driven is also what makes UI AI-writable: an agent that would drown hand-writing React across fifty screens can emit and refactor compact schemas instead — and every screen stays consistent by construction.
Describe → ObjectStack the open-source protocol, toolkit & production runtime
Render → Object UI this repo — JSON / metadata → React UI
Operate → ObjectOS the commercial runtime environment (Cloud & Enterprise)
A JSON schema in, a production React UI out — no component code.
One schema, many view types — dashboards, Gantt schedules, kanban boards, calendars — plus visual designers to build them without code.
Dashboard, Gantt, Kanban, Calendar rendered from metadata, plus visual designers for objects and flows — all from the plugin packages listed below.
Pick the path that matches where you are starting from:
| You want to… | Start here | Install |
|---|---|---|
| Render a JSON schema inside your React app | examples/hello-world |
@object-ui/react @object-ui/components |
| Embed views in an existing app, against your own backend | examples/byo-backend-console |
@object-ui/app-shell @object-ui/plugin-view @object-ui/providers |
| Stand up a complete console on an ObjectStack backend | examples/console-starter |
fork the example |
| Build a UI from a JSON file, no React code at all | @object-ui/cli |
npm install -g @object-ui/cli |
The examples catalog explains each one in more depth.
npm install @object-ui/react @object-ui/componentsimport React from 'react'
import { PredicateScopeProvider, SchemaRenderer } from '@object-ui/react'
// Importing the package registers every default renderer as a side effect —
// there is no separate registration call.
import '@object-ui/components'
const schema = {
type: "page",
title: "Dashboard",
body: {
type: "grid",
columns: 3,
children: [
{ type: "statistic", label: "Total Users", value: "${stats.users}" },
{ type: "statistic", label: "Revenue", value: "${stats.revenue}" },
{ type: "statistic", label: "Orders", value: "${stats.orders}" }
]
}
}
function App() {
// Every key of this object becomes a root the schema's expressions can read —
// `stats` here is what answers `${stats.users}`.
const scope = {
stats: { users: 1234, revenue: "$56,789", orders: 432 }
}
return (
<PredicateScopeProvider scope={scope}>
<SchemaRenderer schema={schema} />
</PredicateScopeProvider>
)
}
export default AppExpression scope reaches the renderer through the provider, never through a prop on the
element. SchemaRenderer declares exactly one prop, schema, and forwards every other prop
it is handed to the component the schema names — so a value passed as data={…} is neither
read nor refused, and the expression that wanted it is returned as its own source text, with
nothing thrown and one line on the console.
Use the shell and views without the full console infrastructure — your routing, your auth, your API:
npm install @object-ui/app-shell @object-ui/plugin-view @object-ui/providersimport type { FC } from 'react';
import { AppShell } from '@object-ui/app-shell';
import { ObjectView } from '@object-ui/plugin-view';
import { ThemeProvider, DataSourceProvider, useDataSource } from '@object-ui/providers';
import type { DataSource } from '@object-ui/types';
// The two pieces you bring: the backend adapter you implement (see "Custom
// Data Sources" below) and your own sidebar component.
declare const myAPI: DataSource;
declare const MySidebar: FC;
function MyConsole() {
return (
<ThemeProvider>
<DataSourceProvider dataSource={myAPI}>
<AppShell sidebar={<MySidebar />}>
<ContactList />
</AppShell>
</DataSourceProvider>
</ThemeProvider>
);
}
function ContactList() {
const dataSource = useDataSource();
return (
<ObjectView schema={{ type: 'object-view', objectName: 'contact' }} dataSource={dataSource} />
);
}examples/byo-backend-console is the complete working version, with a mock REST adapter.
npm install -g @object-ui/cli
objectui init my-app # scaffold an app with a sample schema
cd my-app
objectui dev app.json # dev server on http://localhost:3000Edit app.json to build your UI.
pnpm install
pnpm -w build
cd examples/console-starter # or examples/byo-backend-console
pnpm devhello-world ships no dev server: copy its App.tsx and schema.json into your own
Vite/Next.js app. schema-catalog is a data package — the canonical JSON schemas the
docs render, a smoke test mounts, and AI agents use as a few-shot corpus.
A ${name.…} in any of these reads the root name off the scope the host published — see
"Basic Usage" for the provider that publishes one. A head name nothing
published is not an error: the expression is returned as its own source text.
{
"type": "form",
"title": "Contact Us",
"fields": [
{ "name": "name", "type": "text", "label": "Full Name", "required": true },
{ "name": "email", "type": "email", "label": "Email", "required": true },
{ "name": "subject", "type": "select", "label": "Subject", "options": [
{ "label": "General Inquiry", "value": "general" },
{ "label": "Bug Report", "value": "bug" },
{ "label": "Feature Request", "value": "feature" }
]},
{ "name": "message", "type": "textarea", "label": "Message", "required": true }
],
"submitLabel": "Send Message"
}{
"type": "object-grid",
"objectName": "user",
"title": "Users",
"columns": [
{ "field": "name", "label": "Name", "sortable": true },
{ "field": "email", "label": "Email" },
{ "field": "role", "label": "Role" },
{ "field": "status", "label": "Status" },
{ "field": "created_at", "label": "Joined" }
],
"showSearch": true,
"showFilters": true,
"operations": { "create": true, "read": true, "update": true, "delete": true, "export": true }
}{
"type": "dashboard",
"title": "Sales Dashboard",
"widgets": [
{ "type": "statistic", "label": "Revenue", "value": "${stats.revenue}", "trend": "up", "description": "+12%", "w": 3, "h": 1 },
{ "type": "statistic", "label": "Orders", "value": "${stats.orders}", "trend": "up", "description": "+8%", "w": 3, "h": 1 },
{ "type": "statistic", "label": "Customers", "value": "${stats.customers}", "trend": "up", "description": "+5%", "w": 3, "h": 1 },
{ "type": "statistic", "label": "Conversion", "value": "${stats.conversion}", "trend": "down", "description": "-2%", "w": 3, "h": 1 },
{ "type": "chart", "chartType": "line", "title": "Revenue Over Time", "w": 8, "h": 3 },
{ "type": "chart", "chartType": "pie", "title": "Sales by Region", "w": 4, "h": 3 }
]
}{
"type": "object-kanban",
"objectName": "tasks",
"groupBy": "status",
"titleField": "title",
"cardFields": ["assignee", "priority", "due_date"],
"columns": [
{ "value": "todo", "label": "To Do", "color": "#6366f1" },
{ "value": "in_progress", "label": "In Progress", "color": "#f59e0b" },
{ "value": "review", "label": "In Review", "color": "#3b82f6" },
{ "value": "done", "label": "Done", "color": "#22c55e" }
]
}📖 More schemas:
examples/schema-catalogis the canonical catalog; examples/ has complete working applications.
Object UI talks to any backend through one DataSource interface.
npm install @object-ui/data-objectstackimport { createObjectStackAdapter } from '@object-ui/data-objectstack';
import { SchemaRenderer, SchemaRendererProvider } from '@object-ui/react';
import type { BaseSchema } from '@object-ui/types';
// Your page schema — "Render a schema" above writes one out in full.
declare const schema: BaseSchema;
const dataSource = createObjectStackAdapter({
baseUrl: 'https://api.example.com',
token: 'your-auth-token'
});
// The adapter is injected through the provider — `SchemaRenderer` does not read
// a `dataSource` prop, it forwards it to the component the schema names.
<SchemaRendererProvider dataSource={dataSource}>
<SchemaRenderer schema={schema} />
</SchemaRendererProvider>⛔ dataSource is the adapter — the object data renderers call find() on — and not an
expression root. It is a different channel from the expression scope above: publish the values
your ${…} expressions read with PredicateScopeProvider, and inject the adapter your data
components query with SchemaRendererProvider.
Adapt any backend (REST, GraphQL, Firebase, …) by implementing DataSource:
import type { DataSource, QueryParams, QueryResult } from '@object-ui/types';
// The members `DataSource` REQUIRES — declared here without bodies, so the
// contract is complete instead of elided. Every other member of the interface
// is optional: implement the ones your backend supports.
declare class MyCustomDataSource<T = unknown> implements DataSource<T> {
find(resource: string, params?: QueryParams): Promise<QueryResult<T>>;
findOne(resource: string, id: string | number, params?: QueryParams): Promise<T | null>;
create(resource: string, data: Partial<T>): Promise<T>;
update(resource: string, id: string | number, data: Partial<T>, opts?: { ifMatch?: string }): Promise<T>;
delete(resource: string, id: string | number, opts?: { ifMatch?: string }): Promise<boolean>;
getObjectSchema(objectName: string): Promise<unknown>;
}Stop writing repetitive UI code. A form is a schema, not a component:
import type { ObjectFormSchema } from '@object-ui/types';
// Traditional React: useState, validation, handlers, JSX — per form
function UserForm() {
// ...
}
// Object UI: declare it
const schema: ObjectFormSchema = {
type: "object-form",
objectName: "user",
mode: "create",
fields: ["name", "email", "role"]
}- Shadcn-native. Components follow Shadcn's DOM structure and Tailwind utilities, so every schema node accepts
classNameoverrides and dark mode just works. - Backend-agnostic. One
DataSourceinterface; adapters for ObjectStack, REST, or anything you write. - Full control. Mix with existing React, override any component in the registry, lazy-load heavy plugins only where they render.
- Typed protocol. Every schema is a TypeScript type in
@object-ui/types, derived from@objectstack/spec.
| Object UI | Amis | Formily | Material-UI | |
|---|---|---|---|---|
| Tailwind native | ✅ | ❌ | ❌ | ❌ |
| TypeScript | ✅ Full | Partial | ✅ Full | ✅ Full |
| Tree shakable | ✅ | ❌ | ||
| Visual designer | ✅ | ✅ | ❌ | ❌ |
| Backend-agnostic data layer | ✅ | ✅ | ❌ |
Grouped by dependency weight — atoms and fields stay light, heavy widgets live in plugins.
| Package | Description |
|---|---|
| @object-ui/types | Pure TypeScript definitions — the protocol layer |
| @object-ui/core | Registry, validation, expression evaluation; zero React |
| @object-ui/react | React bindings and SchemaRenderer |
| @object-ui/components | Standard UI components (Tailwind + Shadcn) |
| @object-ui/fields | Field renderers and registry |
| @object-ui/layout | Layout components with React Router integration |
| Package | Description |
|---|---|
| @object-ui/app-shell | Minimal application shell — the base of every console |
| @object-ui/providers | Theme, data-source, and other reusable context providers |
| @object-ui/auth | AuthProvider, useAuth, AuthGuard, sign-in forms |
| @object-ui/permissions | Object / field / row-level permission guards and hooks |
| @object-ui/i18n | Language packs, RTL layout, date and currency formatting |
| @object-ui/mobile | Responsive components, PWA support, touch gestures |
| @object-ui/collaboration | Presence, live cursors, conflict resolution, comment threads |
| @object-ui/console | Fork-ready runtime console with the full plugin set, shipped as a Hono plugin |
| Package | Description |
|---|---|
| @object-ui/data-objectstack | ObjectStack data adapter |
| Plugin | Description |
|---|---|
| @object-ui/plugin-view | ObjectQL-integrated object views (grid, form, detail) |
| @object-ui/plugin-grid | Advanced data grid |
| @object-ui/plugin-form | Advanced form components |
| @object-ui/plugin-detail | Detail pages with sections, tabs, and related lists |
| @object-ui/plugin-list | Unified list view with view-type switching |
| @object-ui/plugin-kanban | Kanban boards with drag-and-drop (dnd-kit) |
| @object-ui/plugin-calendar | Calendar views |
| @object-ui/plugin-gantt | Gantt charts |
| @object-ui/plugin-timeline | Timelines |
| @object-ui/plugin-tree | Tree and tree-grid views |
| @object-ui/plugin-map | Map visualization |
| @object-ui/plugin-charts | Charts powered by Recharts |
| @object-ui/plugin-dashboard | Dashboard layouts and widgets |
| @object-ui/plugin-report | Pivot tables, grouped aggregations, printable reports |
| @object-ui/plugin-designer | Visual page, data-model, process, and report designers |
| @object-ui/plugin-editor | Rich text editor powered by Monaco |
| @object-ui/plugin-markdown | Markdown rendering |
| @object-ui/plugin-chatbot | Chatbot interface |
| @object-ui/plugin-ai | Schema generation and conversational assistants (Vercel AI SDK) |
| Package | Description |
|---|---|
| @object-ui/cli | Scaffold, develop, build, and validate schema-driven apps |
| @object-ui/create-plugin | Scaffold a new Object UI plugin |
| @object-ui/runner | Universal application runner for testing schemas |
| @object-ui/sdui-parser | Constrained JSX source → schema tree compiler (parse, never execute) |
| @object-ui/react-runtime | Trusted runtime execution for kind: 'react' pages |
| vscode-extension | IntelliSense and live preview for schema files |
Contributions are welcome — read the Contributing Guide first.
git clone https://github.com/objectstack-ai/objectui.git
cd objectui
./scripts/setup.sh # or: pnpm install && pnpm build
pnpm dev # development site
pnpm test- 📖 Quick Reference — the one-page command cheat-sheet for this monorepo
- 🧭 AGENTS.md — the working rules, for humans and coding agents alike
- 🏗️ Architecture Overview — package topology and boundaries
- 🔄 @objectstack/spec — the protocol this renderer implements
- 🗺️ Roadmap — current status and upcoming milestones
- ⭐ Star on GitHub — it helps others find the project
- 📖 Documentation — guides and API reference
- 🐛 Report Issues — found a bug? Let us know
- 🧠 Agent skill —
npx skills add objectstack-ai/objectuiinstalls an Object UI skill for Claude Code, Cursor, Copilot, and more
Object UI is MIT licensed. Object UI is inspired by and builds upon ideas from Amis, Formily, Shadcn/UI, and Tailwind CSS.
Built with ❤️ by the ObjectStack team





