# Forms

Add forms to any page. Write `form:` on its own line followed by indented YAML — sitemd renders it as an interactive form that submits via webhook.

## Quick example

```markdown
form:
  webhook: https://hooks.example.com/contact
  submitLabel: Send
  fields:
    - id: email
      type: email
      label: Email
      required: true
    - id: message
      type: longtext
      label: Message
      placeholder: Your message...
```

form:
  webhook: https://hooks.example.com/contact
  submitLabel: Send
  fields:
    - id: email
      type: email
      label: Email
      required: true
    - id: message
      type: longtext
      label: Message
      placeholder: Your message...

Forms submit a JSON payload to the `webhook` URL via POST. No backend needed from sitemd — bring your own endpoint (Zapier, Make, n8n, a serverless function, etc).

## Field types

### Short text

Single-line text input. The default type if `type` is omitted.

```markdown
form:
  webhook: https://hooks.example.com/demo
  fields:
    - id: company
      type: shorttext
      label: Company Name
      placeholder: Acme Inc.
```

form:
  webhook: https://hooks.example.com/demo
  fields:
    - id: company
      type: shorttext
      label: Company Name
      placeholder: Acme Inc.

### Long text

Multi-line textarea.

```markdown
form:
  webhook: https://hooks.example.com/demo
  fields:
    - id: bio
      type: longtext
      label: Tell us about yourself
      placeholder: A few sentences...
```

form:
  webhook: https://hooks.example.com/demo
  fields:
    - id: bio
      type: longtext
      label: Tell us about yourself
      placeholder: A few sentences...

### Email

Input with built-in email validation.

```markdown
form:
  webhook: https://hooks.example.com/demo
  fields:
    - id: email
      type: email
      label: Email Address
      required: true
      placeholder: you@example.com
```

form:
  webhook: https://hooks.example.com/demo
  fields:
    - id: email2
      type: email
      label: Email Address
      required: true
      placeholder: you@example.com

### Phone

```markdown
form:
  webhook: https://hooks.example.com/demo
  fields:
    - id: phone
      type: phone
      label: Phone Number
```

form:
  webhook: https://hooks.example.com/demo
  fields:
    - id: phone
      type: phone
      label: Phone Number

### Number

Supports `min` and `max` constraints.

```markdown
form:
  webhook: https://hooks.example.com/demo
  fields:
    - id: quantity
      type: number
      label: Quantity
      min: 1
      max: 100
```

form:
  webhook: https://hooks.example.com/demo
  fields:
    - id: quantity
      type: number
      label: Quantity
      min: 1
      max: 100

### Date

```markdown
form:
  webhook: https://hooks.example.com/demo
  fields:
    - id: startDate
      type: date
      label: Start Date
```

form:
  webhook: https://hooks.example.com/demo
  fields:
    - id: startDate
      type: date
      label: Start Date

### Name

Composite field that renders first and last name side by side. Submitted as `{ first: "...", last: "..." }`.

```markdown
form:
  webhook: https://hooks.example.com/demo
  fields:
    - id: fullName
      type: name
      label: Full Name
      required: true
```

form:
  webhook: https://hooks.example.com/demo
  fields:
    - id: fullName
      type: name
      label: Full Name
      required: true

Use `name-first` or `name-last` for standalone single-name fields.

### Dropdown

```markdown
form:
  webhook: https://hooks.example.com/demo
  fields:
    - id: plan
      type: dropdown
      label: Plan
      options:
        - Free
        - Pro
        - Enterprise
```

form:
  webhook: https://hooks.example.com/demo
  fields:
    - id: plan
      type: dropdown
      label: Plan
      options:
        - Free
        - Pro
        - Enterprise

### Radio

Single-select radio buttons.

```markdown
form:
  webhook: https://hooks.example.com/demo
  fields:
    - id: priority
      type: radio
      label: Priority
      options:
        - Low
        - Medium
        - High
```

form:
  webhook: https://hooks.example.com/demo
  fields:
    - id: priority
      type: radio
      label: Priority
      options:
        - Low
        - Medium
        - High

### Checkbox

Multi-select checkboxes. Submitted as an array.

```markdown
form:
  webhook: https://hooks.example.com/demo
  fields:
    - id: interests
      type: checkbox
      label: Interests
      options:
        - Product Updates
        - Technical Blog
        - Case Studies
```

form:
  webhook: https://hooks.example.com/demo
  fields:
    - id: interests
      type: checkbox
      label: Interests
      options:
        - Product Updates
        - Technical Blog
        - Case Studies

### Rating

Star rating (1–5). Uses your theme's accent color.

