Skip to main content

Model-Driven Form Definition

Schema id: model-driven-form@2

Superseded version: model-driven-form@1 (model-driven-form-v1.schema.json) stays published and registered so an already-deployed Solution is still validated against the schema it was authored to. Version 2 only adds the optional controlProps.lookupView, so every v1 definition is also a valid v2 one. Machine-readable schema: model-driven-form.schema.json

The canonical representation of a Model-Driven Apps form (Section 6.7) — the layout used to view and edit a single record. Validated identically whether it comes from the designer, an API call, an AI assistant, or a Solution import.

Shape​

FieldTypeRequiredNotes
logicalNamestringyesPublisher-prefixed, lower snake-case.
displayNamestringyesHuman-readable form name.
entitystringyesThe owning entity's logical name.
descriptionstringnoThe line shown under the title on the form's title bar.
iconstringnoThe icon shown on the form's title bar.
sectionsarrayone ofOne or more titled sections — the legacy flat layout (see below).
tabsarrayone ofThe richer tabs layout (see below).
actionsarraynoDesignable command-bar actions.

A form carries either sections or tabs, not neither. New definitions should use tabs; sections is accepted so existing forms keep working, and is treated as one tab of single-column rows.

Section (legacy sections layout)​

FieldTypeRequiredNotes
titlestringyesSection heading.
columnsintegeryesLayout columns, 1–4.
fieldsarrayyesField placements (see below).

Tab (tabs layout)​

FieldTypeRequiredNotes
idstringnoStable designer id.
titlestringyesTab title.
iconstringnoIcon shown next to the tab title.
rowsarrayyesFull-width layout rows: { id?, columns: 1–4, sections: [...] }, each section { id?, title, icon?, span?, fields, timeline? }.

Field placement​

FieldTypeRequiredNotes
fieldstringsee notesThe field's logical name (must be defined on the entity). Omitted only for a Label placement, which is static content and binds to no field.
labelstringnoLabel override; the field's display name is used if omitted.
controlstringnoA Custom Control logical name mounted in place of the native field editor. The control must be installed in the environment; if it is not, the field falls back to its native editor.
controlTypestringnoThe built-in control to render the field with (Section 6.13.5). One of Text, TextArea, Check, Toggle, Date, DateTime, Time, PersianDate, PersianDateTime, PersianTime, Lookup, Dropdown, Label. Omit to let the Player pick the default input for the field's data type. The Persian… variants render the same picker as their Gregorian counterparts but open on the Jalali calendar whatever the app language; the stored value is still a Gregorian instant. Label is static Markdown content (controlProps.labelText) with no bound field.
requiredbooleannoWhether the field is business-required on this form (Section 6.13.5) — the user must fill it before saving. A form concern, not a database constraint (every entity column is nullable); the Player enforces it in the browser and the server does not. Absent defers to the field's own IsRequired.
readOnlybooleannoWhether the field is shown read-only on this form (display, not edit). A read-only field renders as plain text with no input control. Absent is not false: absent means editable on a tab but read-only in header, so a header field is made editable with an explicit false.
controlPropsobjectnoThe chosen control's properties (see below).

Control precedence: a Custom control wins over controlType, which wins over the field data type's default. Omit both and the Player picks the default.

Required is a form rule, not a column constraint. required lives on the placement, so the same field can be required on one form and optional on another, and it is checked only in the Player when the user saves. Entity columns are always nullable at the database level (Power-Apps-style), so a record created by an import or API call is never rejected for a missing required field — the one value the server insists on is the entity's primary (name) field.

Control properties (controlProps)​

