Skip to main content

Model-Driven View Definition

Schema id: model-driven-view@2 Machine-readable schema: model-driven-view.schema.json

Superseded version: model-driven-view@1 (model-driven-view-v1.schema.json) stays published and registered, so a Solution already deployed against it is still validated against the schema it was authored to. Version 2 only ever adds optional properties — the Shape table below is the current list — so every v1 definition is also a valid v2 one and nothing has to be rewritten.

The canonical representation of a Model-Driven Apps view (Section 6.7) — a tabular list of records with its columns, filters, and sorts.

Shape​

FieldTypeRequiredNotes
logicalNamestringyesPublisher-prefixed, lower snake-case.
displayNamestringyesHuman-readable view name.
entitystringyesThe owning entity's logical name.
descriptionstringnoThe line shown under the title on the view's title bar.
iconstringnoThe icon shown on the view's title bar.
viewTypestringnoWhat the view is for: Public (default), Lookup, QuickFind, Associated, QuickView, Card, SeedData.
seedDataKindstringon a SeedData viewRequired on a SeedData view and refused on any other: Configuration (rows the solution needs to work; an import always brings them, Production included) or Demonstration (sample rows; imported only when Include demonstration data is ticked, never into Production). Carried into the package as the seed-data document’s kind.
columnsarrayyesField logical names shown as columns.
filtersarraynoFlat filter clauses (ANDed): field, operator, and (for value operators) value.
filterGroupobjectnoA hierarchical AND/OR filter tree (Advanced-Find style), ANDed with filters.
sortsarraynoSort clauses: field, descending.
actionsarraynoDesignable command-bar actions.
hierarchyobjectnoPresent when the view renders as a tree rather than a grid.
calendarobjectnoPresent when the view renders as a calendar rather than a grid. Never alongside hierarchy.
columnPropertiesarraynoHow individual columns are presented — header, width, format, alignment, control. Sparse.
commandOverridesarraynoWhat this view changes about the commands the platform owns.
commandOrderarraynoOne bar, one order: built-in and maker commands interleaved, by id.
rowSelectionbooleannoWhether rows carry a selection tick box. true by default.

Operators: Equals, NotEquals, Contains, GreaterThan, LessThan, IsNull, IsNotNull. Every referenced field (columns, filters, filter-group conditions, sorts) must exist on the entity.

viewType​

A view's type says which surface reads it. One entity can carry a default view of each type at once, because they answer different questions:

ValueWhat it is for
PublicThe entity's normal list page. This is the default, and what a definition with no viewType is.
LookupThe columns shown inside a lookup control's results panel — usually two to four.
QuickFindThe find columns: the columns a typed search matches against. Its columns are the searched set; its filters and sorts are not read.
SeedDataThe rows and columns that travel with the solution (PF22). Never shown: a solution export runs it — its filters decide the rows, its columns the fields — and packs the result as a seed-data@1 document, which an import upserts by id. A table with several uses its default one, else the first by logical name.
AssociatedThe related-records subgrid. Reserved — nothing renders it yet.

A lookup control that names no view resolves one automatically: the target entity's default Lookup view, else its default Public view truncated to its first three columns, else the entity's primary field alone. A search with no QuickFind view matches the primary field only. Find columns must be text fields, since the match is a case-insensitive "contains".

calendar (a view that renders as a calendar)​

A view with a calendar block draws its records placed on a day, week or month sheet — by two of its own date fields — instead of as a grid. Absent is the grid, exactly as with hierarchy, so every stored view stays valid. A view carrying both blocks is refused: a view is drawn one way.

It is a mapping, not a data model. There is no booking table and no appointment type: any table with a date on it can be laid out on a calendar, and which of its dates means "starts" is the maker's to say.

FieldTypeRequiredNotes
startstringyesThe Date and Time field a block is placed at. The only required role: a record with no start cannot be placed on a sheet, so there is nothing to fall back to. A system field is allowed — created_at_utc lays records out by when they arrived.
endstringnoThe Date and Time field a block ends at. Absent is for a table recording a moment rather than a span, and makes every block defaultDurationMinutes long — not zero, because a block with no height cannot be clicked.
titlestringnoThe field the block's first line reads from. Absent means the table's primary field, which is what a grid, a lookup panel and a breadcrumb already show.
categorystringnoA Choice, State or Reason field whose options tint the blocks and fill the legend.
bookedForstringnoThe field naming who the slot is for, shown quietly under the title. Any type: a lookup resolves to the target record's name.
notestringnoThe field shown on a block tall enough for a third line, and in its tooltip otherwise.
defaultScalestringnoWhich sheet the view opens on: Day, Week (default) or Month.
startHourintegernoThe first hour the day and week sheets show, 0–23. 0 by default.
endHourintegernoThe hour those sheets end at, 1–24, and greater than startHour. 24 by default.
defaultDurationMinutesintegernoHow long a slot is when the reader clicks an empty one — what the new record arrives with — and the height of every block on a view mapping no end. At least 1; 60 by default.

