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:
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.
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.
$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 —
defaultValue,hidden, and the fields a prefill key names. - Form Controls —
inlinePrefill, where$parentand$recordresolve. - Access Control — the
requirelevel that makes$userresolvable. - Forms Overview — where
prefillsits in the full form schema.
Last updated September 1, 2026
This documentation was written with AI, so errors or outdated content are possible. Sovrium is in beta. Contributions and corrections are welcome.