
# Form Prefill

A form that already knows the answer should not ask for it. The campaign that sent the visitor is in the URL; the signed-in user's email is in the session. Asking anyway costs a field, and the answer you get back is worse than the one you already had.

`prefill` maps field names to where their initial value comes from:

```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
      plan: Starter
    fields:
      - { kind: table-field, column: email, required: true }
      - { kind: standalone, name: plan, inputType: short-text }
      - { kind: standalone, name: utm_source, inputType: short-text, hidden: true }
      - { kind: standalone, name: utm_campaign, inputType: short-text, hidden: true }
```

## Prefill Sources

Every value in the map is either a reference or a literal. References resolve server-side while the form is being rendered.

| Source                  | Resolves to                                                                 | When it cannot resolve                  |
| ----------------------- | --------------------------------------------------------------------------- | --------------------------------------- |
| `$query.<name>`         | The named URL query-string parameter of the request that rendered the form. | Entry dropped; the field renders empty. |
| `$user.<prop>`          | A property of the signed-in user — `$user.email`, `$user.id`.               | Entry dropped; the field renders empty. |
| String, number, boolean | Itself, verbatim — a plain default the submitter can overwrite.             | —                                       |

An unresolvable reference is removed from the map, never rendered. The submitter sees an empty input, and the literal `$query.utm_campaign` never leaks into the HTML.

## Prefilled Hidden Fields

A prefill on a `hidden: true` field is the point of the feature for attribution work. The field renders as an `<input type="hidden">` carrying the resolved value, so the campaign the visitor arrived on rides into the record without ever appearing on screen — and without a hand-written tracking script.

```yaml
prefill:
  campaign: $query.ref
fields:
  - { kind: table-field, column: email, required: true }
  - { kind: table-field, column: campaign, hidden: true }
```

`/forms/lead-capture?ref=partner-x` now stores `partner-x` in the record's `campaign` column.

## `$user` Needs a Session

`$user.<prop>` resolves only when the request carries an authenticated session, which in practice means the form requires one. On a public form the reference simply drops and the field renders empty — no error, no leaked session state.

That tolerance is specific to the `prefill` map. A per-field `defaultValue: $user.email` on a public form is a configuration mistake rather than a runtime shrug, and it is rejected when the config is decoded, naming the form and the offending field. Either require authentication, or move the reference into `prefill`.

:::callout
**`$parent` and `$record` do not resolve here.** The schema accepts them, but the top-level `prefill` map is resolved against the request — query string and session — and knows nothing about a host record. Any token it does not recognise passes through as a literal string, so `$parent.id` in `forms[].prefill` renders the characters `$parent.id` into the field. Parent-record prefill belongs on the page's form control, as `inlinePrefill`, where a host record actually exists.
:::

## Prefill Versus `defaultValue`

Both seed an initial value, and both accept `$query` / `$user` references. The choice is about where the wiring lives.

| Use            | When                                                                                                  |
| -------------- | ----------------------------------------------------------------------------------------------------- |
| `prefill`      | Attribution and session seeding — the wiring is a concern of the form, and reads better in one block. |
| `defaultValue` | A value that is part of the field's own definition, such as a fixed starting quantity.                |

## Related Pages

- [Form Fields](/en/docs/form-fields) — `defaultValue`, `hidden`, and the fields a prefill key names.
- [Form Controls](/en/docs/form-controls) — `inlinePrefill`, where `$parent` and `$record` resolve.
- [Access Control](/en/docs/form-access) — the `require` level that makes `$user` resolvable.
- [Forms Overview](/en/docs/forms-overview) — where `prefill` sits in the full form schema.
