ObjectUIObjectUI

ObjectUI CLI

Standalone command-line interface for building ObjectUI applications from JSON/YAML schemas

ObjectUI CLI

@object-ui/cli is a standalone command-line tool for scaffolding, developing, building, and validating ObjectUI applications driven by JSON or YAML schemas.

It is not an oclif plugin — it ships its own objectui binary just like vite, next, or astro.

Installation

npm install -g @object-ui/cli
# or run on demand
npx @object-ui/cli --help

The installed binary is named objectui.

Quick start

objectui init my-dashboard --template dashboard
cd my-dashboard
objectui dev app.json

Your app runs at http://localhost:3000.

Commands

Run objectui --help for the command list, and objectui <command> --help for a command's full flag list.

objectui init [name]

Scaffold a new ObjectUI project (creates app.json, package.json, vite.config.ts, tsconfig.json, index.html, and src/).

objectui init my-app
objectui init my-app --template form
objectui init . --template dashboard
FlagDefaultDescription
-t, --template <name>dashboardTemplate to use (simple, form, dashboard)

objectui dev [schema]

Start the development server with hot reload. Opens the browser automatically.

objectui dev app.json
objectui dev pages/                    # file-system routing
objectui dev my-app/pages              # ...from anywhere, not just inside my-app
objectui dev --port 8080 --no-open
Argument / FlagDefaultDescription
[schema] (positional)app.jsonPath to JSON/YAML schema file, or a project / pages/ directory
-p, --port <port>3000Port to bind
-h, --host <host>localhostHost to bind
--no-open—Do not open the browser

objectui serve [schema]

Legacy alias of dev, kept for older scripts. Same positional argument and flags as dev, --no-open included.

objectui build [schema]

Build the application for production with Vite.

objectui build app.json
objectui build --out-dir build --clean
Argument / FlagDefaultDescription
[schema] (positional)app.jsonPath to JSON/YAML schema file, or a project / pages/ directory
-o, --out-dir <dir>distOutput directory
--cleanfalseClean output directory before build

objectui start

Serve a previously-built production bundle.

objectui start
objectui start --port 8080 --dir build
FlagDefaultDescription
-p, --port <port>3000Port to bind
-h, --host <host>0.0.0.0Host to bind
-d, --dir <dir>distDirectory to serve

objectui generate <type> <name> (alias g)

Generate new resources.

objectui generate object User
objectui g page Dashboard
objectui generate plugin my-widget

objectui validate [schema]

Validate a schema file against the ObjectUI specification. Exits with code 0 on success and non-zero on failure — use it in CI pipelines.

objectui validate app.json
objectui validate ./schemas/dashboard.yaml

What is judged

Every node is judged by its own component schema, at every depth: a child in a node-level slot such as children, and a component nested in a container's props bag — at the positions @objectstack/spec's own page walk (walkAddressedPageComponents) descends, which for the page containers are properties.children and properties.items[].children. A nested failure is reported at the nested node's real path:

1. Invalid input
   Path: properties → items → 0 → children → 0 → type
   Code: invalid_union
   No arm accepts type "element:txt" at properties → items → 0 → children → 0.
   Nearest of the … accepted types: element:text, element:button, …

A slot that walk does not descend is not judged here.

Reading a failure

A document is checked against a union of every component type, so a failure is reported first at the document root and then narrowed to the arm your type selected. Each numbered issue carries a path and a code; the 1.1-style entries beneath it are that arm's own diagnosis, with the real path to the node that failed:

1. Invalid input
   Path: (root)
   Code: invalid_union
   1.1 RETIRED (objectui#6523) — dividers are `{ separator: true }`; …
       Path: items → 0 → type
       Code: invalid_type

Only the selected arm is shown. When your type matches no component at all, the report says so and offers the nearest few of the accepted values instead:

1. Invalid input
   Path: (root)
   Code: invalid_union
   No arm accepts type "dropdwn-menu".
   Nearest of the 108 accepted types: dropdown-menu, context-menu, component, drawer, breadcrumb

A document with no type at all is reported the same way, without a candidate list — there is nothing for the suggestions to be near.

Undeclared keys

objectui validate parses through the strict authoring face: a key that no schema declares is refused, on the root node and on every nested node, where the renderer would silently ignore it. Each refused key is named once more after the numbered issues, with a prescription — including one inside a union the node fits more than one arm of, such as a dashboard widget, which the issue list reports only as an Invalid input that names no key:

Undeclared keys — the strict authoring face refuses them:
   Undeclared key "nonsense" at children → 0 (type "input"): no schema declares it there. Remove it, or check its spelling against the keys declared at that position.

There is no flag to relax this: remove the key, or correct its spelling.

objectui check

Scan the project for ObjectUI schema files and report on the ones it recognises. No flags.

A JSON file with a root type is recognised when its root carries an ObjectUI structural key (children, className, …) or when the whole document validates as an ObjectUI component schema — through the same strict parse objectui validate runs, so a document carrying an undeclared key does not validate here either. Recognised files have their type checked against the component types this build registers.

The two ways in are not equal. A document recognised by validating was parsed in full, nested nodes included. A document recognised by a structural key was not parsed at all: a page whose children hold a node with a refused key is recognised on children alone, and check says nothing about that node. objectui validate is the command that parses it.

Files that are recognised by neither arm fall into one of two reports, and the distinction matters:

  • Reported by name when the root type does name a registered component. That is ObjectUI content the validator could not accept — either the document is off-spec, or its component type is not modelled by @object-ui/types. Under each file, check prints the validator's first issue: its path, spelled as objectui validate spells it, and its message — then one line per undeclared key, worded as objectui validate words it. Run objectui validate <file> for the full diagnosis.
  • Counted as skipped otherwise. A root type heads several unrelated JSON vocabularies — package.json's "type": "module" most of all — and those are not ObjectUI files.
⚠️ 1 file carries a registered ObjectUI component type but did not validate as an ObjectUI schema:
   stores.json (type "object-map")
     Issue at properties → map: Unrecognized key(s) on this map configuration: `latitudeFieId`. Did you mean `latitudeFieId` → `latitudeField`? Until these shapes were closed an unknown key was dropped silently — the view still rendered, without whatever the key was meant to configure.

Expressions on text keys that are never evaluated

SchemaRenderer evaluates a ${…} expression on the closed text keys title, label, value and description only where @objectstack/spec's expressionBindableTextKeysFor(type) lists that key for the node's type. On any other type, the expression is never resolved: the user sees its literal text, or nothing. objectui check refuses it in every recognised file, and in every file it reports by name as a registered type that did not validate — a document carrying an undeclared key is still judged for its expressions:

  • Component nodes only. The file's root node, every node its children hold, and every node under a slot the node's type declares — a dialog's trigger and content, a tab item's content, a page's regions[].components — at any depth. Which keys are slots is per type and declared once, as NODE_SLOT_DECLARATIONS / nodeSlotsFor in @object-ui/types, the same declaration core's schema validator and the SDUI parser walk; the check keeps no list of its own. Objects under any other key (a form's fields, a grid's columns) are definitions, not nodes, and are not judged. A root whose type is page keeps its own title: that is a page key, while its children and its regions' components are still judged.
  • The type is matched as written. ui:card is not card, exactly as at render time.
  • A type no registered component answers to is warned about, not refused: a custom renderer may evaluate its own keys.
  • There is no escape spelling. A ${…} meant as literal text on one of these keys — a code sample in a code-editor's value, a shell ${VAR} — is refused too. Write the text without ${, for example with string concatenation in a code sample.
x Unevaluated expression in dashboard.json at children → 0 → title: "statistic" never evaluates `title` (it evaluates only `label`, `value`, `description`), so the expression is never resolved: the user sees its literal text, or nothing.
   Channels SchemaRenderer evaluates: `label`, `value`, `description`, `content`, `properties.title` (evaluated, then hoisted onto the node); or resolve the value in the host before the schema reaches SchemaRenderer.

A run with no parse errors and no refused expression ends on a tally rather than a pass: how many recognised files validated, how many were recognised by a structural key and not validated, and how many did not validate, followed by a pointer to objectui validate for the verdict.

Unreadable JSON and a refused expression make objectui check exit non-zero; everything else above is a report. Use objectui validate when you want a failing exit code on a document that does not validate.

objectui doctor

Diagnose common project setup issues: package.json and its React and Tailwind dependencies, the Tailwind PostCSS plugin and CSS entry, and tsconfig.json. It reports only and changes no file.

objectui analyze

Analyze the built application's bundle: the total, JavaScript and CSS sizes of dist/ in the current directory (where objectui build writes by default), and its ten largest files. No flags. With no dist/ to analyze, it exits non-zero.

Scaffolding a plugin

A plugin is scaffolded by its own initializer, @object-ui/create-plugin, not by this CLI:

npm create @object-ui/plugin my-awesome-widget

See Create Plugin for where to run it and what it writes.

Schema file format

dev, serve, build, and validate accept either JSON or YAML.

JSON

{
  "type": "div",
  "className": "min-h-screen flex items-center justify-center",
  "children": {
    "type": "card",
    "title": "Hello World",
    "children": { "type": "text", "content": "Welcome to Object UI!" }
  }
}

YAML

type: div
className: min-h-screen flex items-center justify-center
children:
  type: card
  title: Hello World
  children:
    type: text
    content: Welcome to Object UI!

File-system routing

If the project contains a pages/ directory, dev, serve and build automatically switch to file-system routing — every .json/.yaml file under pages/ becomes a route based on its path.

my-app/
├── app.json                → app config (title, layout)
├── pages/
│   ├── index.json          → /
│   ├── about.json          → /about
│   └── users/
│       └── [id].json       → /users/:id

Which directory is "the project" is decided by the schema argument, so the three commands answer one invocation the same way from anywhere:

  1. The directory the schema argument points into — objectui build my-app/app.json run from above my-app/ routes from my-app/pages/, with my-app/app.json as the app config. A directory argument counts here too: it may be the pages/ directory itself (objectui dev my-app/pages) or the project directory holding it (objectui dev my-app), and both answer exactly as naming the app config beside them does — from any working directory.
  2. Otherwise the current working directory — cd my-app && objectui dev routes from ./pages/, with ./app.json as the app config when present.
  3. Otherwise the named file is rendered on its own (single-schema mode), which is what a lone .json/.yaml schema with no pages/ beside it does. A directory that reaches this step is no project, and is refused by name — it is neither a pages/ directory nor does it contain one.

Programmatic API

import { serve, init } from '@object-ui/cli';

await serve('app.json', { port: '3000', host: 'localhost' });
await init('my-app', { template: 'dashboard' });

Only the long-running serve and project-creating init are exported as functions today; other commands are bin-only.

Troubleshooting

Port already in use

objectui dev --port 3001

Schema not found

objectui dev ./path/to/schema.json
# or, scaffold a fresh one
objectui init .

Run diagnostics

objectui doctor

Package information

Name@object-ui/cli
Binobjectui
LicenseMIT
NodeSee the engines field in the repository's root package.json

Dependencies

The CLI is built on:

  • Vite + @vitejs/plugin-react — bundling and dev server
  • Express + express-rate-limit — preview / production server
  • Commander — argument parsing
  • chalk — terminal colors
  • js-yaml — YAML parsing
  • glob — file-system routing scan

Next steps

Need help?

On this page