Skip to main content

Model-Driven App Definition

Schema id: model-driven-app@1 Machine-readable schema: model-driven-app.schema.json

The canonical representation of a Model-Driven App (Section 6.7) — a named app that groups a curated subset of the environment's tables, dashboards and reports into a navigation (site map) with a home page, the way a Power Apps model-driven app does. 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 (for example, par_sales_app).
displayNamestringyesHuman-readable app name shown on the launcher tile.
publisherPrefixstringyesThe owning publisher's prefix; the logical name must start with it.
descriptionstringnoShort description shown on the launcher tile.
iconstringnoIcon key for the launcher tile and the app's rail header.
supportsTasksbooleannoWhether this app takes part in business processes. With it on, the Shell offers the task list and the Decide button while the app is open; with it off it draws neither. Off is the default, because most apps are not part of a process and an inbox on one of those is chrome that never has anything in it.
homedestinationyesWhat opens at /apps/{id}; must appear in navigation. See below.
navigationarrayyesOne or more navigation groups (the site map; see below).
includesarraynoThe app's components: things it can open that are not on its site map. See below.

Destination​

Somewhere the app can go — one shape, used for a navigation item and for home. That is deliberate rather than incidental: "home must be somewhere the navigation goes" is then a comparison of two values of the same type, instead of three near-identical checks that could drift apart one kind at a time.

FieldTypeRequiredNotes
typeEntity | Dashboard | ReportyesThe three things the Player can put on a screen: a grid, a dashboard host, a report viewer.
targetstringyesThe destination's logical name. Which store it must exist in is decided by type.
FieldTypeRequiredNotes
titlestringyesGroup heading shown in the app's left navigation.
itemsarrayyesOne or more navigation items (see below).

A destination, plus how this app wants it labelled. The label and icon overrides apply to every kind — an app that calls the Accounts table "Customers" has the same reason to call a dashboard "Today".

FieldTypeRequiredNotes
typeEntity | Dashboard | ReportyesWhat this item opens.
targetstringyesIts logical name (must exist in the environment, as that kind of thing).
displayNamestringnoLabel override for this app's rail; the component's own display name is used if omitted.
iconstringnoIcon key for the rail item.
formsarray of stringnoEntity items only. The forms of this table this app offers, by logical name. See below.
viewsarray of stringnoEntity items only. The views of this table this app offers, by logical name.

Every destination referenced — in a navigation item and in home — must exist in the environment in the store its own kind names, and home must match one of the navigation items by both type and target. Both are enforced at save time in addition to schema validation (modelapps.app.unknown_entity, modelapps.app.unknown_dashboard, modelapps.app.unknown_report, modelapps.app.home_not_in_navigation).

forms and views on a Dashboard or Report item are refused, not ignored (modelapps.app.component_selection_not_allowed): neither has forms or views, so a selection there is an authoring mistake that would otherwise vanish silently and leave its author believing they had narrowed something.

Who may open one​

Opening a dashboard and running a report are grants on that component — permissions named <logicalName>.Open and <logicalName>.Run, held by a security role like any other, with *.Open / *.Run as the wildcards that keep covering whatever is built next. Placing one in a site map does not grant it. What the dashboard's widgets and the report's datasets then read is authorized in its own right regardless, so a user who reaches a dashboard they have no data for sees empty widgets rather than somebody else's numbers.

Component selection — forms and views​

A table accumulates forms and views over its life: an admin form beside the one a salesperson uses, a "My open items" view that matters to one team. Every app showing every one of them is how a clean table becomes a cluttered app, so a navigation item may name the ones its app offers.

Absence and an empty array are not the same thing, and neither is "everything ticked".

ValueMeaning
key absentEvery form/view the entity has, including ones added later. The default for a table just placed in an app, and what every app authored before this existed means.
a listA closed choice. A form added to the entity afterwards does not appear in this app until it is listed.
[]Invalid. It cannot mean "all" (absence does) and must not mean "none" — a table with no form cannot be opened.

Each listed name must be a form/view of that entity, checked at save time (modelapps.app.unknown_component). A mistyped name is worth failing over rather than ignoring: it would otherwise silently narrow the app to fewer forms than the author chose, and the app would look correct until a user met a table they could not open.

The list decides membership only, never order — the runtime keeps the entity's own ordering and its own default, where that default survives the selection. Ordering a table's views within an app is a separate capability and is not this one.

Included page — includes​

The navigation says what an app shows; includes says what it contains, and an app contains more than it shows. A process opens a form of a table nobody browses; a designed command opens a dashboard deliberately kept off the rail; a report is reached from a record and never from the navigation. Without this list every such destination would be unreachable, or every app would have to show everything it might ever open.

FieldTypeRequiredNotes
typeEntity | Dashboard | Report | StudioAppyesWhat kind of thing this is.
targetstringyesIts logical name.
formsarray | nullnoTables only. The forms of this table the app may open, by logical name — the same rule as a navigation item's.
viewsarray | nullnoTables only. The views of this table the app may open. Same rule as forms.

forms and views follow the rule in Component selection exactly: absent means every one, a list is a closed choice, and an empty array is invalid.

Example​

{
"logicalName": "par_sales_app",
"displayName": "Sales",
"publisherPrefix": "par",
"description": "Manage accounts and invoices",
"icon": "briefcase",
"home": { "type": "Dashboard", "target": "par_sales_overview" },
"navigation": [
{
"title": "Sales",
"items": [
{ "type": "Dashboard", "target": "par_sales_overview", "displayName": "Overview", "icon": "gauge" },
{ "type": "Entity", "target": "par_account", "displayName": "Accounts", "icon": "users" },
{
"type": "Entity",
"target": "par_invoice",
"forms": ["par_invoice_main"],
"views": ["par_invoice_active", "par_invoice_overdue"]
},
{ "type": "Entity", "target": "par_contact" },
{ "type": "Report", "target": "par_invoice_print", "displayName": "Print invoices" }
]
}
]
}

This app opens on its dashboard rather than on a grid of rows, which is what a home that is not a table is for. par_invoice is curated — the app offers one of its forms and two of its views, and a view added to the table tomorrow will not appear here. par_account and par_contact name no selection, so the app offers everything those tables have, now and later.