```markdown
form:
  webhook: https://hooks.example.com/demo
  fields:
    - id: rating
      type: rating
      label: How would you rate us?
```

form:
  webhook: https://hooks.example.com/demo
  fields:
    - id: rating
      type: rating
      label: How would you rate us?

### Country

Dropdown with ~195 countries. Countries listed in `settings/forms.md` under `countrySortToTop` appear first.

```markdown
form:
  webhook: https://hooks.example.com/demo
  fields:
    - id: country
      type: country
      label: Country
```

form:
  webhook: https://hooks.example.com/demo
  fields:
    - id: country
      type: country
      label: Country

### Address

Composite field with street, city, state, zip, and country. Submitted as a nested object.

```markdown
form:
  webhook: https://hooks.example.com/demo
  fields:
    - id: address
      type: address
      label: Mailing Address
```

form:
  webhook: https://hooks.example.com/demo
  fields:
    - id: address
      type: address
      label: Mailing Address

### Heading and paragraph

Display-only fields for structuring your form. Not included in the submission payload.

```markdown
form:
  webhook: https://hooks.example.com/demo
  fields:
    - id: section1
      type: heading
      text: Personal Information
    - id: section1desc
      type: paragraph
      text: Fields marked with * are required.
    - id: name
      type: shorttext
      label: Your Name
      required: true
```

form:
  webhook: https://hooks.example.com/demo
  fields:
    - id: section1
      type: heading
      text: Personal Information
    - id: section1desc
      type: paragraph
      text: Fields marked with * are required.
    - id: yourname
      type: shorttext
      label: Your Name
      required: true

### Hidden

Not visible to the user. Useful for tracking data or passing context via URL prefill.

```markdown
form:
  webhook: https://hooks.example.com/demo
  fields:
    - id: source
      type: hidden
      default: landing-page
```

### Consent

Required agreement checkbox. The label text appears inline beside the checkbox. Always required — the form cannot be submitted without checking it.

```markdown
form:
  webhook: https://hooks.example.com/demo
  fields:
    - id: terms
      type: consent
      label: I agree to the terms and conditions.
```

form:
  webhook: https://hooks.example.com/demo
  fields:
    - id: terms
      type: consent
      label: I agree to the terms and conditions.

## Field properties

Every field requires `id` and `type`. All other properties are optional.

| Property | Description |
|---|---|
| `id` | Unique identifier. Used in the webhook payload and URL prefill. |
| `type` | Field type (see above). |
| `label` | Display label shown above the field. |
| `description` | Help text shown below the label. |
| `placeholder` | Placeholder text inside the input. |
| `required` | Set to `true` to require this field before submission. |
| `options` | List of choices for dropdown, radio, and checkbox types. |
| `default` | Default value. For dropdowns/radios, pre-selects the matching option. |
| `text` | Display text for heading and paragraph types. |
| `min` / `max` | Numeric constraints for number type. |
| `prefill` | Custom URL parameter name for prefilling (defaults to `id`). |
| `showWhen` | Conditional visibility — `fieldId = value` or `fieldId != value`. |
| `showWhenAny` | Show if any condition matches (OR logic). List of conditions. |
| `showWhenAll` | Show if all conditions match (AND logic). List of conditions. |

## Form settings

| Property | Description |
|---|---|
| `webhook` | URL to POST form data to (required). |
| `submitLabel` | Text on the submit button. Default: "Submit". |
| `thankYou` | Message shown after successful submission. |
| `redirectUrl` | URL to redirect to after submission (instead of thank you message). |

## Conditional logic

Show or hide fields based on other field values using `showWhen`:

```markdown
form:
  webhook: https://hooks.example.com/demo
  fields:
    - id: contactMethod
      type: dropdown
      label: Preferred Contact Method
      options:
        - Email
        - Phone
        - Mail
    - id: contactEmail
      type: email
      label: Email Address
      showWhen: contactMethod = Email
    - id: contactPhone
      type: phone
      label: Phone Number
      showWhen: contactMethod = Phone
    - id: contactAddress
      type: address
      label: Mailing Address
      showWhen: contactMethod = Mail
```

form:
  webhook: https://hooks.example.com/demo
  fields:
    - id: contactMethod
      type: dropdown
      label: Preferred Contact Method
      options:
        - Email
        - Phone
        - Mail
    - id: contactEmail
      type: email
      label: Email Address
      showWhen: contactMethod = Email
    - id: contactPhone
      type: phone
      label: Phone Number
      showWhen: contactMethod = Phone
    - id: contactAddress
      type: address
      label: Mailing Address
      showWhen: contactMethod = Mail