🔴 start and end must be Date and Time fields, and category must carry colours. Both are refused on save, because both fail silently on screen: a start that is a text column draws an empty sheet, which reads as "nothing is booked this week" — the one conclusion a reader will act on and cannot check. The colour lives on the Choice's own options, which is what lets "Exam is amber" be said once and be read by the calendar, a grid cell and a chart alike.

startHour and endHour are the difference between a usable booking sheet and one that opens two-thirds empty: a clinic working 8 to 7 does not want sixteen rows of midnight. They say where the sheet opens — the reader still switches scale and narrows the hours from the sheet's own header, because the window is theirs rather than the maker's.

There is deliberately no resource role. The sheets are days-across, so a field naming the column a block belongs in would be a mapping a maker could make and then watch do nothing. It arrives with the axis that draws it.

hierarchy (a view that renders as a tree)​

A view with a hierarchy block draws its records nested by a lookup — an org chart with the people in each unit, a category tree, a bill of materials — instead of as a grid. Absent is the grid every view has always been, which is why every stored view stays valid.

What a tree is for is navigation: rows are fetched a window at a time, so a chart of fifty thousand people opens without loading any of it.

FieldTypeRequiredNotes
nodeTypesarrayyesEvery kind of node the tree can draw. At least one.
orderarraynoThe order segments under one node are drawn in, by node type id. Absent means declaration order.
rootPickerbooleannoWhether a reader may focus the tree on one node — opening on a city rather than the whole organisation.
expandRootsbooleannoWhether the first level opens on load. true by default.

Each entry in nodeTypes:

FieldTypeRequiredNotes
idstringyesWhat this type is called inside the block — unit, person. Unique here, and what order and every childOf.of refer to. The same table may appear twice in one chart, which is why this is the maker's own word rather than a table name.
entitystringyesThe table these nodes are records of.
childOfobjectyesHow this type hangs under its parent — see below.
labelstringnoThe field whose value is the node's line. Absent means the table's primary field.
secondarystringnoThe field shown quietly after the label. Absent means the table's own secondary field; an empty string means none — on a unit the parent's name is the row directly above it, so a chart is one place a table's default second line is wrong.
iconstringnoThe glyph on the row. Absent means the table's own icon.
imagebooleannoWhether the row carries the record's picture.
detailobjectnoWhat fills the pane beside the chart when one is selected. Absent means the table's Main form.

childOf takes a field — the Lookup on this type's table holding the parent record — and an optional of, the node type it hangs under by id.

🔴 Exactly one node type leaves of out. That one is the tree's own table nesting under itself, and its records with an empty parent are the chart's top level; every other type names what it hangs under. Heterogeneous nodes are the general case rather than a bolt-on: units, then the people in them is the same mechanism as province → city → branch modelled as three tables.

The lookup named by childOf.field must target the parent type's table — its own, for the root type. One pointing anywhere else does not describe a nesting, and is refused on save rather than rendering a level with nothing under it.

detail takes a kind of Form (the table's own form, saved through the record form's write path), Card (a Card view laid out to be read rather than typed into, which is what a person under a unit usually wants) or None (for a type that is only ever structure), plus an optional name for which form or Card view.

columnProperties (how a column is presented)​

A sparse list: only columns a maker configured appear, and a column with no entry renders from its field metadata alone. It sits beside columns rather than turning that into a list of objects, so every v1 definition stays valid and every reader that only needs to know which columns a view has is untouched.

FieldTypeRequiredNotes
fieldstringyesThe column this configures. Must be one of the view's columns.
labelstring | nullnoHeader override. Absent uses the field's display name.
widthinteger | nullnoWidth in pixels, 40–1200. Absent lets the column size itself.
formatstring | nullnoValue format. For a DateTime column, the same token set the form's date controls accept (yyyy, yy, MM, dd, HH, mm, ss) — one formatting vocabulary across the product.
alignstring | nullnostart, center or end. Logical, not left/right, so it follows the reading direction (§6.13.6).
controlTypestring | nullnoThe control the column renders with — a switch glyph for a boolean, a Persian date for a date.

