Skip to main content

Model-Driven Entity Definition

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

The canonical, plain-text representation of a Model-Driven Apps entity. This is the source of truth an entity is validated against — the same definition the designer produces, an API call submits, an AI assistant generates, and a Solution import carries. Validation is enforced identically for every origin, so a hand-written or generated file that conforms to this schema is safe to import.

Shape​

A definition is a JSON object with these members:

FieldTypeRequiredNotes
logicalNamestringyesPublisher-prefixed, lower snake-case (e.g. par_invoice).
displayNamestringyesSingular label (e.g. Invoice).
displayCollectionNamestringyesPlural label (e.g. Invoices).
iconstringnoIcon key from the platform's curated icon set, shown next to the table in an app's navigation (§4.4). Omit for the default glyph.
publisherPrefixstringyesShort lower-case prefix (e.g. par).
primaryFieldstringyesLogical name of the primary (name) field; must appear in fields.
primaryImagestringnoLogical name of the record's image (§6.16) — the picture shown beside a record in grids and lookups. Must name a field of type Image in fields. Omit for a table with no picture.
secondaryFieldstring | nullnoLogical name of the field shown as the record's secondary text — the smaller second line under its name wherever records are listed without a grid, such as a lookup panel (§6.13.5). Must name a field in fields that is neither the primary field nor a File/Image column.
hierarchyParentFieldstring | nullnoLogical name of the Lookup that makes this table a hierarchy — the field holding each record's parent, pointing back at this same table (a category's parent category, a unit's parent unit). Must name a field in fields of type Lookup whose targetEntity is this table. It grants nothing and forces nothing: the table still renders as a grid until a view nests its records. What it is for is that being a hierarchy is a property of the data rather than of one view — a tree view's top level defaults its child link to it.
personFieldstringnullno
constraintsarraynoRules the database enforces, not the application (see below) — a unique column, or a period that may not overlap another in the same scope.
fieldsarrayyesOne or more field definitions (see below).
relationshipsarraynoOne-to-Many relationships backed by lookup fields.
isAuditEnabledbooleannoEnables change auditing for the entity (Section 6.5). Defaults to false.
statesarraynoThe States a record of this table can be in (§6.14) — its lifecycle. See below. Absent in a document written before states existed, which is how an importer tells that apart from a table with none.
reasonsarraynoThe Reasons, each belonging to exactly one State. See below.
statusValueBaseintegernoThe floor of the range this table's added State and Reason options are numbered from — the owning publisher's option-value base (§6.14). The seeded Active/Inactive sit outside it at 0 and 1: every table has them and they mean the same thing everywhere, so they need no namespace.
nextStatusValueintegernoThe table's status high-water mark — the next value it will hand out. Stored rather than derived from the options, for the same reason an option set's is: a removed option's number is retired, and re-deriving would hand it to the next new one while records may still hold it.

Field​

FieldTypeRequiredNotes
logicalNamestringyesLower snake-case.
displayNamestringyes
dataTypestringyesOne of Text, MultilineText, Integer, Decimal, Boolean, DateTime, Lookup, PolymorphicLookup, File, Image, OptionSet, State, Reason.
isRequiredbooleanno
isPrimarybooleannoTrue for the single primary field.
maxLengthintegernoFor Text (1–4000).
precision, scaleintegernoFor Decimal (scale ≤ precision ≤ 38).
targetEntitystringconditionalRequired when dataType is Lookup; the referenced entity's logical name.
targetEntitiesstring[]conditionalRequired when dataType is PolymorphicLookup; the tables a value may point at, in picker order. At least two, none repeated.
isAuditExcludedbooleannoExcludes the field from change auditing when the entity is audited (Section 6.5). Defaults to false.

PolymorphicLookup fields are provisioned as two columns: the field's own uuid column holding the record's id, and a sibling <column>_type text column holding the target table's logical name. Neither carries a foreign key — a single column cannot reference several tables — so integrity is the write path's: it checks the target exists, and a target deleted afterwards reads as an unresolved reference.

On the wire the two halves travel as one scalar, <table>:<id>, so a record's values stay a flat map of field logical name to scalar and the discriminator column never surfaces as a field. That is the platform's existing convention for a self-describing reference (recordfile:<id>, webresource:par_/app.js).

The table's logical name is stored rather than a number, because a numeric discriminator would index into targetEntities — so reordering the targets in the designer would silently re-point every stored row at a different table. A record's owner columns use a numeric enum instead, which is consistent rather than contradictory: their five values are platform concepts fixed in code, not a list a maker edits.

Each entry in targetEntities gets its own backing relationship, which is what makes the referenced table's form offer a Related tab for these records.

File / Image fields are provisioned as a text column holding a scheme-qualified reference — recordfile:<id> for a file kept in the environment's own schema, or objectstore:<key> for one large enough to have spilled to Object Storage. The bytes are uploaded to the field's own file endpoint rather than written through the record API, and are served under the record's own authorization. Which of the two stores holds a file is decided by its size and by nothing a maker sets.

Relationship​

FieldTypeRequiredNotes
logicalNamestringyesLower snake-case.
typestringyesOneToMany.
referencedEntitystringyesThe "one" side.
referencingEntitystringyesThe "many" side (carries the lookup).
lookupFieldstringyesThe lookup field on the referencing entity that backs the relationship.

State and Reason​

A State is where a record is in its lifecycle; a Reason says which flavour of that state it is in. They travel with the table because they are part of what the maker designed.

🔴 They are not an option set. Active on an Invoice and Active on a Contact are not the same choice, and modelling them as one shared list would make renaming one rename the other.

State:

FieldTypeRequiredNotes
valueintegeryesThe stored value, preserved across an import: the source environment's records already hold it, so Dev and Test must agree about what it means.
displayNamestringyesThe label.
colorstringnoThe colour its badge is drawn in.
iconstringnoThe glyph on its badge.
defaultReasonintegernoWhich of this state's reasons a record entering it gets. Absent means the first one, which is what makes the setting optional.

Reason:

FieldTypeRequiredNotes
stateintegeryesThe value of the State this reason belongs to. Keyed by value rather than by row id, because a value is what a record stores and what survives an export.
valueintegeryesThe stored value.
displayNamestringyesThe label.
color · iconstringnoAs for a State.

The containment is the point: Won and Lost are both reasons a deal is Inactive, and offering them while it is Active would be nonsense.

Example​

{
"logicalName": "par_invoice",
"displayName": "Invoice",
"displayCollectionName": "Invoices",
"publisherPrefix": "par",
"primaryField": "par_name",
"fields": [
{ "logicalName": "par_name", "displayName": "Name", "dataType": "Text", "isPrimary": true, "isRequired": true, "maxLength": 200 },
{ "logicalName": "par_total", "displayName": "Total", "dataType": "Decimal", "precision": 18, "scale": 2 },
{ "logicalName": "par_account", "displayName": "Account", "dataType": "Lookup", "targetEntity": "par_account" }
],
"relationships": [
{ "logicalName": "par_account_invoices", "type": "OneToMany", "referencedEntity": "par_account", "referencingEntity": "par_invoice", "lookupField": "par_account" }
]
}

Versioning​

Older schema versions are kept alongside new ones so already-deployed solutions keep validating after the schema evolves. When a breaking change is needed, a new version (model-driven-entity@2) is published rather than editing this one.