
# Form Fields

A form's `fields` array is an ordered list of field definitions rendered top to bottom. Each field is one of five **kinds**, discriminated by a `kind` property. Table-bound fields inherit their type and validation from a table column; standalone fields are typed inline; calculation, section, and signature fields cover computed values, dividers, and signatures.

```yaml
forms:
  - id: 1
    name: signup
    title: Create your account
    submitTo: { table: users }
    fields:
      - { kind: table-field, column: email, required: true }
      - { kind: standalone, name: newsletter, inputType: checkbox, label: Subscribe to updates }
```

## Field Kinds

| Kind          | Description                                                                                                                           |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `table-field` | Bound to a column on `submitTo.table`. Type, validation, and persistence flow from the table schema; the form overrides only display. |
| `standalone`  | Typed inline via `inputType`; not persisted to a column directly. Use for forms that route only to automations or the ledger.         |
| `calculation` | Read-only computed value derived from other fields via a `{{fieldName}}` formula. Renders no input.                                   |
| `section`     | Visual divider with an optional heading and description. Renders no input.                                                            |
| `signature`   | Captures a hand-drawn or typed signature.                                                                                             |

## Common Field Properties

Every input-bearing field kind (`table-field`, `standalone`, `calculation`, `signature`) shares a common base.

| Property       | Description                                                                                                                            |
| -------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `label`        | Label shown to the submitter. Supports `$t:` i18n keys.                                                                                |
| `placeholder`  | Placeholder text shown when the field is empty.                                                                                        |
| `helpText`     | Help text rendered below the field.                                                                                                    |
| `required`     | Boolean. When `true`, the field must have a value. For conditional requiredness use `requiredWhen`.                                    |
| `readOnly`     | Boolean. Renders the field display-only.                                                                                               |
| `hidden`       | Boolean. Hides the field from the UI but still submits it (server-only value).                                                         |
| `defaultValue` | Literal value, or a `$query.{name}` / `$user.{prop}` reference resolved at render time.                                                |
| `visibleWhen`  | Conditional visibility rule. See [Conditional Logic](/en/docs/form-conditional-logic).                                                 |
| `requiredWhen` | Conditional required rule (same shape as `visibleWhen`).                                                                               |
| `disabledWhen` | Conditional disabled rule (same shape as `visibleWhen`).                                                                               |
| `permissions`  | Per-field `read` permission. When the requesting role is excluded, the value is redacted from admin exports and per-submission detail. |

## Table-Bound Fields

A `kind: table-field` references a `column` on the form's `submitTo.table`. The field type, validation, and persistence all come from the table schema — the form supplies only display concerns plus, for attachment columns, upload options.

| Property      | Description                                                      |
| ------------- | ---------------------------------------------------------------- |
| `column`      | Column name on `submitTo.table`. Required.                       |
| `accept`      | Comma-separated MIME types or extensions for attachment columns. |
| `maxFileSize` | Maximum size (bytes) per uploaded file (attachment columns).     |
| `maxFiles`    | Maximum number of files for `multiple-attachments` columns.      |
| `dropZone`    | Render a drag-and-drop zone alongside the file picker.           |

```yaml
fields:
  - kind: table-field
    column: subject
    label: What can we help with?
    placeholder: Briefly describe the issue
  - kind: table-field
    column: priority
    helpText: Urgent issues are triaged first
```

Attachment columns (`single-attachment`, `multiple-attachments`) automatically render a file input. See [File Uploads](/en/docs/form-file-uploads) for the upload pipeline and bucket binding.

## Standalone Fields

A `kind: standalone` field is typed inline via `inputType` and is not written to a table column directly. It is ideal for forms that route to an automation or live only in the submission ledger.

| Property      | Description                                                                         |
| ------------- | ----------------------------------------------------------------------------------- |
| `name`        | Field name unique within the form (`^[a-zA-Z][a-zA-Z0-9_-]*`, ≤64 chars). Required. |
| `inputType`   | Control type (see below). Required.                                                 |
| `options`     | `{ value, label }` choices for `select` / `multi-select` / `radio`.                 |
| `accept`      | Accepted MIME types for `attachment` input.                                         |
| `maxFiles`    | Max files for `attachment` input.                                                   |
| `maxFileSize` | Maximum size (bytes) per uploaded file.                                             |
| `dropZone`    | Render a drag-and-drop zone alongside the file picker.                              |

