Special Field Types Reference
WittyForm includes several advanced field types that go beyond standard inputs. This reference covers their schema structure, configuration options, and response data format so you can integrate them into workflows, API calls, and custom logic.
Multi-Question
A Multi-Question field (shown in the builder as a Multi-Question Page) displays several questions together on a single screen. Under the hood it stores a list of question ids that reference other top-level fields, so each grouped question is a normal field and the respondent sees them as one block (for example "First Name" and "Last Name" side by side).
Schema Structure
{
"type": "multi_question",
"id": "field_mq_01",
"label": "Full Name",
"layout": "grid", // "vertical" | "grid"
"columnsCount": 2, // columns per row when layout is "grid"
"questionIds": [ // ids of existing fields to show together
"field_first_name",
"field_last_name"
]
}Referenced Questions
The fields listed in questionIds are ordinary top-level form fields. In the form builder you add and edit them inside the Multi-Question block; in the saved schema they appear as normal fields referenced by id. You configure each one (type, label, placeholder, required, validation) exactly like a standalone question, and it is rendered only inside the group, never as a separate step.
Response Data Format
Grouping is presentational only. Each referenced question stores its answer at the top level under its own field id, the same as a standalone question:
{
"field_first_name": "John",
"field_last_name": "Doe"
}Auto-Save Progress
Long forms automatically save the respondent's answers as they type, so an accidental refresh, tab crash, or brief navigation away does not wipe their progress. This works in the same browser on the same device.
How It Works
As the respondent fills in each field, WittyForm writes the current answers to the browser's localStorage. If they reload the page or come back in the same browser, the form rehydrates from that local copy and they continue where they left off.
- Same device only: the saved answers live in that browser's local storage. There is no local fixed expiry; it stays until successful submit or until the browser data is cleared. For cross-device resume links, enable Save & Resume.
- Cleared on submit: once the form is submitted successfully, the local draft is removed.
Partial Responses for Form Owners
Form owners can see partial (unfinished) responses in analytics. They are surfaced read only so you can gauge where respondents drop off; they are not editable submissions.
Table Repeater
A Table Repeater field displays a table where respondents can add, remove, and fill in multiple rows. Each row has the same set of columns (fields). This is ideal for collecting lists of items: line items on an invoice, attendees at an event, or product orders.
Column Configuration
{
"type": "table_repeater",
"id": "field_tr_01",
"label": "Order Items",
"minRows": 1,
"maxRows": 20,
"defaultRows": 1,
"allowAddRow": true,
"allowDeleteRow": true,
"columns": [
{
"id": "col_product",
"type": "dropdown",
"label": "Product",
"options": ["Widget A", "Widget B", "Widget C"],
"required": true
},
{
"id": "col_quantity",
"type": "number",
"label": "Quantity",
"min": 1,
"max": 999,
"defaultValue": 1,
"required": true
},
{
"id": "col_notes",
"type": "short_text",
"label": "Notes",
"required": false
}
]
}Response Data Structure
{
"field_tr_01": [
{
"col_product": "Widget A",
"col_quantity": 3,
"col_notes": "Rush delivery"
},
{
"col_product": "Widget B",
"col_quantity": 1,
"col_notes": ""
}
]
}The response is an array of objects, one per row. Empty rows (where all columns are empty) are excluded from the response unless includeEmptyRows is set to true.
Appointment
The Appointment field lets a respondent book a time slot without leaving your form. When they pick a slot, the booking details are captured as part of the response. You choose the scheduling provider in the field settings.
Providers
- CalendarJet: the native provider. Connect your CalendarJet credentials in the form owner's integration settings, then pick an event type to expose as bookable slots. Publishing is blocked until an event type is selected and credentials exist.
- Embedded scheduler: for any other scheduler (such as a Calendly page), you can embed it as a generic iframe. The respondent books inside the embed.
Schema Structure
{
"type": "appointment",
"id": "field_appt_01",
"label": "Schedule a Meeting",
"provider": "calendarjet", // "calendarjet" | "iframe"
"calendarjetEventTypeId": "evt_30min", // when provider is "calendarjet"
"schedulerUrl": "", // when provider is "iframe": the scheduler page URL
"embedHeight": 650 // iframe height in pixels
}Response Data Format
{
"field_appt_01": {
"start": "2025-03-15T14:00:00Z",
"end": "2025-03-15T14:30:00Z",
"status": "confirmed"
}
}Calculation
A Calculation field displays a computed value based on a formula referencing other fields. It is read only: the respondent cannot edit it directly.
Formula Syntax
{
"type": "calculation",
"id": "field_calc_01",
"label": "Order Total",
"formula": "({field_quantity} * {field_unit_price}) * (1 + {field_tax_rate} / 100)",
"decimalPrecision": 2,
"prefix": "$",
"suffix": "",
"visible": true,
"fallbackValue": 0
}Available Functions
| Function | Example | Description |
|---|---|---|
SUM() | SUM({field_a}, {field_b}, {field_c}) | Adds all referenced values. |
AVG() | AVG({field_a}, {field_b}) | Average of the referenced values. |
MIN() | MIN({field_a}, {field_b}) | Smallest of the referenced values. |
MAX() | MAX({field_a}, {field_b}) | Largest of the referenced values. |
ROUND() | ROUND({field_a}, 2) | Round to N decimal places. |
IF() | IF({field_a} > 100, {field_a} * 0.9, {field_a}) | Conditional value: if condition is true, return second argument; otherwise, return third. |
ABS() | ABS({field_a} - {field_b}) | Absolute value. |
Decimal Precision
Set decimalPrecision to control how many decimal places are displayed. Set to 0 for whole numbers. The calculation engine uses floating-point arithmetic internally; the precision setting only affects display and the stored response value.
Hidden Field
A Hidden Field captures data without displaying anything to the respondent. The value is set from a predefined source and included in the form response.
Sources
{
"type": "hidden",
"id": "field_hidden_01",
"key": "campaign_source",
"source": "url_param", // "static" | "url_param" | "cookie" | "referrer"
"sourceConfig": {
"paramName": "utm_source" // For url_param: the query parameter name
},
"defaultValue": "direct" // Fallback if source value is empty
}Source Types
| Source | Config | Description |
|---|---|---|
static | value: "fixed_string" | A hardcoded value set at form creation time. Never changes. |
url_param | paramName: "utm_source" | Reads the value from a URL query parameter when the form loads. |
cookie | cookieName: "user_id" | Reads the value from a browser cookie. Useful for tracking returning visitors. |
referrer | (none) | Captures document.referrer: the URL of the page that linked to the form. |
Data Flow
Hidden field values are resolved at form load time. The resolution order is: source value (if available) then default value (if source is empty). The resolved value is included in the form submission payload alongside all visible field responses. In the API response, hidden fields appear just like any other field in the answers object.
Note: Hidden fields are never shown to respondents, but their values are visible in the response data, API responses, and exports. Do not use hidden fields to store sensitive secrets: they are not encrypted differently from other fields.