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 --helpThe installed binary is named objectui.
Quick start
objectui init my-dashboard --template dashboard
cd my-dashboard
objectui dev app.jsonYour 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| Flag | Default | Description |
|---|---|---|
-t, --template <name> | dashboard | Template 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 / Flag | Default | Description |
|---|---|---|
[schema] (positional) | app.json | Path to JSON/YAML schema file, or a project / pages/ directory |
-p, --port <port> | 3000 | Port to bind |
-h, --host <host> | localhost | Host 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 / Flag | Default | Description |
|---|---|---|
[schema] (positional) | app.json | Path to JSON/YAML schema file, or a project / pages/ directory |
-o, --out-dir <dir> | dist | Output directory |
--clean | false | Clean output directory before build |
objectui start
Serve a previously-built production bundle.
objectui start
objectui start --port 8080 --dir build| Flag | Default | Description |
|---|---|---|
-p, --port <port> | 3000 | Port to bind |
-h, --host <host> | 0.0.0.0 | Host to bind |
-d, --dir <dir> | dist | Directory to serve |
objectui generate <type> <name> (alias g)
Generate new resources.
objectui generate object User
objectui g page Dashboard
objectui generate plugin my-widgetobjectui 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.yamlWhat 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_typeOnly 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, breadcrumbA 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
typedoes 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,checkprints the validator's first issue: its path, spelled asobjectui validatespells it, and its message — then one line per undeclared key, worded asobjectui validatewords it. Runobjectui validate <file>for the full diagnosis. - Counted as skipped otherwise. A root
typeheads 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
childrenhold, and every node under a slot the node's type declares — a dialog'striggerandcontent, a tab item'scontent, a page'sregions[].components— at any depth. Which keys are slots is per type and declared once, asNODE_SLOT_DECLARATIONS/nodeSlotsForin@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'sfields, a grid'scolumns) are definitions, not nodes, and are not judged. A root whosetypeispagekeeps its owntitle: that is a page key, while itschildrenand its regions' components are still judged. - The type is matched as written.
ui:cardis notcard, 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 acode-editor'svalue, 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-widgetSee 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/:idWhich directory is "the project" is decided by the schema argument, so the three commands answer one invocation the same way from anywhere:
- The directory the schema argument points into —
objectui build my-app/app.jsonrun from abovemy-app/routes frommy-app/pages/, withmy-app/app.jsonas the app config. A directory argument counts here too: it may be thepages/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. - Otherwise the current working directory —
cd my-app && objectui devroutes from./pages/, with./app.jsonas the app config when present. - Otherwise the named file is rendered on its own (single-schema mode), which
is what a lone
.json/.yamlschema with nopages/beside it does. A directory that reaches this step is no project, and is refused by name — it is neither apages/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 3001Schema not found
objectui dev ./path/to/schema.json
# or, scaffold a fresh one
objectui init .Run diagnostics
objectui doctorPackage information
| Name | @object-ui/cli |
| Bin | objectui |
| License | MIT |
| Node | See 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
- Create Plugin — author your own plugins
- Schema Overview — learn the schema format
- Plugins — explore available plugins
- Examples — see example projects