Field Types
Every form field has a type from a fixed set. The type determines what the widget renders, what shape data[fieldId] takes on submit, and which validation rules the server enforces.
Types at a glance
Section titled “Types at a glance”| Type | Submitted value | Validation |
|---|---|---|
text | string | required, min/max length, pattern |
email | string | + email format |
tel | E.164 string | + libphonenumber-js validity |
select | string | matches options[].value |
checkbox | string or string[] | each entry matches options[].value |
location | studio string (UUID) | matches options[].value |
appointment-type | appointmentType string (UUID) | in allowedAppointmentTypes and publicly bookable |
booking-calendar | (none — see below) | top-level bookingSlot |
captcha | (none — see below) | top-level captchaToken |
privacy | literal "true" (consent mode) | strict equality |
Field config (shared interface)
Section titled “Field config (shared interface)”Every field shares this base shape:
interface FormField { id: string; type: '...'; label: string; description?: string; placeholder?: string; required?: boolean; name?: string; // stable key — emitted as DOM `name=`, surfaces as `fieldName` in events and `resolvedData` keys in webhooks fieldMappingKey?: string; // destination column (camelCase) or custom-field UUID defaultValue?: string | string[]; // lowest-priority prefill — see prefill docs validation?: { required?, minLength?, maxLength?, format?, pattern? }; showIf?: { field: string; value: string | string[] }; options?: { value: string; label: string }[]; bookingConfig?: { allowedAppointmentTypes: string[]; maxDaysInFuture?: number }; appointmentTypeConfig?: { locationFieldRef?: string; allowedAppointmentTypes?: string[] }; privacyConfig?: { mode: 'informative' | 'consent'; text: string; links?: { label, url }[] };}fieldMappingKey directs a submitted value to a column on the destination Lead Pool (e.g., firstName, email, phone) or to an org-level custom field by UUID. Unmapped fields stay in submittedData and the webhook payload but don’t populate the lead-pool entry.
showIf controls visibility — when a referenced field has a different value (or the referenced field is itself hidden), the dependent field skips required/validation. Hidden-field values from prior visibility states are accepted on submit and forwarded to webhooks.
Plain text input.
Submitted value: string.
Server validation:
required— non-empty stringvalidation.minLength/maxLengthvalidation.pattern— compiled to RegExp; matches against the full value
Single-line email input.
Submitted value: string.
Server validation: all of text’s rules, plus a loose email format check (^[^\s@]+@[^\s@]+\.[^\s@]+$).
Phone number input.
Submitted value: string — what you submit; what gets persisted is the E.164-normalized form.
Server validation:
required,minLength,maxLength,pattern(against the original input).libphonenumber-jsvalidity check after normalization.
select
Section titled “select”Single-choice dropdown.
Submitted value: string — must match one of options[].value.
Server validation: required (if set), value must be present in the options list.
checkbox
Section titled “checkbox”Multi-choice checkbox group.
Submitted value: string or string[] — each entry must match options[].value.
Server validation: required (if set), each value must be in the options list.
location
Section titled “location”Studio selector for multi-studio forms.
Submitted value: string — the selected studio ID (UUID), matching one of options[].value.
Server validation: required (if set), value must match an option.
The form editor pins each option’s label to the per-form display string, separate from the live studios.name. Renaming a studio after publication doesn’t update the form’s location label until the form is re-published.
appointment-type
Section titled “appointment-type”Appointment-type selector (without a calendar slot).
Submitted value: string — the selected appointment-type ID (UUID).
Server validation: must be in the field’s allowedAppointmentTypes (or ['*'] wildcard) and publicly bookable on the resolved calendar — same allowlist that GET /appointment-types returns.
The resolution can chain off a location sibling via appointmentTypeConfig.locationFieldRef. Multiple appointment-type fields per form are supported.
booking-calendar
Section titled “booking-calendar”Calendar slot picker.
Submitted value: none in data. The selected slot is sent as a top-level bookingSlot on /submit:
bookingSlot: { appointmentTypeId: string; calendarId: string; resourceId?: string; startDateTime: string; // ISO-8601 endDateTime: string; // ISO-8601}Server validation:
- Required when a visible booking-calendar field exists.
startDateTime < endDateTime.- Calendar/studio/appointment-type allowlist re-check.
- Existence check — appointment-type and calendar must still exist.
- Live availability re-fetch — the slot must still be available, with
available: true. - Capacity recheck inside the DB transaction (race-condition guard).
The created booking surfaces in the webhook payload as calendarBookingId and the field’s resolved block in fieldContext carries the appointment-type, calendar, resource, and start/end labels.
captcha
Section titled “captcha”Spam-prevention challenge.
Submitted value: none in data. The token goes in the top-level captchaToken on /submit. See Public API → Captcha for the discovery flow.
Server validation:
- Token is present (when the form has a configured provider).
- The provider’s verify endpoint returns success.
privacy
Section titled “privacy”Privacy-policy notice or consent checkbox.
Submitted value:
mode: 'informative'— no value required; the act of submission marksacceptedPrivacyPolicy = 'implicit'.mode: 'consent'— must be the literal string"true"to indicate explicit consent.
Server validation:
- For
consentmode: strict equality with"true". Anything else is rejected withField "<label>" is required: explicit privacy consent missing.
The submitted value is stripped from the persisted data regardless of mode — consent state is captured in the lead-pool entry’s acceptedPrivacyPolicy column instead. Privacy fields cannot be prefilled.
Related
Section titled “Related”- Public API — endpoint reference for
/submit,/slots,/appointment-types. - Prefill — what each field type accepts as a prefill value.
- Webhooks — how each field type’s value renders in
data,fieldContext, andresolvedData. - Events —
field:change/field:blurcarryfieldIdand the stablefieldName.