FieldTypeRead byNotes
dateFormatstringDate, DateTime, Time (and their Persian… variants)Display format. Tokens: yyyy/yy, MMMM (full month name)/MMM (short)/MM/M, dd/d, HH, mm, ss; any other characters are literal. Absent uses the calendar's default — d MMMM yyyy for Persian (e.g. 12 اردیبهشت 1405), yyyy/MM/dd for Gregorian.
trueLabelstringCheck, ToggleThe label shown for the "yes" state.
falseLabelstringCheck, ToggleThe label shown for the "no" state.
rowsintegerTextAreaVisible row count, 2–20.
lookupEntitystringLookupLogical name of the entity the lookup resolves against.
lookupViewstringLookupLogical name of the view supplying the columns shown in the lookup's results panel. Absent lets the platform resolve one — see the view schema's viewType.
lookupRowCountintegerLookupHow many candidate rows the lookup's panel offers at once (1–500). Absent uses the platform default of 10. The server clamps whatever is asked for — a picker is not a report. Added after v2 by widening in place: every definition valid without it is still valid.
lookupPrimaryFieldstringLookupPer-control override for which field is each candidate's name line. Absent uses the target table's own primary field; a name that is not a field on the target is ignored.
lookupSecondaryFieldstringLookupPer-control override for the second line under the name, overriding the field the target table nominates. Absent uses the table's nomination.
lookupImageFieldstringLookupPer-control override for which File/Image field is each candidate's picture, overriding the target table's record image. Absent uses the table's image; a non-image field is ignored.
imageSizestringImage fieldHow large an Image field's preview renders — sm (default, avatar-sized), md, or lg (a prominent picture). Only meaningful for an Image field.
labelTextstringLabelA Label control's fixed text, rendered as a small safe Markdown subset: #/##/### headings, **bold**, *italic*, `code`, - lists, [text](url) links (http/https/mailto/relative only), and {color:name}…{/color} (an allow-listed colour name or #hex). No raw HTML is ever rendered.
optionsarrayDropdownFixed options, each { value, label }, belonging to this one field. Prefer an Option Set for anything reused across fields or tables: a set is a shared, versioned component with collision-proof values, whereas these options are a private list on one form. A field whose data type is OptionSet ignores this and draws from its set.

Properties that do not apply to the chosen controlType are ignored, not rejected — so switching a field's control and switching back does not lose its configuration.

Every field referenced must be a field defined on the entity — this is enforced at save time in addition to schema validation.

Timeline section (timeline)​

A section in a tab may be a timeline instead of a group of fields: the records of other tables whose lookup points at the open record, merged and newest first — a customer's emails, calls and notes in one column. A section is one or the other: when timeline is present, fields must be an empty array.

FieldTypeRequiredNotes
sourcesarray (1–10)yesThe tables it reads, one entry each (below).
pageSizeinteger (5–100)noItems shown before Load more; 20 when absent.

Each source:

FieldTypeRequiredNotes
entitystringyesThe source table's logical name.
regardingstringyesA lookup on the source table that targets the form's table, or a multi-table lookup that lists it. The timeline shows the rows whose value is the open record.
datestringyesA Date and Time field of the source (system fields such as created_at_utc included). Items are ordered by it, newest first.
titlestringyesThe field each item is headed with.
bodystringnoA field whose opening words each item shows.
typestringnoAn option-set field that tells the source's rows apart. Each item takes its icon and colour from its option, the filter shows one chip per option, and New offers one entry per option, pre-filled.
statusstringnoA field shown beside each item's date.

On save and on import, every name is checked against the environment's tables: the source table must exist, regarding must be able to point at the form's table, date must be a date, type must be a Choice, and the rest must exist. A timeline that fails any of these is refused (modelapps.form.timeline_*) rather than drawn empty. Deleting a field a timeline uses removes it from the timeline: an optional part is dropped, and a source that loses its regarding, date or title is removed.

Example​

{
"logicalName": "par_invoice_main",
"displayName": "Main",
"entity": "par_invoice",
"description": "The invoice's main form",
"icon": "Receipt",
"tabs": [
{
"id": "tab-general",
"title": "General",
"rows": [
{
"id": "row-1",
"columns": 2,
"sections": [
{
"id": "sec-header",
"title": "Header",
"fields": [
{ "field": "par_name", "required": true },
{ "field": "par_total", "label": "Amount" },
{ "field": "par_number", "readOnly": true },
{
"field": "par_due",
"controlType": "Date",
"controlProps": { "dateFormat": "dd/MM/yyyy" }
},
{
"field": "par_paid",
"controlType": "Toggle",
"controlProps": { "trueLabel": "Paid", "falseLabel": "Unpaid" }
},
{ "field": "par_signature", "control": "par_signature_pad" }
]
}
]
}
]
}
]
}