Artifact Package Manifest
Schema id: artifact-package@1
Machine-readable schema: artifact-package.schema.json
File name inside the archive: parsware.json, at its root
What turns a folder of bundler output into something the platform can accept. It is
written by npm run pack beside the build output and zipped with it; the platform
reads it at upload, and a hand-assembled archive is refused by exactly the same rule as a
generated one (the one-validation-path principle, Section 6.7).
Why it exists
Registration used to ask a maker for six fields, two of which were copied out of a bundler config: the Module Federation scope and the exposed module. Getting either wrong produced one symptom — the control did not load — with nothing to say which of the two was wrong. The build already knows both, so it writes them down.
Shape
| Field | Type | Required | Notes |
|---|---|---|---|
formatVersion | integer | yes | Must be exactly 1. A const rather than a minimum, so an archive from a future format is refused with a clear message instead of read under today's rules and failing somewhere less obvious. |
kind | string | yes | CustomControl. Custom Apps join this enum when they adopt the package — they will use the same store, cache and serving endpoint, and differ only in this manifest's per-kind fields. |
logicalName | string | yes | Publisher-prefixed, lower snake-case (e.g. par_signature_pad). Checked against the owning solution's publisher prefix at upload, so a prefix cannot be claimed by an archive from elsewhere. |
displayName | string | yes | Human-readable name shown in the form designer's field palette. |
description | string | null | no | Short description, max 512 characters. |
version | string | yes | Three-part version (1.2.3), the control's own npm version. Recorded for diagnostics and shown to a maker checking that what is installed is what they built; it gates nothing. |
scope | string | yes | The Module Federation container scope, read from the build's federation config. |
module | string | yes | The exposed module path (e.g. ./Control), read from the federation config's exposes map. |
entry | string | yes | The container entry file inside the archive (e.g. remoteEntry.js). A bare file name, not a path: it names a file at the archive root, which is also the base path the platform serves the artifact's async chunks from. |
substitutesFor | string[] | yes for CustomControl | The field data-type families the control can be bound to. Must match custom-control-manifest@1's enum exactly. |
requestedScopes | string[] | yes for CustomApp | The OAuth scopes a Custom App declares it needs, requested when its App Context Token is minted (§6.3). Absent for a Custom Control, which declares none — it runs with the permissions of the person using it (§28). |
Where the values come from
Two sources, and neither guesses at the other's half:
scope,module,entryare read from the build's ownmf-manifest.json, because the bundler decides them.@module-federation/viteemits that file only when configured withmanifest: true, which is off by default — so every Parsware control turns it on, and the packer refuses to guess if it is missing.logicalName,displayName,description,substitutesForcome from aparswareblock in the control'spackage.json, because the bundler has never heard of any of them.versionis that file's ownversion.
What is deliberately absent
There are no declared scopes and no declared entities. An artifact runs with the permissions of the person using it (§24), so a scope list on the artifact could only ever narrow its user, never widen them.
There is no remote-entry URL. The package is served from the platform's own origin at a path computed from the environment and the logical name, which is what lets a control travel inside its solution instead of being fetched from an external host.
What the platform checks
At upload, before anything is stored:
- the archive is within the compressed size cap and is a readable zip;
- it holds no more than the configured number of files, and expands to no more than the configured total (checked while reading, and again while extracting — a zip header's declared length is a claim the archive makes about itself);
- every entry path is relative and safe —
.., a rooted path, and a Windows drive or UNC prefix are rejected rather than sanitized, because an archive naming a path outside its own folder is not one to quietly repair; - every file has an extension on the platform's serve allow-list;
parsware.jsonis present at the root and validates against this schema;- the
kindmatches the endpoint, and the file named byentryis actually present.
Related
- Build a Custom Control — writing and packing one.
- Register a Custom Control — uploading the package.
- Custom Control Manifest — the stored shape this manifest is composed into.