Skip to content

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.

ts
import { parseAppDefinition, safeParseAppDefinition } from '@samabaasi/core';

const app = parseAppDefinition(json);          // throws ZodError on invalid
const r = safeParseAppDefinition(json);        // { success, data | error }

Top level

jsonc
{
  "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.tokenRef points at a credential in the vault; the raw token never lives in app.json, prompts, or logs.
  • theme — one of lara-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 componentRenders
DataTableA bound grid: entitySet, dataKey, columns, query, searchable.
ListPage / CrudPage / FormPage / MasterDetailPage / ReportPageFull template screens.
ChartchartType (Line/Area/Bar/Pie) over an entitySet + query, with x + series.
StatA KPI tile — static value or valueFrom (an aggregate).
MarkdownStatic text content (rendered as text — no raw HTML, by design).
Stack / Grid / Card / PanelLayout primitives with nested children (recursive).
(any other string)A Custom section — a registered component by name + opaque props.
jsonc
{
  "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:

jsonc
{ "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).
  • HardeningMarkdown renders as text (no raw HTML); size/depth caps guard against abuse. See Security.

Released under the MIT License.