controlType takes Text, TextArea, Check, Toggle, Date, DateTime, Time, PersianDate, PersianDateTime, PersianTime, Lookup, PolymorphicLookup, Dropdown or Label. Absent inherits the field's own default and then the data type's.

🔴 It is always rendered read-only. A grid has no inline editing, so a switch here shows state rather than taking a click. It is also the only way a column can render Jalali in a Gregorian tenant, since format is a pattern with no calendar attached.

commandOverrides (changing a command the platform owns)​

What this view changes about the built-in commands — New, Edit, Delete, Refresh, Save, Save & Close, Pin. Absent changes nothing, which is what every view authored before this existed means.

FieldTypeRequiredNotes
commandstringyesThe stable id of the command being overridden.
labelstringnoReplaces the platform's label. Absent keeps it — including its translations, which a typed override necessarily loses.
iconstringnoReplaces the platform's glyph.
initiallyHiddenbooleannoWhether the command starts hidden.
initiallyDisabledbooleannoWhether the command starts disabled.

🔴 Keyed by a stable command id, never by label or position. The id is the platform's own name and does not change when the label does, so an override cannot detach from what it overrides the moment somebody edits it — and a built-in can be renamed or retired later without every stored document needing reconciliation, since an override naming a command that no longer exists is simply ignored.

🔴 A maker may hide and disable, never show or enable. The Player hides New from somebody who cannot create and disables Delete with nothing selected; an override able to undo that would build a bar offering what the server will refuse.

"Initially" is the whole of the word. It is the state the bar opens in, not a permanent condition — a Business Rule or the command's own script may show it afterwards. A command that must never appear for somebody is not this; that is a permission, decided on the server where a browser cannot argue with it.

commandOrder (one bar, one order)​

The built-in commands and the maker's own, interleaved as the maker arranged them, by id.

An explicit list rather than a rank on each command, because a rank must be rewritten on every command whenever one moves, and two commands can hold the same one — a tie the renderer would then break differently from however the designer broke it.

🔴 Anything this list does not mention keeps its natural place, at the end. That is what makes the rule survive time: a built-in added to the platform next year, or a command an upgrade adds, appears rather than vanishing because a stored order predates it. An id naming something that no longer exists is skipped.

rowSelection (whether rows carry a tick box)​

true by default, and what every view authored before this existed means: the grid gets its select-all header and per-row boxes, which is what the command bar acts on.

false is for a view that is only ever read — a list nobody deletes or reassigns from — where a tick box offers a multi-select the surface has no use for and costs a column of width.

filterGroup (hierarchical AND/OR)​

A filterGroup is a recursive node combining its members with a connector:

FieldTypeRequiredNotes
connectorstringyesAnd (all members match) or Or (any member matches).
conditionsarraynoLeaf conditions (field, operator, value) in this group.
groupsarraynoNested sub-groups (same shape), for arbitrary AND/OR nesting.

Example​

{
"logicalName": "par_invoice_active",
"displayName": "Active Invoices",
"entity": "par_invoice",
"viewType": "Public",
"columns": ["par_name", "par_total"],
"filters": [ { "field": "par_total", "operator": "GreaterThan", "value": 0 } ],
"filterGroup": {
"connector": "And",
"conditions": [ { "field": "par_name", "operator": "Contains", "value": "INV" } ],
"groups": [
{
"connector": "Or",
"conditions": [
{ "field": "par_total", "operator": "GreaterThan", "value": 1000 },
{ "field": "par_total", "operator": "IsNull" }
]
}
]
},
"sorts": [ { "field": "par_name", "descending": false } ]
}

Example — a lookup view​

{
"logicalName": "par_contact_pick",
"displayName": "Contact Lookup",
"entity": "par_contact",
"viewType": "Lookup",
"columns": ["par_name", "par_email", "par_city"],
"sorts": [ { "field": "par_name", "descending": false } ]
}

Example — a quick find view​

Only columns is read: they are the columns a typed search matches against.

{
"logicalName": "par_contact_find",
"displayName": "Contact Quick Find",
"entity": "par_contact",
"viewType": "QuickFind",
"columns": ["par_name", "par_email", "par_accountnumber"]
}