Form Definition Reference
This page describes the JSON schema that administrators write to define a form used by the Using FormPlugin.
Description
Each form is a JSON file stored on the server in the forms data directory. The file name (without .json) becomes the form's id used in the [{Form id='...'}] plugin invocation. Administrators create and edit these files directly; there is no browser-based form builder yet.
A form definition controls which fields appear, how they are validated, whether the submitter can enter data on behalf of someone else, and what happens after the form is submitted.
Top-Level Properties
| Property | Type | Default | Description |
|---|---|---|---|
id | string | *(required)* | Lowercase alphanumeric with hyphens. Must match the filename (without .json). |
title | string | *(required)* | Display title shown at the top of the rendered form. |
description | string | *(none)* | Optional subtitle or instructions shown below the title. |
handler | string | *(none)* | Identifier of a registered server-side handler (e.g. "clubhouse-reservation"). If omitted, submissions are stored only. |
proxySubmission | boolean | false | When true, adds a collapsible **For Another Occupant** section so a staff member can submit on behalf of a resident. |
notifyRole | string | "admin" | Role that receives in-app notifications for new submissions. |
confirmationUrl | string | *(none)* | URL appended to the confirmation email sent to the submitter (e.g. "/view/makeareservation"). |
fields | array | *(required)* | Ordered list of field definitions. At least one field is required. |
Field Properties
| Property | Type | Default | Description |
|---|---|---|---|
name | string | *(required)* | Machine name used in the submitted data. Must be unique within the form. |
type | string | *(required)* | Field type — see Field Types below. |
label | string | *(required)* | Human-readable label shown above the field. |
required | boolean | false | When true, the field must be non-empty to submit. |
description | string | *(none)* | Optional helper text shown below the field. |
placeholder | string | *(none)* | Placeholder text shown inside the input before the user types. |
options | array of strings | *(none)* | Choices for dropdown fields when the list is defined inline. |
optionsSource | string | *(none)* | Dynamic option source — currently supports config:some.key to read a list from configuration. |
prefill | string | *(none)* | Dot-path into the logged-in user's profile that pre-populates the field. See Prefill Paths below. |
Field Types
| Type | Renders as | Notes |
|---|---|---|
text | Single-line text input | General purpose. |
email | Email input | Validated as a proper email address on submission. |
tel | Phone number input | Must be at least 7 characters when required. |
textarea | Multi-line text area | 4 rows. |
date | Date picker | Browser native date input. |
time | Time picker | Browser native time input. If the form also has startTime and endTime fields, the server enforces that end time is after start time. |
dropdown | Select menu | Requires options or optionsSource. |
checkbox | Checkbox | Accepted values on submission: on, true, or 1. |
hidden | Not rendered | Contributes no data to the submission. |
section | <fieldset> group header | Visually groups the fields that follow it into a labelled fieldset. Contributes no data to the submission. |
Section Field Type
A field with "type": "section" inserts a Bootstrap-styled <fieldset>/<legend> that groups all the fields listed after it (up to the next section or the end of the list). Only the name and label properties are used; all others are ignored.
Example — splitting a reservation form into two groups:
{ "name": "details", "type": "section", "label": "Reservation Details" },
{ "name": "date", "type": "date", "label": "Date", "required": true },
{ "name": "contact", "type": "section", "label": "Who Is the Reservation For?" },
{ "name": "name", "type": "text", "label": "Full Name", "required": true }
Fields before the first section marker are rendered without a fieldset wrapper.
Prefill Paths
When a logged-in user loads a page containing the form, any field with a prefill property is pre-populated with data from the user's profile. The value is always editable — prefill sets the default, not a locked value. Anonymous users see the field empty.
| Path | Source |
|---|---|
user.displayName | User's display name |
user.firstName | User's first name |
user.lastName | User's last name |
user.email | User's email address |
user.cellPhone | User's cell phone |
user.homePhone | User's home phone |
user.unit.address | Street address of the unit linked to the user's parcel number |
The user.unit.* paths require the user's account to have a parcel value that matches an entry in the units data file. If no match is found, or if the user has no parcel, the field renders empty.
Proxy Submission (For Another Occupant)
When "proxySubmission": true, the rendered form appends a For Another Occupant fieldset after the regular fields. This section is optional — if left blank, the form submits normally on behalf of the logged-in user.
If any field in the proxy section is filled in, the Full Name field in that section becomes required. The server validates this and returns an error if name is missing while other proxy fields are filled.
Example Form Definition
{
"id": "clubhouse-reservation",
"title": "Clubhouse Reservation Request",
"description": "Reserve the clubhouse for a private event. Subject to availability and board approval.",
"handler": "clubhouse-reservation",
"confirmationUrl": "/view/makeareservation",
"proxySubmission": true,
"notifyRole": "clubhouse-manager",
"fields": [
{ "name": "details-section", "type": "section", "label": "Reservation Details" },
{ "name": "date", "type": "date", "label": "Date", "required": true },
{ "name": "startTime", "type": "time", "label": "Start Time", "required": true },
{ "name": "endTime", "type": "time", "label": "End Time", "required": true },
{ "name": "description", "type": "textarea", "label": "Event Description" },
{ "name": "address", "type": "text", "label": "Unit Address", "prefill": "user.unit.address" },
{ "name": "who-section", "type": "section", "label": "Who Is the Reservation For?" },
{ "name": "name", "type": "text", "label": "Full Name", "required": true, "prefill": "user.displayName" },
{ "name": "email", "type": "email", "label": "Email", "prefill": "user.email" },
{ "name": "phone", "type": "tel", "label": "Phone", "prefill": "user.cellPhone" }
]
}
Notes
- The form definition is validated when the server starts. If a definition file has errors (missing required properties, invalid field type), it is skipped and a warning is logged — other forms are unaffected.
- Field
namevalues become the keys in the submitted data record. Changing a field'snameafter the form goes live may break existing submission records. - The
prefillproperty is purely server-side. The user's data is never exposed via a client-readable API endpoint. - Submitted values are always used as-is, even if they differ from the prefill — the user may edit the pre-filled value before submitting.
- For the full plugin invocation syntax, see Using FormPlugin.
No comments yet.