Skip to Content
FrontendAppsReportTemplate JSON schema

Template JSON schema

A template is plain JSON stored raw in templates.source. The render layer reads it, resolves it to a React component from the registry, and fills it with the data-processor result. Nothing gets compiled or executed, so it stays safe and editable without a redeploy.

The JSON is strictly validated against the contract at both save and render time. Unknown keys at any level are rejected, and the widget box shows a JSON-vs-contract diff instead of rendering something wrong.

Shape

The current format is namespaced into three blocks: template, display, and one data.<kind> block for the widget itself.

{ "schemaVersion": 1, "template": { "name": "PHPReactionReport/Widget/ListingTableTemplate", "version": 1, }, "display": { "title": "Inventory by supplier", "colSpan": 4, // integer 1–12 (12 = full width) "variant": "full", // "full" | "sidebox" }, "data": { "listing": { /* the widget's own config, see below */ }, }, }

display may also be written flat at the top level, and the whole legacy flat form (fields at the top level, no data/display) still parses and is treated as a listing block. Prefer the namespaced form above for new templates.

template, name and version

Canonical names have no leading @. The version is a colon suffix or the { name, version } object. A leading @ and the legacy @N suffix are still accepted on input, but don’t write them, the @ gets URL-encoded when passed around.

"template": "PHPReactionReport/Widget/ListingTableTemplate" // LATEST "template": "PHPReactionReport/Widget/ListingTableTemplate:2" // version 2 "template": { "name": "PHPReactionReport/Widget/ListingTableTemplate", "version": 2 }

Omitting the version resolves to the highest registered version. The base name must resolve in the registry:

KindTemplate nameData block
listingPHPReactionReport/Widget/ListingTableTemplatedata.listing
htmlPHPReactionReport/Widget/HTMLTemplatedata.html
summaryPHPReactionReport/Widget/SummaryTemplatedata.summary
meterPHPReactionReport/Widget/AuxiliaryMeterTemplatedata.meter
gaugePHPReactionReport/Widget/GaugeTemplatedata.gauge
graphPHPReactionReport/Widget/GraphTemplatedata.graph

listing and html render fully today. summary, meter, gauge, and graph are registered too, but their data-block schema is reserved (see Other templates).

Dynamic values, $data

The dynamic values come straight from the data processor: $data resolves to the DP’s results columns (from the metadata endpoint) and each row is a populate result.

  • "$data" (bare) as columns/headers: “I don’t know the columns, pull them all from the DP result”. Legacy alias: "$results".
  • "$data.<field>": pull one value from the current row, used in a column map or in data.html.sourceMap. Single level only.

data.listing

Dynamic columns

Pull every column the data processor returns:

{ "schemaVersion": 1, "template": { "name": "PHPReactionReport/Widget/ListingTableTemplate", "version": 1, }, "display": { "title": "Products", "colSpan": 12 }, "data": { "listing": { "columns": "$data", "headers": "$data" } }, }

Explicit columns

Each column is a field name (shorthand) or a { name, map, header, type } def. map pulls the value with $data.<field> and defaults to $data.<name>. A column def accepts only those four keys.

"data": { "listing": { "columns": [ "market", // = { name: "market", map: "$data.market" } { "name": "amt", "map": "$data.amount", "header": "Amount", "type": { "type": "money", "currencyField": "currency" } } ], "headers": { "market": "Market" }, // optional; label defaults to the name "columnTypes": { "updated_at": "datetime" } } }

If a column has no header / headers entry, its own name is used as the label.

Column types

type is string | number | decimal | percent | money | date | datetime | time, or an object form for precision/currency:

"columns": [ { "name": "Inv_Amount", "map": "$data.Inv_Amount", "type": { "type": "money", "currency": "CAD" } }, { "name": "Inv_Amount_rate", "map": "$data.Inv_Amount_rate", "type": { "type": "percent", "precision": 1 } }, { "name": "updated_at", "map": "$data.updated_at", "type": "datetime" } ]

The shorthand columnTypes: { "column": "type" } map is also accepted.

Currencies are not hardcoded. money formatting is driven by GET /fee_currencies, using each currency’s symbol, position, and separators. Use { "type": "money", "currencyField": "currency" } to format each row in its own currency code; an unknown/absent code falls back to USD.

Listing extras

All of these live inside data.listing and render today (progression and nested go through ReportListingAdvanced).

"data": { "listing": { "columns": ["label", { "name": "qty", "map": "$data.qty", "type": "number" }], // backend search box above the listing; writes ?search= to the URL. // `fields` is advisory, the API decides what's searchable. "search": { "enabled": true, "fields": ["Code", "name"] }, // render-time sign flip. Prefer fixing the sign in the DP. "transforms": { "qty": "invert" } } }

Progression bar, a per-row colored bar from a numeric field, colored by ratio zone:

"data": { "listing": { "columns": ["name", { "name": "achieved", "map": "$data.achieved", "type": "number" }], "progression": { "field": "achieved", "maxField": "target", // or a constant "max": 100 "zones": [ { "upTo": 0.5, "color": "#e5484d" }, { "upTo": 0.8, "color": "#f5b800" }, { "upTo": 1.0, "color": "#30a46c" } ] } } }

Nested tree, indent rows from a parent pointer, with expand/collapse:

"data": { "listing": { "columns": "$data", "nested": { "idField": "id", "parentField": "parent_id", "labelField": "name", "defaultExpanded": false } } }

data.html

The HTML template renders markup. sourceMap is the only key, either literal markup or a $data.<field> pulled from the DP row.

{ "schemaVersion": 1, "template": { "name": "PHPReactionReport/Widget/HTMLTemplate", "version": 1 }, "display": { "title": "Inventory links", "colSpan": 6 }, "data": { "html": { "sourceMap": "<div>…</div>" } }, }
// dynamic markup from the DP (row.html) "data": { "html": { "sourceMap": "$data.html" } }

Other templates

These components are registered and render, but their data.<kind> block schema is reserved: the block is whitelisted so a descriptor authoring it passes validation, but the field-to-render wiring is a scheduled follow-up. Author them like listing (same $data tokens and column shape) until each is wired.

TemplateNameBlockPurpose
SummaryPHPReactionReport/Widget/SummaryTemplatedata.summaryKVS / title-value rollups
MeterPHPReactionReport/Widget/AuxiliaryMeterTemplatedata.meterAuxiliary meter
GaugePHPReactionReport/Widget/GaugeTemplatedata.gaugeSingle-value gauge (sum / count)
GraphPHPReactionReport/Widget/GraphTemplatedata.graphLine / bar chart

Each will follow the same { literal | "$data.<field>" } value pattern as listing and HTML.

Last updated on