Building a record type's field schema¶
An internal-form record type collects structured data — a cleaning log, a visitor register, a calibration entry. What fields it collects is described by a small piece of JSON called the field schema. This page is the complete guide to writing one, with a worked example for every field type.
You don't need to be a programmer. A field schema is a form described as a list of fields, and the editor on the record-type page checks it for you as you type — press Test schema (or just keep typing) to see the exact form your schema produces, side by side.
Let the AI assistant write it for you
You can describe the form in plain English to the QStack AI assistant and have it build the record type for you — see Generate a record type from a description at the end. This page still matters: it's how you read, check and tweak what the assistant produced.
The skeleton¶
Every field schema has the same outer shape:
"type": "object"— always this. A record is one filled-in form."properties"— the fields, one entry each. This is where your work goes."required"— the names of the fields a user must fill before submitting. Optional; leave it as[]if nothing is mandatory.
A field goes inside properties as "field_name": { …how it behaves… }:
{
"type": "object",
"properties": {
"equipment_id": { "type": "string", "title": "Equipment ID" }
},
"required": ["equipment_id"]
}
The field name (equipment_id) is the internal key — lowercase, no spaces,
use underscores. The title is the human label shown on the form. Always give
a title; without one QStack falls back to a tidied-up version of the name.
The field types¶
QStack deliberately supports a small, dependable set of field types. Here is every one, with the JSON that produces it.
| You want | Use |
|---|---|
| A short text box | {"type": "string"} |
| A long text area | {"type": "string", "format": "textarea"} |
| A date picker | {"type": "string", "format": "date"} |
| A dropdown (pick one) | {"type": "string", "enum": ["A", "B", "C"]} |
| A whole number | {"type": "integer"} |
| A decimal number | {"type": "number"} |
| A yes/no checkbox | {"type": "boolean"} |
| A value the system fills in | {"type": "string", "x-server": "today"} (see below) |
Text field¶
Cap the length with maxLength:
Long text (textarea)¶
Date¶
Renders as a native date picker (<input type="date">):
Dropdown (choose one)¶
List the allowed choices in enum. The stored value is exactly the text you
list:
Whole number and decimal¶
"page_number": { "type": "integer", "title": "Page number" }
"ph_reading": { "type": "number", "title": "pH reading" }
Bound them with minimum / maximum:
Yes/no checkbox¶
A boolean listed in required must be ticked to submit — use that for an
attestation checkbox. An optional boolean is just a normal checkbox.
Labels and help text¶
titleis the field's label.descriptionadds a line of help text under the field.
"cleaning_agent": {
"type": "string",
"title": "Cleaning agent",
"description": "The detergent or solvent used, e.g. 70% IPA."
}
Making fields mandatory¶
List the field names (not their titles) in required:
{
"type": "object",
"properties": {
"equipment_id": { "type": "string", "title": "Equipment ID" },
"cleaned_on": { "type": "string", "format": "date", "title": "Cleaning date" },
"cleaning_agent": { "type": "string", "title": "Cleaning agent" }
},
"required": ["equipment_id", "cleaned_on"]
}
Here the agent is optional; the equipment and date are not.
Field order¶
By default fields appear in the order you wrote them. To fix a specific order (handy after editing), set the Field order box — a JSON list of the field names:
Any field you leave out of the list still appears, after the ones you named.
Fields the system fills in¶
Some values shouldn't be typed by the user — the date the record was made, or
who made it. Mark those with x-server. They render disabled, and QStack
fills them at submission time, so they can't be faked:
x-server value |
QStack fills in |
|---|---|
"today" |
The date the record is created |
"person" |
The originator's name |
"performed_by": { "type": "string", "title": "Performed by", "x-server": "person" }
"logged_on": { "type": "string", "title": "Logged on", "x-server": "today" }
Never put an x-server field in required — the user can't fill it, so QStack
supplies it for you regardless.
A complete worked example¶
An Equipment Cleaning Log, end to end:
{
"type": "object",
"properties": {
"equipment_id": { "type": "string", "title": "Equipment ID", "maxLength": 50 },
"cleaned_on": { "type": "string", "format": "date", "title": "Cleaning date" },
"cleaning_agent": { "type": "string", "title": "Cleaning agent" },
"within_spec": { "type": "boolean", "title": "Visually clean and dry" },
"performed_by": { "type": "string", "title": "Performed by", "x-server": "person" },
"comments": { "type": "string", "format": "textarea", "title": "Comments" }
},
"required": ["equipment_id", "cleaned_on", "cleaning_agent"]
}
Field order:
Paste that into the editor, press Test schema, and you'll see the cleaning log render on the right.
Testing your schema¶
The editor validates continuously. When something is off you'll get a plain message pointing at the problem — a missing comma, an unknown field type, a misplaced bracket. Common ones:
- "not valid JSON" — usually a missing comma between fields, or a trailing
comma after the last one. JSON does not allow a comma before a closing
}. - "Unsupported field type" — you used a
typeoutside the supported set above (for example"array"). Pick one of the supported types. - "Add at least one field" —
propertiesis empty. A record type needs at least one field before it can be published.
Nothing is saved until you press Save, and no version exists until you Publish — so experiment freely.
What a field schema can't do¶
Keeping the set small keeps records dependable and searchable. Not supported:
- Repeating rows / tables (a variable-length list of line items). For now, either add a fixed set of fields, or capture the tabular evidence as an external upload.
- Nested sections (an object inside a field). Keep fields flat.
- Conditional fields (show B only if A is ticked). Every field always shows.
- References between schemas (
$ref,allOf). Write each type out in full.
If you need one of these, capture the evidence as an external-document record type instead, or talk to your QStack administrator.
Versioning — why editing is safe¶
Editing a field schema only touches the draft. Records already in flight keep the exact schema version they were created under and render against it forever. Your edit becomes a new immutable version only when you Publish (or when the governing Form document becomes Effective). So you can correct a schema without disturbing a single existing record. See Quality Records for the full versioning rules.
Generate a record type from a description¶
If you use the QStack AI assistant (or another MCP client — see API & AI-tool access), you can describe the form in words:
"Create an internal record type called Fridge Temperature Log, code FRIDGE, with fields: fridge ID, date (auto), temperature in °C, within-range yes/no, and a comments box. Temperature and fridge ID are required."
The assistant turns that into a field schema using exactly the types on this
page, checks it, and creates the record type (via the create_record_type
tool). You then open it here to review, tweak the schema, and publish — the
review-and-publish step stays with a human.