# Fields

> Add signature and form fields to documents

Source: https://docs.korala.ai/guides/fields

---

Fields define where signers need to sign, initial, or fill in information on a document.

## Field Types

| Type | Description |
|------|-------------|
| `signature` | Full signature (drawn or typed) |
| `initials` | Initials only |
| `date` | Auto-filled signing date |
| `text` | Free-form text input |
| `checkbox` | Boolean checkbox |
| `dropdown` | Single choice from a fixed option list (`options`) |

## Field Properties

| Property | Type | Description |
|----------|------|-------------|
| `id` | string | Unique identifier |
| `signerId` | string | Which signer fills this field |
| `fieldType` | enum | Type of field |
| `pageNumber` | number | PDF page (1-indexed) |
| `xPosition` | number | X coordinate from left edge (points) |
| `yPosition` | number | Y coordinate from top edge (points) |
| `width` | number | Field width (points) |
| `height` | number | Field height (points) |
| `required` | boolean | Whether field must be filled |
| `label` | string | Prompt shown to the signer (e.g. "Your full name") |
| `value` | string | Filled value (null until signed) |
| `options` | string[] | Allowed values for `dropdown` fields |
| `acroFieldName` | string | Embedded PDF form field this field fills (see [Fillable PDFs](https://docs.korala.ai/guides/fillable-pdfs)) |
| `maxLength` | number | Maximum characters accepted at fill time |
| `validationPattern` | string | Regex the value must match (see [Fillable PDFs](https://docs.korala.ai/guides/fillable-pdfs#fill-time-validation)) |
| `validationMessage` | string | Signer-facing error when the pattern does not match |
| `groupKey` | string | Members sharing a key are mutually exclusive (choose-one) |
| `groupRequired` | boolean | Require at least one member of the group to be selected |
| `requiredWhenFieldId` | string | Conditional requirement: the field is required only while the referenced field is checked/filled (see [Conditionally Required Fields](#conditionally-required-fields)) |
| `requiredWhenValue` | string | Narrows the condition to one exact value of the referenced field |
| `sharedFill` | boolean | Any signer able to act may fill or correct it until its owner signs (see [Shared-Fill Fields](#shared-fill-fields)) |

## Adding Fields

    ```typescript
    // Add a signature field
    const signatureField = await korala.fields.create(documentId, {
      signerId: signer.id,
      fieldType: 'signature',
      pageNumber: 1,
      xPosition: 100,  // 100 points from left
      yPosition: 500,  // 500 points from top
      width: 200,
      height: 50,
    });

    // Add initials field
    const initialsField = await korala.fields.create(documentId, {
      signerId: signer.id,
      fieldType: 'initials',
      pageNumber: 1,
      xPosition: 400,
      yPosition: 700,
      width: 80,
      height: 40,
    });

    // Add date field (auto-fills when signed)
    const dateField = await korala.fields.create(documentId, {
      signerId: signer.id,
      fieldType: 'date',
      pageNumber: 1,
      xPosition: 300,
      yPosition: 500,
      width: 150,
      height: 30,
    });
    ```
    ```bash
    api_request "POST" "/api/v1/documents/${DOCUMENT_ID}/fields" \
      '{
        "signerId": "sig_abc123",
        "fieldType": "signature",
        "pageNumber": 1,
        "xPosition": 100,
        "yPosition": 500,
        "width": 200,
        "height": 50
      }'
    ```

## Collecting Information from Signers

Signers can fill in fields themselves. The signer completes any field created
**without a value** in the signing UI, on the hosted signing page and inside
the embedded [`<KoralaSigner>` iframe](https://docs.korala.ai/sdks/react) alike. Use this to collect
data your application doesn't have, such as phone numbers or consent
checkboxes, without adding those questions to your own forms.

What the signer sees for each field type:

| Field Type | Signer interaction |
|------------|--------------------|
| `text` | Inline text input on the document |
| `checkbox` | Click to check |
| `date` | Auto-fills the signing date on click |
| `signature` | Opens the signature pad (draw or type) |
| `initials` | Opens the signature pad for initials |

### Date formats

When a signer clicks a `date` field, the filled value follows the format the
document requires, resolved in this order:

1. The field's `dateFormat` property — e.g. `dateFormat: 'MM-DD-YYYY'` fills
   `08-20-2026`. Accepted formats combine `D`/`DD`, `M`/`MM`, and `YY`/`YYYY`
   tokens with a consistent `-`, `/`, or `.` separator; invalid formats are
   rejected at field creation. Only valid on `date` fields — for typed text
   input, use `validationPattern` instead.
2. A format token in the field's `label` — e.g. `label: 'Date (MM-DD-YYYY)'`.
   Handy when the label already prints the required format.
3. The field's `validationPattern` — common formats are tested against the
   regex and the first match is used (e.g. `^\d{4}-\d{2}-\d{2}$` fills
   `2026-08-20`).
4. Otherwise the signer's browser-locale format.

Set `dateFormat` explicitly when the document mandates a format; template
fields carry it onto every generated document.

Set a `label` so the field shows a meaningful prompt instead of the generic
"Enter text":

    ```typescript
    // The signer types their phone number into the document
    await korala.fields.create(documentId, {
      signerId: signer.id,
      fieldType: 'text',
      label: 'Phone number',
      pageNumber: 1,
      xPosition: 100,
      yPosition: 400,
      width: 200,
      height: 30,
    });

    // The signer checks a consent box
    await korala.fields.create(documentId, {
      signerId: signer.id,
      fieldType: 'checkbox',
      label: 'I agree to the terms',
      pageNumber: 1,
      xPosition: 100,
      yPosition: 450,
      width: 30,
      height: 30,
    });
    ```
    ```bash
    api_request "POST" "/api/v1/documents/${DOCUMENT_ID}/fields" \
      '{
        "signerId": "sig_abc123",
        "fieldType": "text",
        "label": "Phone number",
        "pageNumber": 1,
        "xPosition": 100,
        "yPosition": 400,
        "width": 200,
        "height": 30
      }'
    ```

Fields are **required by default**, so the signer cannot complete signing
until they fill each required field. Korala stamps the filled values into the
final PDF and returns them via the API once the signer has signed:

```typescript
const fields = await korala.fields.list(documentId);

for (const field of fields) {
  if (field.value) {
    console.log(`${field.label ?? field.fieldType}: ${field.value}`);
  }
}
```

If you're embedding the signing UI, the [React SDK](https://docs.korala.ai/sdks/react) emits an
`onFieldFilled` event each time the signer fills a field, so your app can
track progress in real time.

Prefilled values are read-only by default. A `text`, `date`, `dropdown`, or
`checkbox` field that already has a value (for example from template merge variables,
bulk generation, or a signing-packet lock) renders in the signing UI as a
muted value with a "set by the sender" hint, and the signer cannot change it.
To prefill a value the signer may correct, set `editable: true` on the
template field; see [Editable Defaults](https://docs.korala.ai/guides/templates#editable-defaults).
Prefill non-editable values only for data you're certain of.

Each field reports who wrote its current value in `filledBy`: `sender` for a
non-editable prefill or a packet lock, `signer` for anything the signer
entered (including bulk signing on their behalf), and `null` while nobody has
filled it, which includes an editable default the signer has not confirmed.
The signing UI uses it to tell the sender's values apart from the signer's
own entries.

## Shared-Fill Fields

Some forms are prepared by one party and confirmed by another: HR types the
employment details, the employee checks them and signs. Set
`sharedFill: true` on a field and any signer whose turn the signing order
allows may fill or correct it — until the field's owner signs, which
freezes it for everyone.

```typescript
await korala.fields.update(documentId, fieldId, {
  sharedFill: true,
});
```

The field still belongs to one signer. It counts toward *their* required
fields, their signing locks the final value, and the completed PDF carries
whatever stood when they signed — so the owner always has the last word on
a prepared value. The audit trail records who actually wrote each value.

Rules to know:

- `sharedFill` applies to `text`, `date`, `checkbox`, and `dropdown`
  fields. Signature and initials fields refuse it: a signature can only
  come from its owner.
- Order the owner **after** the preparer. A shared field owned by signer 1
  freezes as soon as they sign, before signer 2's turn ever starts.
- A required shared field is satisfied by a prepared value alone; keep the
  owner last in the signing order if they must see it before it counts.

In the signing UI, another signer's shared field renders with a dashed
border and names its owner. Template fields accept the same flag and carry
it onto every generated document.

## Conditionally Required Fields

Some fields are mandatory only when another answer makes them so. On the
IRS W-9, the LLC tax-classification code (C, S, or P) must be filled only
when the **LLC** classification box is checked. Express that by pointing the
dependent field at its controller:

```typescript
const llcBox = await korala.fields.create(documentId, {
  signerId: signer.id,
  fieldType: 'checkbox',
  label: 'LLC',
  pageNumber: 1,
  xPosition: 73,
  yPosition: 193,
  width: 8,
  height: 8,
  required: false,
});

await korala.fields.create(documentId, {
  signerId: signer.id,
  fieldType: 'text',
  label: 'LLC tax classification (C, S, or P)',
  pageNumber: 1,
  xPosition: 418,
  yPosition: 192,
  width: 29,
  height: 11,
  requiredWhenFieldId: llcBox.id,
});
```

While the controller is off, the field stays optional. Once the signer
checks the box, the field turns required: it joins the progress counter,
gets required styling, and Korala refuses completion until it is filled,
with the error `"LLC tax classification (C, S, or P)" is required when
"LLC" is selected`. Unchecking releases it again.

Rules to know:

- The controller must be a field on the same document belonging to the
  **same signer**. A checkbox controller counts when checked; any other
  type counts once filled.
- Set `requiredWhenValue` to trigger only on one exact value — e.g. a
  dropdown's `"Other"` choice requiring a "please specify" text field.
- Korala stores a conditional field as individually optional, forcing its
  `required` flag off at creation and rejecting `required: true` on later
  updates while the condition stands. The condition alone decides.
- Choose-one group members cannot carry a condition (the group's
  `groupRequired` covers them), though a group member — like the LLC box,
  part of the W-9's classification group — can be a controller.
- Clear a condition with `requiredWhenFieldId: null` on update. Clearing or
  re-pointing it also drops any `requiredWhenValue` narrowing, since a
  value chosen for one controller means nothing against another.
- Reassigning either field to a different signer clears the condition: the
  sign gate evaluates within one signer's fields, so a cross-signer link
  could never be enforced and Korala refuses to store one that looks live.
- Template fields accept the same properties, and generated documents carry
  the condition with references remapped for you.

## Coordinate System

PDF coordinates use **points** (1 point = 1/72 inch):

- Origin is at the **top-left** corner of the page
- `xPosition` increases going **right**
- `yPosition` increases going **down**
- A standard US Letter page is 612 x 792 points

```
(0,0) ────────────────────────────► x (612)
  │
  │     ┌─────────────────┐
  │     │   PDF Content   │
  │     │                 │
  │     │   ┌─────────┐   │
  │     │   │ Field   │   │
  │     │   │(100,500)│   │
  │     │   └─────────┘   │
  │     │                 │
  │     └─────────────────┘
  ▼
  y (792)
```

Use the document viewer to find coordinates, or calculate positions from page dimensions.

## Fillable PDFs (AcroForm)

Many pre-created documents, like the IRS W-9, ship as fillable PDFs with
embedded form fields. Korala reads those fields and writes values back
through them: detection proposes each field at its exact position, linked
fields fill natively (SSN digits land one per comb box, checkboxes render
the form's own checkmark), and the completed document is flattened so it
can no longer be edited.

See [Fillable PDFs](https://docs.korala.ai/guides/fillable-pdfs) for the full flow,
including the `GET /documents/{id}/form-fields` discovery endpoint and
radio-group handling.

## Recommended Field Sizes

| Field Type | Width | Height |
|------------|-------|--------|
| `signature` | 200 | 50 |
| `initials` | 80 | 40 |
| `date` | 150 | 30 |
| `text` | 200 | 30 |
| `checkbox` | 30 | 30 |

## How Content Fills the Box

`width` and `height` set the field's **bounds**. They do not set the size of
what lands inside it, so a box in the right place can still put the ink in the
wrong one.

**Signatures and initials.** Korala scales the image to fit inside the box,
preserves its aspect ratio, then centres it on both axes — the same "contain"
rule the signer preview uses, so a signature never stretches. Whichever
dimension runs out first sets the scale, and the other gains padding.

Signature images are usually much wider than they are tall, so **width is
normally the constraint**. A 600 x 21 px signature in a 200 x 24 pt box scales
by `200 / 600`, rendering about 7pt tall and leaving ~8pt of padding above and
below:

```
┌──────────────────────────────────┐  ← 24pt box
│                                  │
│     ~~~~~~~~~~~~~~~~~~~~~~~      │  ← ~7pt of ink, centred
│                                  │
└──────────────────────────────────┘
```

To make the signature render larger, **narrow the box** rather than raising its
height.

**Text, date and dropdown.** Values render left-aligned with 4pt of left
padding, centred against the box height, at `min(12, height * 0.6)` points
(a field imported from a fillable PDF keeps the point size, color and
alignment the form declares). A single-line value is never wrapped or shrunk
to fit, so a value wider than the box overflows it — size the box for your
longest expected value. A `multiline` field instead lays text out from the
top of the box, word-wrapped to its width, and drops lines that don't fit
the height.

**Checkbox.** A checked box draws an `X` centred in the box, sized to
`min(width, height) * 0.6`. An unchecked field draws nothing. (A checkbox
linked to an embedded form field renders the form's own checkmark instead —
see [Fillable PDFs](https://docs.korala.ai/guides/fillable-pdfs).)

The signing page previews filled values with these same rules — size, face,
alignment and wrapping — so what a signer sees while filling is what the
completed document shows.

## Listing Fields

    ```typescript
    const fields = await korala.fields.list(documentId);

    for (const field of fields) {
      console.log(`${field.fieldType} on page ${field.pageNumber}`);
      console.log(`  Position: (${field.xPosition}, ${field.yPosition})`);
      console.log(`  Assigned to: ${field.signerId}`);
      if (field.value) {
        console.log(`  Value: ${field.value}`);
      }
    }
    ```
    ```bash
    api_request "GET" "/api/v1/documents/${DOCUMENT_ID}/fields"
    ```

## Removing Fields

    ```typescript
    await korala.fields.delete(documentId, fieldId);
    ```
    ```bash
    api_request "DELETE" "/api/v1/documents/${DOCUMENT_ID}/fields/${FIELD_ID}"
    ```

You can remove fields only while the document is in `draft` status.

## Multiple Signers Example

Placing fields for multiple signers on the same document:

    ```typescript
    // Create document and add signers
    const alice = await korala.signers.create(documentId, {
      email: 'alice@example.com',
      name: 'Alice (Seller)',
      signingOrder: 1,
    });

    const bob = await korala.signers.create(documentId, {
      email: 'bob@example.com',
      name: 'Bob (Buyer)',
      signingOrder: 2,
    });

    // Alice's fields (left side)
    await korala.fields.create(documentId, {
      signerId: alice.id,
      fieldType: 'signature',
      pageNumber: 2,
      xPosition: 50,
      yPosition: 650,
      width: 200,
      height: 50,
    });

    await korala.fields.create(documentId, {
      signerId: alice.id,
      fieldType: 'date',
      pageNumber: 2,
      xPosition: 50,
      yPosition: 710,
      width: 100,
      height: 25,
    });

    // Bob's fields (right side)
    await korala.fields.create(documentId, {
      signerId: bob.id,
      fieldType: 'signature',
      pageNumber: 2,
      xPosition: 350,
      yPosition: 650,
      width: 200,
      height: 50,
    });

    await korala.fields.create(documentId, {
      signerId: bob.id,
      fieldType: 'date',
      pageNumber: 2,
      xPosition: 350,
      yPosition: 710,
      width: 100,
      height: 25,
    });
    ```

## Response Format

```json
{
  "id": "fld_abc123",
  "documentId": "doc_xyz789",
  "signerId": "sig_def456",
  "fieldType": "signature",
  "pageNumber": 1,
  "xPosition": 100,
  "yPosition": 500,
  "width": 200,
  "height": 50,
  "required": true,
  "value": null,
  "signatureImageUrl": null,
  "filledAt": null,
  "filledBy": null,
  "createdAt": "2024-01-14T09:00:00Z",
  "updatedAt": "2024-01-14T09:00:00Z"
}
```
