The Form Engine: JSON Schemas, ZIP Packages, and Five Scopes of Inspection Forms
#forms#feature#architecture#building-in-public#aimqc#devlog#quality-control
David OlssonQC forms are not generic. A transmittal form, a daily report, an NCR, and a calibration record look nothing alike. Different fields. Different signature blocks. Different required vs. optional rules. Client A's NCR form is not the same as Client B's — and both are different from the industry default. We built a form engine that handles all of this without hardcoding any of it.
The problem with hardcoded forms
The obvious approach to QC forms is to hardcode them: build a React component for each form type, wire it to a database model, done. This works for the first three form types and breaks when a client needs a different layout, an additional field, or a custom signature block.
In construction QC, form templates are part of the quality plan. Clients often have approved templates that cannot be changed without an approval process. The software has to accommodate the client's form, not force the client to use the software's form.
JSON-schema-driven templates
Every form in AIMQC is defined by a FormTemplate record with a formDefinition JSON field. The field schema describes:
- Field list: field ID, label, type (text, number, date, select, signature, checkbox), and required status
- Section groupings: how fields are organized visually
- Validation rules: required fields, format constraints, conditional requirements
- Signature block configuration: which parties sign, in what order, whether witness signature is required
- Theme: client branding, logo placement, header format
{
"formId": "QF-005",
"title": "Non-Conformance Report",
"sections": [
{
"id": "header",
"fields": [
{ "id": "ncrNo", "label": "NCR No.", "type": "text", "required": true },
{ "id": "dateRaised", "label": "Date Raised", "type": "date", "required": true },
{ "id": "discipline", "label": "Discipline", "type": "select",
"options": ["MECH", "PIPE", "ELEC", "INST", "WELD"], "required": true }
]
},
{
"id": "description",
"fields": [
{ "id": "description", "label": "Description of Non-Conformance",
"type": "textarea", "required": true },
{ "id": "codeReference", "label": "Code / Spec Reference", "type": "text" }
]
}
],
"signatures": [
{ "role": "raisedBy", "label": "Raised By", "required": true },
{ "role": "reviewedBy", "label": "QC Manager Review", "required": true },
{ "role": "clientAcceptance", "label": "Client Acceptance", "required": false }
]
}
The renderer reads the schema and generates the form dynamically. Adding a field means updating the JSON. No code deployment required.
ZIP package upload
Organizations upload form packages as ZIP archives. A package contains:
index.json— the manifest listing all forms in the package- One JSON schema file per form type
- Optional CSS or theme overrides
- Optional logo and branding assets
We ship two reference packages in the codebase — mccool-forms and sundown-forms — covering the standard QC form set (QF-001 through QF-020): transmittals, RFIs, daily reports, NCRs, CAPAs, material inspection records, calibration logs, and more. These can be used as-is or overridden by client packages.
Five scopes
A form instance can be attached to five different parent record types:
| Scope | Model | Example |
|---|---|---|
| Project | ProjectForm | Project-level quality register |
| Turnover Package | TurnoverPackageForm | Package-level checklist |
| ITP | ITPForm | Pre-inspection checklist for an ITP |
| ITR | ITRForm | Inspection record form |
| NCR | NCRForm | Non-conformance documentation |
The same FormTemplate can be instantiated at any scope. An NCR form template generates an NCRForm instance when linked to an NCR record. The formData JSON field on the instance holds the filled-in values.
Signature capture
Signature fields in a form definition render as touch/mouse signature capture widgets. Captured signatures are stored as JSON blobs (base64 SVG or PNG) on the form instance. For high-volume deployments, the storage recommendation is to persist signature images to file storage and keep only a reference URL in the field.
Why this matters operationally
The form engine is what allows AIMQC to onboard a new client with a custom form set without a code change. The development cost of supporting a new client template is a JSON file and a ZIP upload. The forms work the first day on the new project.
David Olsson is CTO at AIMQC. Contact: dolsson@aimqc.com