app.json schema
app.json is the declarative definition of an entire app — its data connection, theme, navigation, pages, and workflows. It's defined and validated by @samabaasi/core (Zod). It is data, never code: the renderer and engine only ever interpret it.
import { parseAppDefinition, safeParseAppDefinition } from '@samabaasi/core';
const app = parseAppDefinition(json); // throws ZodError on invalid
const r = safeParseAppDefinition(json); // { success, data | error }Top level
{
"schemaVersion": 1, // literal — forward-compatible versioning
"meta": { "name": "Northwind Ops", "version": "1.0.0", "description": "..." },
"odata": {
"baseUrl": "https://.../svc",
"auth": { "type": "bearer", "tokenRef": "northwind" } // ref into the vault — NEVER a raw token
},
"theme": "material-light", // a pack, or { "extends": "...", "tokens": { "--ui-...": "..." } }
"nav": [{ "label": "Products", "to": "/products", "icon": "box", "permission": ["products.view"] }],
"pages": [ /* … */ ],
"workflows": [ /* … (see Workflow engine) */ ]
}odata.auth.tokenRefpoints at a credential in the vault; the raw token never lives inapp.json, prompts, or logs.theme— one oflara-light·lara-dark·material-light·material-dark·bootstrap·high-contrast, or a custom token object.
Pages & sections
A page has a path, optional layout (stack | grid), and sections. Each section is discriminated on component:
Section component | Renders |
|---|---|
DataTable | A bound grid: entitySet, dataKey, columns, query, searchable. |
ListPage / CrudPage / FormPage / MasterDetailPage / ReportPage | Full template screens. |
Chart | chartType (Line/Area/Bar/Pie) over an entitySet + query, with x + series. |
Stat | A KPI tile — static value or valueFrom (an aggregate). |
Markdown | Static text content (rendered as text — no raw HTML, by design). |
Stack / Grid / Card / Panel | Layout primitives with nested children (recursive). |
| (any other string) | A Custom section — a registered component by name + opaque props. |
{
"id": "products", "path": "/products", "title": "Products", "layout": "stack",
"sections": [
{
"id": "grid", "component": "CrudPage", "entitySet": "Products", "dataKey": "ProductID",
"columns": [
{ "field": "ProductName", "header": "Name", "sortable": true },
{ "field": "UnitPrice", "header": "Price", "format": "currency" }
],
"schema": { "fields": [ { "type": "text", "name": "ProductName", "label": "Name", "required": true } ] },
"query": { "filter": "Discontinued eq false", "orderBy": "ProductName asc", "top": 25 }
}
]
}Query options (per data section)
filter (raw $filter), orderBy, select, expand, top, skip, search, apply, count — the JSON subset of the OData query options.
Column display
Columns are JSON‑only (no render functions). format (currency · number · date · datetime · boolean) provides declarative formatting in place of a body renderer.
Forms (schema)
CrudPage/FormPage/MasterDetailPage take a form schema: fields (text, number, textarea, checkbox, switch, radio, date, dropdown — static or OData‑backed, subform, repeater), optional rules, and wizard steps. Recursive (subforms/repeaters nest a schema).
The Condition language
Shared by form visibleWhen, rules when, and workflow trigger.condition:
{ "field": "UnitsInStock", "lt": 10 }
{ "and": [ { "field": "Discontinued", "eq": false }, { "field": "UnitPrice", "gte": 20 } ] }Operators: eq · ne · gt · gte · lt · lte · in, composed with and / or / not.
Safety properties (why declarative is safe)
- No executable code — JSON can't carry functions; the renderer only instantiates registered components.
.strict()validation — unknown keys are rejected (catches typos in authored or AI‑emitted JSON).- Hardening —
Markdownrenders as text (no raw HTML); size/depth caps guard against abuse. See Security.