Solution Package
Schema id: solution-package@1
Machine-readable schema: solution-package.schema.json
The canonical, plain-text representation of an exported Solution. A Solution package is the unit of environment promotion: it is produced by exporting a Solution from a source environment and consumed by importing it into a target environment. The package is self-describing — it carries both the manifest (which components the Solution contains) and the embedded canonical definitions of every Model-Driven component, so an import re-creates real entities, forms, views, and plugin steps rather than dangling references.
Validation is enforced identically for every origin: a package built by the export tool, submitted by an API call, or generated by an AI assistant is validated against this same schema before the deployment pipeline touches the target environment. A hand-written or generated file that conforms to this schema is safe to import.
Shape
A package is a JSON object with these members:
| Field | Type | Required | Notes |
|---|---|---|---|
formatVersion | integer | yes | The package format version. Currently 1. |
publisherName | string | yes | The owning publisher's display name. |
publisherPrefix | string | yes | The publisher's short lower-case prefix (e.g. par). Recreated on import if absent in the target tenant. |
solutionName | string | yes | The Solution's name. |
version | string | yes | Semantic version major.minor.patch (e.g. 1.0.0). |
description | string | null | no | Optional Solution description. |
requires | array | null | no | The solutions this one is built on: { solutionName, minVersion? }. Checked against the target environment before anything is written; a missing or older one refuses the import. |
components | array | yes | The manifest: one entry per component (type + logical name). |
modelComponents | object | yes | The embedded canonical definitions, grouped by type. |
customApps | array | null | no | Embedded Custom App manifests. Optional so packages produced before Custom Apps existed still validate; each item is additionally validated against custom-app-manifest@1 at materialization. |
webResources | array | null | no | Embedded Web Resources (Section 6.15): application files stored in the database, each carrying its content (UTF-8 text for text types, Base64 for binary). Optional so older packages still validate; each item's metadata is re-validated against web-resource-manifest@1 and its content hash-checked at materialization. |
customControls | array | null | no | Embedded Custom Controls (§28), each carrying its whole build archive Base64-encoded, so a control's code moves with the solution rather than being fetched from an external host. Each item's manifest is additionally validated against custom-control-manifest@1 at materialization, and its archive checked against the declared contentHash — the point at which the code that was reviewed and the code that runs are shown to be the same. |
Manifest component (components[])
| Field | Type | Required | Notes |
|---|---|---|---|
componentType | string | yes | One of Entity, Form, View, App, CustomApp, CustomControl, Process, Report, WebResource, PolicyDefinition, SecurityRole, SavedQuery, Dashboard, BusinessRule, Script. (App is a Model-Driven App's site map; CustomApp is a hand-written React app.) |
logicalName | string | yes | Publisher-prefixed; lower-case, allowing _, ., /, - for path-like Web Resource names (e.g. par_invoice, par_/dashboard/app.js). |
componentReferenceId | string | null | no | The component's own id in its subsystem, if any. |
Embedded definitions (modelComponents)
An object with arrays of component-definition entries — entities, forms, views,
pluginSteps, and the optional apps (Model-Driven Apps). Ordering carries dependency
intent: entities are materialized first, then forms/views/plugin steps, then apps (whose
navigation references entities by logical name). apps is optional so a package produced
before apps travelled still validates and imports.
seedData (optional) is the one list whose entries are rows rather than definitions:
one seed-data@1 document per table — the table's logical name in both the logicalName and
entity slots — holding the rows the table's Seed Data view returned on export. An import
materializes it last of all and upserts each row by its id through the ordinary record write
path, writing only the columns the row carries and never deleting; a row the target refuses is
skipped and named in the import's status detail. See
Ship seed data with a solution.
| Field | Type | Required | Notes |
|---|---|---|---|
logicalName | string | yes | The component's logical name. |
entity | string | yes | The owning entity's logical name (the entity's own name, for an entity). |
json | string | yes | The component's canonical JSON definition (an entity definition, form, view, or plugin-step wrapper — each validated against its own schema at materialization). |
isDefault | boolean | no | For forms/views: whether this is the entity's default. |
One validation path. The
jsonpayload of each embedded component is not re-validated by this package schema; instead each is validated against its own published schema (for example Model-Driven Entity Definition, Form, View, Plugin Step) as it is materialized — the same check a designer save performs.
Example
{
"formatVersion": 1,
"publisherName": "Parsware",
"publisherPrefix": "par",
"solutionName": "Sales",
"version": "1.0.0",
"description": "Core sales entities.",
"components": [
{ "componentType": "Entity", "logicalName": "par_invoice" },
{ "componentType": "Form", "logicalName": "par_invoice_main" }
],
"modelComponents": {
"entities": [
{ "logicalName": "par_invoice", "entity": "par_invoice", "json": "{ ...entity definition... }" }
],
"forms": [
{ "logicalName": "par_invoice_main", "entity": "par_invoice", "json": "{ ...form definition... }", "isDefault": true }
],
"views": [],
"pluginSteps": []
}
}
How it is used
- Export reads the manifest and the real definitions from a source environment,
producing a package at
GET /solutions/{id}/export?sourceEnvironmentId=…. - Import validates the package against this schema, resolves dependencies (every
form/view/plugin-step must target an entity bundled in the package), passes any
approval gate, then materializes the components into the target environment's
isolated schema — at
POST /solution-imports.
Versioning
Older schema versions are kept alongside new ones so already-deployed Solutions keep
validating after the schema evolves. A breaking change is published as a new version
(solution-package@2) rather than editing this one; a package carries its own
formatVersion so the pipeline can reject a version it does not support.