This page describes the JSON schema that administrators write to define a form used by the Using FormPlugin.
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.
| 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. |
| 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. |
| 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. |
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.
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.
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.
{
"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" }
]
}
name values become the keys in the submitted data record. Changing a field's name after the form goes live may break existing submission records.prefill property is purely server-side. The user's data is never exposed via a client-readable API endpoint.