**Available `inputType` values:** `short-text`, `long-text`, `email`, `url`, `phone`, `number`, `date`, `datetime`, `select`, `multi-select`, `checkbox`, `radio`, `rating`, `attachment`.

```yaml
fields:
  - kind: standalone
    name: rating
    inputType: rating
    label: How was your experience?
    required: true
  - kind: standalone
    name: source
    inputType: select
    label: How did you hear about us?
    options:
      - { value: search, label: Search engine }
      - { value: referral, label: Referral }
      - { value: social, label: Social media }
```

## Calculation, Section & Signature Fields

| Kind          | Key properties                                                                                                                         |
| ------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `calculation` | `name`, `formula` (references other fields via `{{name}}`), optional `format` (`number` / `currency` / `percent` / `text`). Read-only. |
| `section`     | Optional `heading`, `description`, and a whole-section `visibleWhen` rule. Renders no input.                                           |
| `signature`   | `name`. Captures a hand-drawn or typed signature.                                                                                      |

```yaml
fields:
  - { kind: standalone, name: quantity, inputType: number, label: Quantity }
  - { kind: standalone, name: unit_price, inputType: number, label: Unit price }
  - kind: calculation
    name: total
    label: Total
    formula: '{{quantity}} * {{unit_price}}'
    format: currency
```

## Prefill

The top-level `prefill` map seeds field values at render time. Keys are form field names; values are either a literal default or a reference:

| Reference        | Resolves to                                                                                                  |
| ---------------- | ------------------------------------------------------------------------------------------------------------ |
| `$query.{name}`  | A URL query-string parameter (e.g. `$query.utm_source`).                                                     |
| `$user.{prop}`   | A property of the authenticated user (e.g. `$user.email`). Silently dropped on public forms with no session. |
| `$parent.{path}` | A field of the parent record — only valid for inline-relationship forms (see below).                         |

```yaml
forms:
  - id: 1
    name: lead-capture
    title: Request a demo
    submitTo: { table: leads }
    prefill:
      utm_source: $query.utm_source
      utm_campaign: $query.utm_campaign
    fields:
      - { kind: table-field, column: email, required: true }
      - { kind: standalone, name: utm_source, inputType: short-text, hidden: true }
      - { kind: standalone, name: utm_campaign, inputType: short-text, hidden: true }
```

:::callout
**Two ways to default a value.** Per-field `defaultValue` accepts `$query` / `$user` references inline, while the top-level `prefill` map keeps prefill wiring in one place. On a public form (`access.require: all`), per-field `defaultValue: $user.*` is rejected at validation time — use top-level `prefill` (which tolerates an empty `$user` resolution) or require authentication.
:::

## Inline Relationship Create

When a form is embedded inside a parent record's detail page (e.g. a "+ New ticket" button on a Project page), the `$parent.{path}` prefill source ties the new child back to its parent automatically. This is configured on the page form control via `inlinePrefill`.

| Property      | Description                                                                                                               |
| ------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `prefill`     | Field-name → prefill-source map (same `$query` / `$user` / `$parent` union).                                              |
| `lockPrefill` | When `true`, the prefilled fields are hidden and cannot be overridden (still validated server-side). Defaults to `false`. |

```yaml
forms:
  - id: 1
    name: new-ticket
    submitTo: { table: tickets }
    fields:
      - { kind: table-field, column: title, required: true }
      - { kind: table-field, column: project_id }
      - { kind: table-field, column: reporter_id }

pages:
  - id: 1
    name: project-detail
    path: /portal/projects/:id
    components:
      - type: form
        formRef: new-ticket
        inlinePrefill:
          prefill:
            project_id: $parent.id # locked to the current project
            reporter_id: $user.id
          lockPrefill: true
```

On submit, the engine revalidates parent existence to defend against stale `$parent` references. Both single and multi-relationship columns are supported.

## Related Pages

- [Forms Overview](/en/docs/forms-overview) — form metadata, submit targets, access control.
- [Conditional Logic](/en/docs/form-conditional-logic) — `visibleWhen` / `requiredWhen` / `disabledWhen`.
- [Multi-Step Forms](/en/docs/form-multi-step) — distributing fields across steps.
- [File Uploads](/en/docs/form-file-uploads) — attachment input options and bucket binding.
- [Relational Fields](/en/docs/relational-fields) — the relationship columns `$parent` targets.
- [Form Controls](/en/docs/form-controls) — the page component that hosts `formRef` + `inlinePrefill`.
