Skip to content

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.

TypeSubmitted valueValidation
textstringrequired, min/max length, pattern
emailstring+ email format
telE.164 string+ libphonenumber-js validity
selectstringmatches options[].value
checkboxstring or string[]each entry matches options[].value
locationstudio string (UUID)matches options[].value
appointment-typeappointmentType string (UUID)in allowedAppointmentTypes and publicly bookable
booking-calendar(none — see below)top-level bookingSlot
captcha(none — see below)top-level captchaToken
privacyliteral "true" (consent mode)strict equality

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 string
  • validation.minLength / maxLength
  • validation.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-js validity check after normalization.

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.

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.

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 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.

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.

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-policy notice or consent checkbox.

Submitted value:

  • mode: 'informative' — no value required; the act of submission marks acceptedPrivacyPolicy = 'implicit'.
  • mode: 'consent' — must be the literal string "true" to indicate explicit consent.

Server validation:

  • For consent mode: strict equality with "true". Anything else is rejected with Field "<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.

  • 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, and resolvedData.
  • Eventsfield:change / field:blur carry fieldId and the stable fieldName.