For complex conditions, use `showWhenAny` (OR) or `showWhenAll` (AND):

```yaml
- id: urgentNote
  type: paragraph
  text: We'll prioritize your request.
  showWhenAll:
    - priority = High
    - contactMethod = Phone
```

## Multi-page forms

Split forms into pages with optional conditional routing. Use `pages` instead of `fields`:

```markdown
form:
  webhook: https://hooks.example.com/apply
  thankYou: Application received!
  submitLabel: Submit Application
  pages:
    - id: info
      title: Your Info
      fields:
        - id: name
          type: name
          label: Name
          required: true
        - id: role
          type: radio
          label: Role
          options:
            - Developer
            - Designer
      next:
        - when: role = Developer
          goto: dev
        - default: general
    - id: dev
      title: Developer Questions
      fields:
        - id: language
          type: dropdown
          label: Primary Language
          options:
            - JavaScript
            - Python
            - Rust
    - id: general
      title: General
      fields:
        - id: about
          type: longtext
          label: Tell us about yourself
          required: true
```

form:
  webhook: https://hooks.example.com/apply
  thankYou: Application received!
  submitLabel: Submit Application
  pages:
    - id: info
      title: Your Info
      fields:
        - id: mpname
          type: name
          label: Name
          required: true
        - id: mprole
          type: radio
          label: Role
          options:
            - Developer
            - Designer
      next:
        - when: mprole = Developer
          goto: dev
        - default: general
    - id: dev
      title: Developer Questions
      fields:
        - id: mplanguage
          type: dropdown
          label: Primary Language
          options:
            - JavaScript
            - Python
            - Rust
    - id: general
      title: General
      fields:
        - id: mpabout
          type: longtext
          label: Tell us about yourself
          required: true

Pages flow sequentially by default. Add `next` rules to route conditionally — the first matching `when` condition wins, or `default` is used as a fallback. The last page shows the submit button.

## URL prefill

Pre-populate form fields by adding query parameters to the URL. By default, the parameter name matches the field `id`:

```
https://yoursite.com/contact?email=jane@example.com&source=newsletter
```

Use the `prefill` property to map a different URL parameter to a field:

```yaml
- id: email
  type: email
  label: Email
  prefill: user_email
```

Now `?user_email=jane@example.com` fills the email field.

## Answer piping

Reference field values in labels and descriptions using `{{fieldId}}`:

```yaml
- id: name
  type: shorttext
  label: Your Name
- id: feedback
  type: longtext
  label: Thanks {{name}}, what's on your mind?
```

The label updates live as the user types.

## Webhook payload

When a form is submitted, sitemd POSTs JSON to your webhook URL:

```json
{
  "formId": "form-1",
  "submittedAt": "2026-03-16T14:30:00.000Z",
  "page": {
    "title": "Contact Us",
    "url": "https://yoursite.com/contact"
  },
  "fields": {
    "email": "jane@example.com",
    "message": "Hello!",
    "name": { "first": "Jane", "last": "Smith" },
    "interests": ["Product Updates", "Case Studies"],
    "rating": "4",
    "address": {
      "street": "123 Main St",
      "city": "Austin",
      "state": "TX",
      "zip": "78701",
      "country": "United States"
    }
  },
  "meta": {
    "referrer": "https://google.com",
    "userAgent": "Mozilla/5.0...",
    "locale": "en-US"
  }
}
```

- Composite fields (`name`, `address`) submit as nested objects
- Checkboxes submit as arrays
- Hidden conditional fields are excluded
- Display-only fields (`heading`, `paragraph`) are excluded

## Global settings

Configure defaults for all forms in `settings/forms.md`:

```yaml
---
# Countries to sort to top of country dropdown
countrySortToTop:
  - United States
  - United Kingdom
  - Canada
  - Australia

# Default submit button label
submitLabel: Submit

# Default thank you message
thankYou: Thank you for your submission!

# Anti-spam honeypot field (invisible, catches bots)
honeypot: true
---
```

Per-form settings override these defaults.

## Notes

- Forms inside fenced code blocks are displayed as text, not rendered.
- Multiple forms can appear on the same page — each gets a unique ID.
- Forms inherit your theme's colors, fonts, and border radius automatically across light, dark, and paper modes.
- The honeypot field is invisible to users but catches bots. When a bot fills it, the form silently "succeeds" without actually submitting.
- Validation runs client-side. Required fields show inline error messages. Email fields validate the format automatically.
- Forms work with sitemd's instant navigation — they reinitialize after page transitions.